I encountered some weird behavior in the assert module of node.js. I'm not sure if this is a bug or intended behavior.
When I create an array, and then initialize a value for example on the 3nd element, the first two elements are undefined. Testing whether the first two elements are undefined return true, however comparing the array as a whole with undefined for the first two elements fails.
var assert = require('assert');
var a = [];
a[2] = 2;
console.log(a); // [ , , 2 ]
assert.equal(a[0], undefined); // ok
assert.equal(a[1], undefined); // ok
assert.equal(a[2], 2); // ok
assert.deepEqual(a, [, , 2]); // ok
assert.deepEqual(a, [undefined, undefined, 2]); // error ???
I can understand there is a difference between undefined elements and elements having a value undefined, as Array extends Object, and array elements are just properties on an object.
But why does the last assertion fail?
Most likely because [,,2] and [undefined,undefined,2] don't have the same number of keys:
> Object.keys([,,2]).length
1
> Object.keys([undefined,undefined,2]).length
3
Here is the relevant part of the assert code that is checking this.
I'm going to step out on a limb here...
Most likely, deepEqual uses the hasOwnProperty method of object to make sure it doesn't catch any inherited properties.
If this is the case, the following console log may help you:
var test1 = [];
undefined
test1[2] = "xyzzy";
"xyzzy"
test1.hasOwnProperty(0)
false
test1.hasOwnProperty(1)
false
test1.hasOwnProperty(2)
true
var test2 = [undefined, undefined, "plugh"];
undefined
test2.hasOwnProperty(0)
true
test2.hasOwnProperty(1)
true
test2.hasOwnProperty(2)
true
I'm going to hazard a guess. It appears that there is a distinction between array locations that contain nothing (i.e., uninitialized) and are undefined, versus actually sticking an actual undefined into a specific location in an array.
You can actually see this in Chrome on the console log:
> a = [undefined, undefined, 2];
[undefined, undefined, 2]
> b = [,,2]
[undefined × 2, 2]
> c = [];
> c[2] = 2;
> c
[undefined × 2, 2]
It's a very subtle difference; in the first case ([,,2]) you created an array without anything in locations 0 and 1; it's a spare array where only location 3 contains a value. In the second case, you created an array which isn't a spare array. Instead you specifically inserted the value undefined at locations 1 and 2. It appears that both these cases are treated differently which is probably why they are not equal.
I think it might be easier to visualize the first array as [,,2] = [uninitialized, uninitialized, 2]. So the distinction is between an uninitialized value versus a concrete undefined that has been inserted into the array (which means the location is not uninitialized).
UPDATE
I took a look at the code in assert.js. Specifically, pay attention to lines 221 and 222:
if (ka.length != kb.length)
return false;
ka and kb are set in lines 213-215:
var ka = Object.keys(a),
kb = Object.keys(b),
key, i;
The value of Object.keys(a).length for [,,2] is 1 whereas the value of Object.keys(b).length for [undefined, undefined, 2] is 3. This is why they are not equal.
Not a bug. Those arrays have different behaviour. Consider the following use case:
function log(item, idx) {
console.log(item, " - ", idx);
}
a.map(log); // 2 - 2
[undefined, undefined, 2].map(log); //undefined - 0 undefined - 0 2 - 2
Related
I am learning ES6 Sets. I am trying to add an array and check its existence in the set but it is returning me false. Here is the code I am working on.
const set = new Set();
set.add(1);
set.add([2, 3]);
set.add([2, 3]);
console.log(set);
console.log(set.has([2, 3]));
Please can someone explain this behaviour.
When a Set contains an object, .has() determines whether or not the Set contains the same exact object, not whether the Set contains an object that contains the same data as some other object. It has to contain the same object.
So, .has(someObj) or .has(someArray) only returns true if that actual object is in the Set. It does not compare to see if a similar object with the same values/properties are in the Set, only if the actual object itself is in the Set.
It works similar to comparing two objects with === as in obj1 === obj2.
If you wanted something with features similar to a Set that would tell you if it had any other object matching the current one, then you'd have to write a different collection yourself and implement your own comparison algorithm. This is not how a Set or a Map work.
You can see some other discussion of a similar topic here: How to customize object equality for JavaScript Set
This should work:
const set = new Set();
const array = [ 2, 3 ];
set.add(1);
set.add(array);
set.add(array);
console.log(set);
console.log(set.has(array));
Set#has checks for object identity, not deep equality. This is different than, say, Java's HashMap, which uses Object#equals in the background.
just because primitive value is immutable
and object is reference (such as array, function etc) and reference object is mutable
see example
let x = 1; // orginal
let y = x; // copy
y = y + x; // modifing
console.log(x); // return 1
console.log(y); // return 2
Note: example above is not impacting in original value
// reference object
let x = [1]; // orginal
let y = x; // copy
y.push(2);
console.log(x); // return [1, 2]
console.log(y); // return [1, 2]
note: example above is impact in all
// comparison
let a = [1];
let b = [2];
console.log(a === b); // return false
console.log(a !== b);
Note: example above to know reason go to this link - https://dmitripavlutin.com/value-vs-reference-javascript/#:~:text=The%20simple%20rule%20of%20passing,Every%20single%20time.
following the snippet bellow, why an array with one position is not working properly when using IN operation?
You can see that with 2 item the operation works fine.
Why this is happening?
What's the work around?
// helper function
function showResult(result) {
document.querySelector('#result').innerHTML += result+'<br/>';
}
var a = ["1"]
var b = "1"
showResult(b in a);
//false
var a = ["1","2"]
var b = "1"
showResult(b in a);
//true
<div id="result"></div>
That's not what in is for. in is not for searching an array for an element. It's for searching an object for a key.
Example:
var x = {a: 1, b: 2};
console.log('a' in x); // true
Your 2nd example works because the array (object) a contains a key called 1.
The in operator checks if an object has a key, not a value. Your second case returns true because any array with two elements has keys '0' and '1'. It doesn't matter that '1' also happens to be a value in that array; '1' in ['foo', 'bar'] is also true.
To test if an element is in an array, you should use Array.prototype.indexOf or in modern engines Array.prototype.includes. You can use the polyfill from either of those links to get support for that method in all engines:
// Most engines (IE 9+)
showResult( a.indexOf( b ) >= 0 );
// Modern engines only:
showResult( a.includes( b ) );
The first result is false because a does not have an index 1, while the second version of a has that index.
The in operator is not about affirming values, but properties (keys).
The confusion is maybe because CoffeeScript does use in for searching values. See here how it translates that into an indexOf
What is different between two variable one is assigned a value undefined and second one is only declared a var not initiased ?
var a = undefined;
var b;
a === b; // returns true
What is different between two variable a and b?
var ar = new Array(4); // ar = 0[undefined × 4]
ar[0] = null; // ar = [null, undefined × 3]
ar[1] = undefined; // ar = [null, undefined, undefined × 2]
ar.filter(function(item, index, arr){
return item == undefined;
}); // [null, undefined]
I know Array's filter function runs for only initialized index.
How javascript internally check that ar[1] is assigend undefined so run filter for this and ar[2] is unassigned undefined so dont run for this ?
There are three separate issues going on here:
A regular variable that has just been declared, but not assigned a value will report undefined when you read that variable. That is essentially its default value.
You have to be very careful when comparing null with == because you will find that null == undefined is true because of auto-type conversion. So, if you want to filter on only undefined elements, you have to use item === undefined in the filter.
.filter() only iterates elements in the array that have actually been assigned a value. Unlike plain variables, there is a difference between an array element that has assigned a value and one that has never been assigned a value and .filter() knows to skip the ones that have never been assigned a value (the ones that are essentially "sparse").
Here's some more detail on these three issues:
A variable that has been declared, but not explicitly initialized, has a value of undefined. That is its default value.
This, this code is as expected:
var a = undefined;
var b;
a === b; // returns true
Then, in your second code block, if you want to truly test for whether something is undefined, then you need to use ===, not == to avoid any type conversion.
var ar = new Array(4); // ar = 0[undefined × 4]
ar[0] = null; // ar = [null, undefined × 3]
ar[1] = undefined; // ar = [null, undefined, undefined × 2]
var result = ar.filter(function(item, index, arr){
return item === undefined;
});
for (var i = 0; i < result.length; i++) {
document.write(i, ": ", result[i], "<br>");
}
Note: .filter() will skip elements of the array that have not been initialized to have any value, thus they will never even be considered in the .filter() operation. If you try to read their values, you will get undefined, but .filter() knows the difference between a "sparse" array value that has never been assigned anything. It has to do with whether the array key exists or not, not what the value is and thus it is different for array items than it is for plain variables.
From the MDN doc for .filter():
The range of elements processed by filter() is set before the first
invocation of callback. Elements which are appended to the array after
the call to filter() begins will not be visited by callback. If
existing elements of the array are changed, or deleted, their value as
passed to callback will be the value at the time filter() visits them;
elements that are deleted are not visited.
Here's a demonstration of how the array keys do not actually exist until someting is assigned to the array element. This is the information that .filter() uses to know what to iterate and what to skip:
var ar = new Array(4);
ar[1] = "foo";
ar[2] = undefined;
for (var index in ar) {
document.write(index, ": ", ar[index], "<br>"); // 1 foo, 2 undefined
}
Note that indexes 0 and 3 are not iterated with the for loop because those keys do not exist in the array. The array is "sparse". Only some elements actually exist.
var a = [ [1,2,3], [4,5,6], [7,8,9] ];
_.map( a, _.max );
// gives [3, -Infinity, -Infinity]
I have tested it on my Chrome browser, on the lodash site.
Shouldn't the code above return [3, 6, 9]?
I can get the correct result with forEach:
var a = [ [1,2,3], [4,5,6], [7,8,9] ];
var result = [];
_.forEach( a, function(arr) { result.push(_.max(arr)); } );
Overall conclusion
I've opened an issue on GitHub: https://github.com/lodash/lodash/issues/379
Thanks to jdalton, the issue is now fixed. Sample: → jsFiddle
Details (why is the result 'weird'?)
I have never worked with Lodash in detail, but here are my researches:
Retrieves the maximum value of a collection. If the collection is empty or falsey -Infinity is returned.
— Lodash Documentation: _.max()
Testing _.max() with each group invidually worked perfectly:
_.max([1,2,3]); // 3
_.max([4,5,6]); // 6
_.max([7,8,9]); // 9
Now, I tried calling _.max() manually in the callback function of _.map():
var a = [ [1,2,3], [4,5,6], [7,8,9] ];
alert(_.map( a, function(val) {
return _.max(val);
}));
Works fine! So where is the difference between that and supplying _.max() as the second parameter?
_.map() actually sends 3 parameters to the callback function:
(value, index|key, collection).
— Lodash Documentation: _.map()
Consider the second parameter of _.max() now:
2.[callback=identity] (Function|Object|string): The function called per iteration. If a property name or object is provided it will be
used to create a ".pluck" or ".where" style callback, respectively.
— Lodash Documentation: _.max()
Conclusion: _.max() gets also passed the second and third parameter supplied by _.map(). The second parameter is important here! Passing truthy values (e.g. integers != 0) for it lets the function return -Infinity.
Test cases (→ jsFiddle):
alert(_.max([1,2,3], 0)); // 3
alert(_.max([1,2,3], 1)); // -Infinity
alert(_.max([1,2,3], 2)); // -Infinity
That is coming from the boolean checks performed in the source code:
https://github.com/lodash/lodash/blob/f0f7eee963966516490eb11232c9e9b4c6d0cc6c/dist/lodash.js#L3431
Because callback (the second parameter) is a truthy value, we will directly jump into the else branch. There, callback is reassigned as the following (the ternary operation will also take the else branch):
lodash.createCallback(callback, thisArg, 3);
createCallback() is defined here.
It returns the following function for our specific input parameters (these are 1, null, 3, see _.max() for details):
return function(object) {
return object[func];
};
Let's say, we save that in a variable called callback (→ jsFiddle):
var callback = _.createCallback(1, null, 3);
Calling that function with object = 1 (or 2, 3 or 6, etc.) results in undefined (that's pretty obvious).
Going back to _.max(), we see that there is a loop which compares the current value (from the callback function) to the initial/last value, and that is -Infinity as set at the beginning of the function.
undefined > -Infinity never results in true, so -Infinity will stay the 'maximum' value.
I'm using Mocha to test a small module in my Express.js application. In this module, one of my functions returns an array. I want to test whether or not the array is correct for a given input. I am doing so like this:
suite('getWords', function(){
test("getWords should return list of numbers", function() {
var result = ['555', '867', '5309'];
assert.equal(result, getWords('555-867-5309'));
});
});
When this runs, I get the following assertion error:
AssertionError: ["555","867","5309"] == ["555","867","5309"]
However, when I change my test to an assert.deepEqual, the test passes fine. I was wondering if it was a case of == vs ===, but if I enter
[1,2,3] === [1,2,3]
into the node.js command line, I still get false.
Why do arrays not compare the way other values do (e.g. 1 == 1)? and what is the difference between assert.equal and assert.deepEqual?
Why do arrays not compare the way other values do (e.g. 1==1)
Numbers, strings, booleans, null, and undefined are values, and are compared as you might expect. 1 == 1, 'a' == 'a', and so on. The difference between === and == in the case of values is that == will attempt to perform type conversion first, which is why '1' == 1 but not '1' === 1.
Arrays, on the other hand, are objects. === and == in this case do not signify that the operands are semantically equal, but that they refer to the same object.
what is the difference between assert.equal and assert.deepEqual?
assert.equal behaves as explained above. It actually fails if the arguments are !=, as you can see in the source. Thus it fails for your arrays of numbers strings because although they are essentially equivalent, they are not the same object.
Deep (aka structural) equality, on the other hand, does not test whether the operands are the same object, but rather that they're equivalent. In a sense, you could say it forces objects to be compared as though they're values.
var a = [1,2,3]
var b = a // As a and b both refer to the same object
a == b // this is true
a === b // and this is also true
a = [1,2,3] // here a and b have equivalent contents, but do not
b = [1,2,3] // refer to the same Array object.
a == b // Thus this is false.
assert.deepEqual(a, b) // However this passes, as while a and b are not the
// same object, they are still arrays containing 1, 2, 3
assert.deepEqual(1, 1) // Also passes when given equal values
var X = function() {}
a = new X
b = new X
a == b // false, not the same object
assert.deepEqual(a, b) // pass, both are unadorned X objects
b.foo = 'bar'
assert.deepEqual(a, b) // fail!