Why Babel introduces unused variable when destructuring property from object? - javascript

I just looked at compiled script of my source code and I think I noticed something weird.
This ES6 code:
const data = {someProperty:1, someNamedPropery: 1, test:1};
const {
someProperty, // Just gets with 'someProperty' peoperty
someNamedPropery: changedName, // Works same way, but gets with 'changedName' property
test: changedNameWithDefault = 1, // This one, introduces new variable and then uses it to compare that to void 0
} = data;
Compiles to this:
"use strict";
const data = {someProperty:1, someNamedPropery: 1, test:1};
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
_data$test = data.test,
changedNameWithDefault = _data$test === void 0 ? 1 : _data$test;
I am curious why Babel introduces new variable _data$test. Can't it just be something like this?
...
changedNameWithDefault = data.test === void 0 ? 1 : data.test;
It still works.
Notice, that new variable introduction happens only when I'm trying to assign default value if key isn't present in data variable or is undefined.
Does this affect application performance if data.test is big enough? And I wonder if Garbage Collector takes care of it (_data$test variable).

Can't it just be something like this?
No, but it's slightly subtle why not: If test is an accessor property, accessing it is a function call. The destructuring code, and Babel's translation of it, will only call the accessor once. Your change would call it twice. Here's an example:
const data = {
someProperty:1,
someNamedPropery: 1,
get test() {
console.log(" test called");
return 1;
},
};
function es2015OriginalCode() {
const {
someProperty,
someNamedPropery: changedName,
test: changedNameWithDefault = 1,
} = data;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
function babelCode() {
"use strict";
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
_data$test = data.test,
changedNameWithDefault = _data$test === void 0 ? 1 : _data$test;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
function yourCode() {
"use strict";
var someProperty = data.someProperty,
changedName = data.someNamedPropery,
changedNameWithDefault = data.test === void 0 ? 1 : data.test;
console.log(` changedNameWithDefault = ${changedNameWithDefault}`);
}
console.log("ES2015 original code:");
es2015OriginalCode();
console.log("Babel's code:");
babelCode();
console.log("Your code:");
yourCode();
.as-console-wrapper {
max-height: 100% !important;
}
While we can tell looking at the code that test isn't an accessor property in that specific example, the code transformations don't usually work with that much context, so this is the safer general case way to translate that code.

Related

How to mock global variable in Jest

I will try to be brief and to the point. In our school we use Google Sheets as a class book. I have a script that counts the absences of individual students. The script is written in Google Apps Scripts (GAS), but the purpose of the application and even GAS are irrelevant to this question.
I'm currently trying to rewrite my old code and try to implement Jest unit testing. So my question will be simple. How can I mock a global variable that I call inside the function under test?
Here I paste the part of the code that introduces the global variables:
const absenceFileId = "some_string"
const teachersFileId = "some_string"
const studentGroupsId = "some_string"
const classbookFolderId = "some_string"
// STATE
let teachers = {}
let groups = {}
let subjectCounter = {}
let errors = []
let students = {}
function setupStateVariables() {
teachers = getTeachers(removeEmptyRowsAndHeader(getValuesArray(teachersFileId, 0, 1, 1, 50, 3)));
groups = getGroups(removeEmptyRowsAndHeader(getValuesArray(studentGroupsId, 0, 1, 1, 400, 6)))
subjectCounter = createSubjectCounter(removeEmptyRowsAndHeader(getValuesArray(studentGroupsId, 1, 1, 1, 200, 4)))
}
Subsequently, I need to update the global variable subjectCounter inside the function:
function updateSubjectCounter(group, schoolClass, subject) {
if (!(subject in subjectCounter[schoolClass])) {
return
}
if (group) {
subjectCounter[schoolClass][subject][group] += 1
} else {
let keys = Object.keys(subjectCounter[schoolClass][subject])
if (keys.length > 1) {
for (let key of keys) {
subjectCounter[schoolClass][subject][key] += 1
}
} else {
subjectCounter[schoolClass][subject][null] += 1
}
}
}
I have the previous code in the main.js module.
I export the function for testing as follows. If statement is here due to GAS enviroment.:
if (typeof module !== 'undefined') {
module.exports = {
"getTeachers": getTeachers,
"getGroups": getGroups,
"createSubjectCounter": createSubjectCounter,
"updateSubjectCounter": updateSubjectCounter
}
}
For testing, I use the main.test.js module, which looks like this:
const main = require('./main');
test("Test funkce updateSubjectCounter", () => {
main.updateSubjectCounter("", "a", "IFM")
expect(subjectCounter).toEqual({something})
The question is how to mock the global variable subjectCounter in this test?
EDIT: I am using local enviroment with NodeJS for testing.

Overwriting a variable at the beginning of a JavaScript calculation with a produced variable

New to this and have an issue. I need to run a calculation - that will then compare a produced variable with a default value provided at the start and if different override the default and rerun the calculation. It’s all ifs and else right now....
An example would be
let defaultCount = 3;
defaultCount = actualCount ? actualCount : defaultCount;
actualCount = a + b.
So the problem is that in the beginning actualCount isn’t defined yet. So I need to ignore this, then rerun once it has been defined?
defaultCount is needed to get the comparison.
Thanks in advance!
so the at the top of the code I had
let appliedTaxRate = 0.45
if(incomeTaxRate == undefined) {
appliedTaxRate = appliedTaxRate
} else {
appliedTaxRate = incomeTaxRate
}
In between there are a lot of calculations that then add to the final comparison below, these need to use the default value for the first load up,
then it would need to rerun if the new calculated value is different than the
starting default value. Which makes the whole calculation need to run twice.
and at the base I have
//incomeTaxRate
if(totalEarnings>personalHigherUL) {
incomeTaxRate = personalAdvancedRate
} else {
if(totalEarnings>personalBaseUL) {
incomeTaxRate = personalHigherRate
} else {
if(totalEarnings>personalZeroUL) {
incomeTaxRate = personalBaseRate
} else {
incomeTaxRate = 0
}
}
}
You would need to declare actualCount as undefined, then have a function check if it's defined. If not, defaultCount is used.
let defaultCount = 3;
let actualCount = undefined;
checkCount();
actualCount = 1;
checkCount();
function checkCount() {
if (actualCount) {
defaultCount = actualCount;
}
console.log(defaultCount);
}
My general finished work around was like this:
let actualCount;
let function = mainCalculation() {
let function = primaryCalculation() {
...calculations
};
primaryCalculation();
primaryCalculation();
};
mainCalculation();
I removed the if/else statements and the default variable.
Thanks for all the help

Which type of variable is created in the code below

In the code below is an iterator:
const cart = ['Product 0','Product 1','Product 2','Product 3','Product 4','Product 5','Product 6','Product 7','Product 8','Product 9','Product 10']
function createIterator(cart) {
let i = 0;//(*)
return {
nextProduct: function() {
//i:0; (**)
let end = (i >= cart.length);
let value = !end ? cart[i++] : undefined;
return {
end: end,
value: value
};
}
};
}
const it = createIterator(cart);
First I know a copy of the present state of the function's variables and the parameters are parsed.(Right?)...
And when you run
const it = createIterator(cart);
Is a property below created?
//i:0 (**);
Making it.next(); equivalent to
{
i:0;//state or value of i from createIterator() definition;
next : function(cart){
let end = (this.i >= cart.length);
let value = !end ? cart[this.i++] : undefined;
return {
end: end,
value: value
};
}
Or does state of the value of i in line (*) from the first code, Is what's what is modified?
Please if this point is not clear... let me know to explain better.
Calling the iterator will create an instance of i scoped to the createIterator function. The object returned from it will only have access to that specific instance of i, but i is not a property of the object returned. It only can access it due to the function scope.
You can see a little better how this works if we break your code down a little more simply:
function createIterator(cart, display) {
let i = 0;
return {
next: function() {
i++;
console.log(display + ' next: ', i);
}
};
}
const cart1 = [];
const cart2 = [];
const it1 = createIterator(cart1, 'cart1');
it1.next();
it1.next();
const it2 = createIterator(cart2, 'cart2');
it2.next();
it2.next();
Each instance of the iterator has a different copy of i and only the object returned from the iterator function can access it.

How do I manage context when exposing object methods in JS modules?

Okay, I realize this can be considered subjective, but I'm trying to better understand how to consider scope when writing modules that only expose what's needed publicly. I have a string utility that I've written as an object literal below:
const substrings = {
query: {},
text: "",
results: [],
exists: function (index) {
const exists = index >= 0
return exists
},
check: function () {
const q = this.query
const start = q.openIndex
const stop = q.closeIndex
if (q.hasOpen && !q.hasClose) {
console.log("Missing closing delimiter.")
}
if (!q.hasOpen && q.hasClose) {
console.log("Missing opening delimiter.")
}
if (q.hasOpen && q.hasClose && start > stop) {
console.log("Closing delimiter found before opening.")
}
if (!q.hasOpen && !q.hasClose && this.results.length == 0) {
console.log("No results found.")
}
const order = start < stop
const check = q.hasOpen && q.hasClose && order
return check
},
update: function () {
const q = this.query
const text = this.text
q.before = this.text.indexOf(q.open)
q.start = q.before + q.open.length
this.text = text.slice(q.start, text.length)
q.stop = this.text.indexOf(q.close)
q.after = q.stop + q.close.length
q.openIndex = q.before
q.closeIndex = q.before + q.stop
q.hasOpen = this.exists(q.openIndex)
q.hasClose = this.exists(q.stop)
const newPosition = q.start + q.after
q.position = q.position + newPosition
this.query = q
},
substrings: function () {
const q = this.query
const current = this.text.slice(0, q.stop)
const fullLength = this.text.length
this.text = this.text.slice(q.after, fullLength)
this.results.push(current)
this.update()
if (this.check()) {
this.substrings()
}
},
init: function (open, close, text) {
this.results = []
this.query = {
open,
close,
position: 0,
}
this.text = text
this.update()
},
getSubstrings: function (open, close, text) {
this.init(open, close, text)
if (this.check()) {
this.substrings()
return this.results
}
},
getSubstring: function (open, close, text) {
this.init(open, close, text)
if (this.check()) {
return this.text.slice(0, this.query.stop)
}
}
}
I want to use it as a Node module and expose the getSubstring and getSubstrings methods, but if I were to do:
module.exports = {
all: substrings.getSubstrings,
one: substrings.getSubstring
}
I would get an error due to the usage of this. I realize that if I replace this with the object var name substrings to reference it directly, it works. I could also refactor it to be one big function or smaller functions and just export the 2 I need.
I am trying to go about learning things the right way and am struggling with how I should be thinking about context. I understand how this changes here, but I feel like I'm not fully wrapping my head around how I should consider context when structuring my code.
Is there a more elegant solution to expose methods with code like this that wasn't written to separate private and public methods?
A simple solution would be to bind the exported functions to the proper calling context inside the exports object:
module.exports = {
all: substrings.getSubstrings.bind(substrings),
one: substrings.getSubstring.bind(substrings)
}
Personally, I prefer using the revealing module pattern over object literals for situations like this. With the revealing module pattern, create an IIFE that returns the desired functions, referring to local variables instead of properties on this. For example:
const { getSubstrings, getSubstring } = (() => {
let query = {}
let text = ''
let results = []
function exists(index) {
return index >= 0
}
function check() {
const q = query;
// ...
}
...
function getSubstrings(open, close, text) {
}
...
return { getSubstrings, getSubstring };
})();
module.exports = {
all: getSubstrings,
one: getSubstring
}
This is somewhat opinion-based, but code can be easier to read when there aren't any this references to worry about.

Reference an object, based on a variable with it's name in it

I'm looking for a way to reference an object, based on a variable with it's name in it.
I know I can do this for properties and sub properties:
var req = {body: {jobID: 12}};
console.log(req.body.jobID); //12
var subProperty = "jobID";
console.log(req.body[subProperty ]); //12
var property = "body";
console.log(req[property][subProperty]); //12
is it possible for the object itself?
var req = {body: {jobID: 12}};
var object = "req";
var property = "body";
var subProperty = "jobID";
console.log([object][property][subProperty]); //12
or
console.log(this[object][property][subProperty]); //12
Note: I'm doing this in node.js not a browser.
Here is an exert from the function:
if(action.render){
res.render(action.render,renderData);
}else if(action.redirect){
if(action.redirect.args){
var args = action.redirect.args;
res.redirect(action.redirect.path+req[args[0]][args[1]]);
}else{
res.redirect(action.redirect.path);
}
}
I could work around it by changing it to this, but I was looking for something more dynamic.
if(action.render){
res.render(action.render,renderData);
}else if(action.redirect){
if(action.redirect.args){
var args = action.redirect.args;
if(args[0]==="req"){
res.redirect(action.redirect.path+req[args[1]][args[2]]);
}else if(args[0]==="rows"){
rows.redirect(action.redirect.path+rows[args[1]][args[2]]);
}
}else{
res.redirect(action.redirect.path);
}
}
Normally it's impossible to reference an object by its name. But since you have only two possible candidates...
var args, redirect_path = '';
if(args = action.redirect.args) {
try {
redirect_path = ({req:req,rows:rows})[args[0]][args[1]][args[2]];
} catch (_error) {}
}
res.redirect(action.redirect.path + (redirect_path || ''));
I'm using inline object {req:req,rows:rows} as a dictionary to lookup for args[0] value.
I wrapped the whole construction with try ... catch, because it's dangerous. For example, passing anything except 'req' or 'rows' as args[0] will result in an exception being thrown.
I also moved res.redirect outside of if to clarify the code.
Update
It's also possible to use args array with arbitrary length. But to do so you'll need to loop through args array:
var branch, redirect_path;
if (action.redirect.args) {
try {
branch = { req: req, rows: rows }
action.redirect.args.forEach(function(key) {
branch = branch[key];
});
if ('string' === typeof branch) {
redirect_path = branch;
}
} catch (_error) {}
}
res.redirect(action.redirect.path + (redirect_path || ''));
A added 'string' === typeof branch check to ensure that the resulting value is a string.

Categories

Resources