New property assignment to object leads to weird behaviour in NodeJS - javascript

On a recent interview process, I was asked to write code for an API.
I used NodeJS, v16.14.0.
However, I encountered some very weird behavior:
module.exports = {
findSomething: (data) => {
const keys = Object.keys(data).sort();
let maxObj = null;
let maxKey = null;
for (let i = 0; i < keys.length; ++i) {
let key = keys[i];
const currObj = data[key];
if (maxObj == null || currObj.prop > maxObj.prop) {
maxObj = currObj;
maxKey = key;
}
}
// Attempt to assign a new property to the object:
// Variant 1, causes odd behaviour.
maxObj.maxKey = maxKey;
// Variant 2, object copy - works OK.
let newData = {...maxObj}
newData.maxKey = maxKey;
return newData;
}
}
This very minor change (Variant 1 instead of 2), gave no errors, however led the function behave very oddly. sometimes producing right results and others just nothing.
This led me to lose precious time, and eventually I was stunned to find it out.
I am not aware of cases of such behaviour. As I want to learn from this and get better, are you aware of why this could possibly happen, or if I am missing something obvious here? I know that assigning a new property to an existing object is fine in JavaScript, and my variables were all within scope usage.

Related

How to prevent duplication of values in local storage?

I'm coding a shopping cart with local storage support but it's not working properly. When i increment or decrement, i have an if-else statement to handle whether if the item is in the basket or not as shown below:
let basket = JSON.parse(localStorage.getItem("data")) || [];
.
.
.
let increment = (id) => {
let selectedItem = id;
const itemId = selectedItem[1].id;
let currentItem = shopItemsData.find(item => item.id === itemId);
//checking if the object already exists
if(basket.includes(currentItem)) {
currentItem['itemCount'] += 1;
selectedItem[1].innerText = currentItem['itemCount'];
} else {
basket.push(currentItem);
currentItem['itemCount'] = 1;
selectedItem[1].innerText = currentItem['itemCount'];
}
updateCart();
localStorage.setItem("data", JSON.stringify(basket));
}
At first it works normally and the items got saved in the local storage but every time i reload and click on the same item it duplicates the object and the count starts again from zero.
Visual Explanation of the problem
Short version
You should compare using a method like .some() or find() as referenced values can't directly be compared despite them having the same structure.
For example :
// ... Omitting the rest for brevity
const basketItem = basket.find((item) => item.id === id);
if(basketItem) {
basketItem['itemCount'] += 1;
currentItem['itemCount'] = basketItem['itemCount']
selectedItem[1].innerText = basketItem['itemCount'];
} else {
basket.push(currentItem);
currentItem['itemCount'] = 1;
selectedItem[1].innerText = currentItem['itemCount'];
}
// ... Omitting the rest for brevity
Slightly longer explanation
The issue is that you're looking for a different reference of an object. Javascript has 2 different ways of comparing and representing data: by reference or by value. This is common across other programming languages, but it's usually a lot more explicit than in JS. A very simple way to think about it is that primitives (string, booleans, numbers) are usually compared by value. More complex structures (Date, Array, Object) are compared by their reference, like a pointer in memory.
Here's a couple of examples :
const str1 = 'string';
const str2 = 'string';
console.log(str1 === str2); // true as they are both primitives and they represent the same value.
const obj1 = { obj: 1 };
const obj2 = { obj: 1 };
console.log(obj1 === obj2); // false as they are referenced values and point to different objects despite the value being the same.
// Now to compare with your issue.
const arr = [obj1];
console.log(arr.includes(obj1)); // true
console.log(arr.includes(obj2)); // false as obj2 isn't in the array despite the value of obj2 being the same as obj1.

Setting value of key in nested object happening incorrectly using a for loop and if statement

Here is an example that you can type in your console. Super new to Javascript. The example is reproducible by opening a new tab and typing it out in a console (The JSX Fiddle's console feature is in beta, so I'm not sure if it can be trusted)
let clothing = ['card0', 'card1', 'card2', 'card3'];
let timers = {}
let timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false} //Nested object I want for all indices, will manipulate 0th index alone inside for loop
for (var i = 0; i < clothing.length; i++) {
timers[i] = timerObj
if (i == 0) {
timers[i]["startTime"] = Date.now();
}
}
console.log(timers)
What I'm intending to do is, for the 0th index alone, set the timers[0]["startTime"] as Date.now(), and for the rest, let the startTime be null as defined in the timerObj.
Strangely, after running the for loop, I see that for all i, the Date.now() has been set. I understand that Javascript objects are mutable, but why is why are all indices being set to Date.now()?
I looked at other Javascript related Object questions related to a concept call "freezing", not sure I have my basics right.
EDIT: I think this is related the object reference being altered..
var clothing = ['card0', 'card1', 'card2', 'card3'];
var timers = {}
var timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false} //Nested object I want for all indices, will manipulate 0th index alone inside for loop
for (var i = 0; i < clothing.length; i++) {
timers[i] = Object.assign({}, timerObj)
if (i == 0) {
timers[i]["startTime"] = Date.now();
}
}
console.log(timers)
You can refer this for more information on this topic.
You have to clone your object. There are multiple ways to clone. One would be spread operator(...). Like below:
let clothing = ['card0', 'card1', 'card2', 'card3'];
let timers = {}
let timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false}
clothing.forEach((val, i)=>{
timers[i] = {...timerObj};
if(i==0){
timers[i].startTime = Date.now()
}
});
console.log(timers);
Javascript does not copy objects. It passes references around, so when you assign timers[i] = timerObj once, then you assign Date.now() once , this value goes to your single timeorObj. All subsequent assignments of timerObj to timers[i] for all i refer to the single timerObj you defined.
To fix this force a copy: timers[i] = JSON.parse(JSON.stringify(timerObj));
This will serialize your clean timerObj to a JSON string, then convert it back to a new javascript object and then assign the new object to timers[i].
This way, you end up with copies of timerObj in each slot of your timers array.

Is there any reason to keep using var?

WHY NOT TO BAN 'VAR'?
My question is not about the difference between 'var' and 'let'. All such answers are advocating the advantages of using 'let'.
My question is: why not to tell frankly "do not use 'var' anymore"?
Is there a reason for not being so direct?
'var' is still in use on many serious tutorial sites: Mozilla MDN, w3schools ...
I am wondering if there is an hidden reason that I am missing.
There is one answer below: legacy (old browsers not supporting ES6)
Is that the only reason?
Any performance reason?
Or some fancy use of hoisting?
[ Here was the rest of my original post...
var arr = []; // -> const arr = [];
var obj = {}; // -> const obj = {};
var temp; // -> let temp;
var f = function(){}; // -> const f = function(){};
Doing so, I think that the only way a variable may behave like a var variable (hoisting etc.), is an -unfortunately- undeclared variable: x = sthg; in some function (becoming: var x = sthg; at global scope).
If I missed something, it would highly help me. ]
The only time I've even considered using var in the last year is to take advantage of it being hoisted outside of a code block, particularly with conditionals. Something like this contrived example:
const oneOrZero = shouldBeOne => {
if (shouldBeOne) {
var test = 1
} else {
var test = 0
}
return test
}
You can replace the var in this case with let, but that always struck me as kind of messy:
const oneOrZero = shouldBeOne => {
let test
if (shouldBeOne) {
test = 1
} else {
test = 0
}
return test
}
In the end, what I've done is take advantage of the ternary operator and const. This has the added advantage of not being reassignable after checking the initial conditional, which is typically my intent:
const oneOrZero = shouldBeOne => {
const test = shouldBeOne
? 1
: 0
return test
}
TL;DR: I haven't used var in over a year. const is much preferable, and typically forces me to write better code. let is occasionally useful.

Add a key to an object with keys of an existing array with objects

I've got an array of objects array = [object1, object2, ...], each of them has some keys object1 = { key1: 'value1', ... }. I want to add a key this way:
$rootScope.array[i].newKey = 'someValue'
But angular tells me that $rootScope.array[i] is undefined.
What I've noticed from console is that the objects get the new key but the console still says the same.
You should use less than and not less or equal than comparator.
$scope.init = function () {
for (i = 0; i < /* not <= */ $rootScope.meatTypes.length; i++) {
console.log("I am showing the meatypes:");
console.log($rootScope.meatTypes);
$rootScope.meatTypes[i].counter = '0';
counterOperations.setCounters(i, 0);
}
$rootScope.total = 0;
counterOperations.setTopCounter(0);
};
because when i equals $rootScope.meatTypes.length then $rootScope.meatTypes[i] is undefined.
You are trying to access a member of the array that does not exist.
You need to create a new object and push it onto the array:
$rootScope.array.push({'key1': 'someValue'});
You did not mention lodash, but when I see someone encounter an issue like this, I want to offer the recommendation of using lodash (or underscore.js).
With lodash, you would do something like so, using _.set, which defensively protects against your described issue by automatically adding the necessary elements in the path:
_.set($rootScope, ['array', i, 'newKey'], 'someValue');
This library, properly utilized, solves many issues that you can have with setting and getting variables, ase well as other super useful tools. It has been a major life-saver (and time-saver) for us on our projects.
Like this you can add
$rootScope.array[i] = {}; // first we should create an object on that array location
$rootScope.array[i]['newKey'] = 'someValue'; // then only we can add values
EDIT:
$scope.init = function () {
for (i = 0; i <= $rootScope.meatTypes.length; i++) {
console.log("I am showing the meatypes:");
console.log($rootScope.meatTypes);
**// This is needed**
$rootScope.meatTypes[i]={};// here we should tell that metaType[newItem] is an object other wise it treat it as undefined
$rootScope.meatTypes[i].counter = '0';
counterOperations.setCounters(i, 0);
}
$rootScope.total = 0;
counterOperations.setTopCounter(0);
};

Cannot set property '0' of undefined in 2d array

I know this has been asked a lot of times, but how do I fix exactly this thing?
I have a map[][] array (contains tile ids for a game) and I need to copy it to pathmap[][] array (contains just 0's and 1's, it is a path map), however when I do so..
function updatepathmap(){
pathmap = [];
var upm_x = 0;
while (upm_x < map.length){
var upm_y = 0;
while (upm_y < map[upm_x].length){
pathmap[][]
if (canPassthrough(map[upm_x][upm_y])) {
pathmap[upm_x][upm_y] = 1;
} else {
console.log(upm_x);
console.log(upm_y);
pathmap[upm_x][upm_y] = 0;
}
upm_y++;
}
upm_x++;
}
console.log(map);
console.log(pathmap);
}
..it gives me Cannot set property '0' of undefined typeerror at line pathmap[upm_x][upm_y] = 0;
Despite the foo[0][0] syntactic sugar, multi-dimensional arrays do not really exist. You merely have arrays inside other arrays. One consequence is that you cannot build the array in the same expression:
> var foo = [];
undefined
> foo[0][0] = true;
TypeError: Cannot set property '0' of undefined
You need to create parent array first:
> var foo = [];
undefined
> foo[0] = [];
[]
> foo[0][0] = true;
true
You can determine whether it exists with the usual techniques, e.g.:
> var foo = [];
undefined
> typeof foo[0]==="undefined"
true
> foo[0] = true;
true
> typeof foo[0]==="undefined"
false
I would have thought pathmap[][] was a syntax error, I'm surprised you're not seeing one.
Before you can use an array at pathmap[upm_x], you must create an array at pathmap[upm_x]:
pathmap[upm_x] = [];
This would be the first line in your outer while, so:
while (upm_x < map.length){
pathmap[upm_x] = [];
// ...
Remember that JavaScript doesn't have 2D arrays. It has arrays of arrays. pathmap = [] creates the outer array, but doesn't do anything to create arrays inside it.
Side note:
var upm_x = 0;
while (upm_x < map.length){
// ...
upm_x++;
}
is an error-prone way to write:
for (var upm_x = 0; upm_x < map.length; upm_x++){
// ...
}
If you use while, and you have any reason to use continue or you have multiple if branches, it's really easy to forget to update your looping variable. Since looping on a control variable is what for is for, it's best to use the right construct for the job.
Side note 2:
Your code is falling prey to The Horror of Implicit Globals because you don't declare pathmap. Maybe you're doing that on purpose, but I wouldn't recommend it. Declare your variable, and if you need it outside your function, have your function return it.
Side note 3:
map would make this code a lot simpler:
function updatepathmap(){
var pathmap = map.map(function(outerEntry) {
return outerEntry.map(function(innerEntry) {
return canPassthrough(innerEntry) ? 1 : 0;
});
});
console.log(map);
console.log(pathmap);
}

Categories

Resources