A way to flat an array recursively on MDN - javascript

MDN provides a way to flat an array recursively:
function flatDeep(arr, d = 1) {
return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
: arr.slice();
}
Why we need arr.slice() to get a copy? I think just arr is OK, is there any problem with just using arr?

It's intended to exactly emulate the behavior of Array.prototype.flat, which returns a new array even if there's nothing to be flattened:
const arr = [0, 1, 2];
console.log(arr === arr.flat());
The .slice isn't necessary to produce a flattened array, it's just there to create a new array reference just in case there's no recursion to do.
The requirement of the creation of a new array is described in the specification here, and in FlattenIntoArray. Even if there aren't any elements to flatten, a new array is created by assigning all properties and values of the old array to the new array. ("Repeat, while sourceIndex < sourceLen: (assign property origArr[sourceIndex] to new array)")

Technically there is no problem if you don't modify the returned value.
However the behaviour is confusing if the returned value is modified, either by an unknowing user or by accident.
A function that sometimes returns a clone and sometimes returns a reference makes it harder to debug,
and is most likely considered an antipattern.
Since there is no standard for indicating whether a returned array is mutable in JavaScript,
MDN most likely considered it is better to be safe and consistent

Related

Javascript Arrays - How do they work internally?

I wanted to know how javascript arrays work internally, I mean when for example you create a new array.
array = [1,2,3];
It's internally creating a new array: array = new Array();
and then calling Array.push() or similar?
Thanks.
The best resource to find out how javascript internals work is ECMAScript specification itself.
In order to understand what happens internally when you do array = [1, 2, 3] you would need to read section 7.3.16 CreateArrayFromList (elements). Roughly what happens is that first Array object gets created, then each element gets set to this object with CreateDataProperty (7.3.4 CreateDataProperty section) (DefineOwnProperty) internal method.
Then you want to learn what exactly happens when you push element to array. You check 22.1.3.17 Array.prototype.push ( ...items ) section for this. There you will find out that it uses quite different algorithm, namely it sets specific property of an object (7.3.3 Set (O, P, V, Throw) section).
So the answer is no, creating array like a = [1, 2, 3] does not uses same mechanics to insert items as push does. The first one roughly creates new property on (newly created) array object, the push sets property to existing object.
and then calling Array.push() or similar?
No. As you can see in the below example that when array is initialized push (overridden) method is not invoked.
Array.prototype.push = function(){ console.log("adding") };
var array = [1,2,3];
//console.log(array);
But, it is invoked when console statement is executed (after un-commenting the same).
var array = [1,2,3];
It doesn't call push but it's a simpler way to initialize an array when you know the elements in advance.
If you want to add another element you can do it in multiple ways like:
1) array.push(22); // adds integer 22 to the array
or
2) array[4] = 22; // adds integer 22 to the array at index 4
You can even do this:
array[20] = 22; // this will resize the array and keep all the uninitialized elements returns undefined. So array[10] is undefined, for ex.
If you want to know all the details about arrays explained in a simple way I recommend you the book: Secrets of the JavaScript Ninja. It has an entire chapter about arrays.
https://www.amazon.com/Secrets-JavaScript-Ninja-John-Resig/dp/1617292850/ref=sr_1_1?ie=UTF8&qid=1519484868&sr=8-1&keywords=javascript+ninja

Arr.includes(item) - Can I Use with a Multidimensional Array?

I'm trying to use the arr.includes(item). The function should return True if the item is an element of the array. But it doesn't seem to be able to do so with a multidimensional array. Take a look at this screenshot (running node in the console):
I got a similar result on my Google Chrome.
Is it because it's an EC6 function, and not yet completely functional?
No information on such a problem on the Mozille page.
No, you can't use it on deep structures, because it performs an === test that checks that the operands are the same object, and not two (different) objects that happen to have the same contents.
On the MDN page you linked to there's a polyfill where you can see that === test within the sameValueZero() nested function.
For the above reasons, this would actually return true:
let a = [0, 1];
let b = [1, 2];
let c = [a, b];
c.includes(b);
> true
because the object passed to .includes really is the same object that's contained in c.

Javascript array type casting

I've found something interesting and I don't know why it's happening.
If I try in google chrome developer tools, the following two staments
(Array([1,2,3])).filter(function (item, index, array) {
return item === 1;
}); ==> []
and
([1,2,3]).filter(function (item, index, array) {
return item === 1;
}); ==> [1]
The results are an empty array for the first statement and array with a single value (1) for the second
Inspecting the parameters for the callback function, i found that in the first statement the arguments are (array, index, value) and for the second statemente are(value, index, array).
Inspecting with typeof and constructor of both objects the result are the expected, and the same "object", and Array.
Why is this happening?
Thanks
Because that's not how you define an array with Array().
Should be without the square brackets, otherwise it's an array of a single element, which is also an array ([1,2,3]).
Array(1,2,3)
That inner array never equals to 1 (basically you check [1,2,3] == 1), so the result is an empty array.
If you define an array by using Array([1,2,3]) this code, then the following array will be created,
[[1,2,3]]
Since you are pushing an array into another one. And if you really want the Array function to create an array by reading an array then you have to write something like this,
Array.apply([], [1,2,3])
But the above one is completely pointless. Again I am telling it is completely pointless since we are having an array that we require in our hand. But just for a knowledge you can know about it.
Array([1,2,3]) create array of arrays [[1, 2, 3]] so .map()function will iterate one time only.
If you want to create array with Array constructor use next syntax:
Array(1,2,3)
the shorter is the better :
[1,2,3].filter(item => item === 1);

Array reference versus value semantics

Any programming language which provides arrays (lists, vectors, tuples etc.) must decide whether they have reference or value semantics, with the usual/obvious choice being reference semantics for mutable arrays and value semantics for immutable ones.
JavaScript which provides mutable arrays appears to have chosen reference semantics e.g. given
var a = [1, 2, 3]
var b = [1, 2, 3]
then a != b, as expected because though they have the same contents, they are different arrays.
However when you use them as keys in an object, the picture changes; if you set obj[a] to a value, then obj[b] gets the same value. Furthermore, this remains true if you change the contents of the arrays; at least when I tested it in Rhino, it behaves as though the interpreter were recursively comparing the full contents of the supplied and stored key arrays on every lookup, complete with a check for the infinite loop that would occur if one of the arrays were made to point to itself.
Is this the intended/specified behavior in all implementations?
Does it also apply to objects used as keys?
Is there any way to get the other behavior, i.e. to look up values using arrays as keys with reference semantics?
When arrays are used as property names they are cast to a string:
[1,2,3].toString() == '1,2,3'
Once turned into a string value, arrays with the same contents would map to the same property.
To answer your last question, you can't use objects to reference property names (keys) whereby only the same object maps to the same property (1:1 mapping).
obj[a] and obj[b] will run the toString function on the arrays and produce the same result for both. It doesn't try to use the arrays as keys.
var a = [1,2,3];
var x = {};
x[a] = "test";
var i;
for(i in x)
{
alert(i); //"1,2,3"
}
jsFiddle example

Javascript passing arrays to functions by value, leaving original array unaltered

I've read many answers here relating to 'by value' and 'by reference' passing for sending arrays to javascript functions. I am however having a problem sending an array to a function and leaving the original array unaltered. This example llustrates the problem:
function myFunction(someArray)
{
// any function that makes an array based on a passed array;
// someArray has two dimensions;
// I've tried copying the passed array to a new array like this (I've also used 'someArray' directly in the code);
funcArray = new Array();
funcArray = someArray;
var i = 0;
for(i=0; i<funcArray.length; i++)
{
funcArray[i].reverse;
}
return funcArray;
}
I can't understand why anything in this function should alter the original array.
calling this function directly changes the original array if the function call is assigned to a new array:
myArray = [["A","B","C"],["D","E","F"],["G","H","I"]];
anotherArray = new Array();
anotherArray = myFunction(myArray);
// myArray gets modified!;
I tried using .valueOf() to send the primitive:
anotherArray = myFunction(myArray.valueOf());
// myArray gets modified!;
I have even tried breaking the array down element by element and sub-element by sub-element and assigning all to a new 2-d array and the original array still gets modified.
I have also joined the sub-elements to a string, processed them, split them back into arrays and the original array still gets modified.
Please, does any one know how I can pass the array values to a function and not have the passed array change?
Inside your function there's this:
funcArray = new Array();
funcArray = someArray;
This won't actually copy someArray but instead reference it, which is why the original array is modified.
You can use Array.slice() to create a so-called shallow copy of the array.
var funcArray = someArray.slice(0);
Modern versions of ES also support destructuring expressions, which make it look like this:
const funcArray = [...someArray];
The original array will be unaltered, but each of its elements would still reference their corresponding entries in the original array. For "deep cloning" you need to do this recursively; the most efficient way is discussed in the following question:
What is the most efficient way to deep clone an object in JavaScript?
Btw, I've added var before funcArray. Doing so makes it local to the function instead of being a global variable.
Make a copy of the array that you can use.
A simple way to do this is by using var clone = original.slice(0);
With ES6, you can use the rest element syntax (...) within a destructuring expression to perform a shallow copy directly in the parameter list, allowing you to keep the original array unaltered.
See example below:
const arr = [1, 2, 3, 4, 5];
function timesTen([...arr]) { // [...arr] shallow copies the array
for(let i = 0; i < arr.length; i++) {
arr[i] *= 10; // this would usually change the `arr` reference (but in our case doesn't)
}
return arr;
}
console.log(timesTen(arr));
console.log(arr); // unaltered
The above is useful if you have an array of primitives (strings, booleans, numbers, null, undefined, big ints, or symbols) because it does a shallow copy. If you have an array of objects, or an array of arrays (which are also technically just objects), you will want to perform a deep clone to avoid modifying the array and its references. The way to do that in modern-day JS is to use a structured clone, which helps deal with many of the shortcomings of deep cloning with the JSON.stringify() technique as previously used:
function myFunction(someArray) {
const funcArray = structuredClone(someArray);
...
}
What about destructuring assignment (ES6+, check compatibility)? Nice and clean solution.
function myFunction(someArray) {
for(let i = 0; i < someArray.length; i++)
{
someArray[i].reverse();
}
return someArray;
}
let myArray = [["A","B","C"],["D","E","F"],["G","H","I"]];
// Using destructuring assignment.
// NOTE: We can't just use `[...myArray]` because nested arrays will still be copied by reference.
let anotherArray = myFunction([...myArray.map(nested => [...nested])]);
console.log({original: myArray, copy: anotherArray});
A variable pointing to an array is a reference to it. When you pass an array, you're copying this reference.
You can make a shallow copy with slice(). If you want a full depth copy, then recurse in sub objects, keeping in mind the caveats when copying some objects.
If you need to do this with an object, try this fancy trick...
MY_NEW_OBJECT = JSON.parse(JSON.stringify(MY_OBJECT));
A generic solution would be...
// Use the JSON parse to clone the data.
function cloneData(data) {
// Convert the data into a string first
var jsonString = JSON.stringify(data);
// Parse the string to create a new instance of the data
return JSON.parse(jsonString);
}
// An array with data
var original = [1, 2, 3, 4];
function mutate(data) {
// This function changes a value in the array
data[2] = 4;
}
// Mutate clone
mutate(cloneData(original));
// Mutate original
mutate(original);
This works for objects as well as arrays.
Very effective when you need deep cloning or you don't know what the type is.
Deep cloning example...
var arrayWithObjects = [ { id: 1 }, { id: 2 }, { id: 3 } ];
function mutate(data) {
// In this case a property of an object is changed!
data[1].id = 4;
}
// Mutates a (DEEP) cloned version of the array
mutate(cloneData(arrayWithObjects));
console.log(arrayWithObjects[1].id) // ==> 2
Warnings
Using the JSON parser to clone is not the most performant option!
It doesn't clone functions only JSON supported data types
Cannot clone circular references
by default in javascript except objects and arrays, everything is copy-by-value
but if you want to use copy-by-value for arrays: use [yourArray].slice(0)
and for objects use Object.assign(target, ...sources)
var aArray = [0.0, 1.0, 2.0];
var aArrayCopy = aArray.concat();
aArrayCopy[0] = "A changed value.";
console.log("aArray: "+aArray[0]+", "+aArray[1]+", "+aArray[2]);
console.log("aArrayCopy: "+aArrayCopy[0]+", "+aArrayCopy[1]+", "+aArrayCopy[2]);
This answer has been edited. Initially I put forth that the new operator handled the solution, but soon afterward recognized that error. Instead, I opted to use the concat() method to create a copy. The original answer did not show the entire array, so the error was inadvertently concealed. The new output shown below will prove that this answer works as expected.
aArray: 0, 1, 2
aArrayCopy: A changed value., 1, 2

Categories

Resources