In the following example (https://jsfiddle.net/dhkzwxr1/2/) :
I try to understand the behaviour of splice
const arr = [];
arr[0] = {name:"Jani",age:24};
arr[1] = {name:"Hege",age:32};
arr[2] = {name:"Stale",age:52};
arr[3] = {name:"Kai Jim",age:42};
arr[4] = {name:"Borge",age:25};
arr.splice(1, 0, {name:"Lene",age:27});
console.log(arr);
Insert at
Behave as expected
const arr2 = [];
arr2[0] = {name:"Jani",age:24};
arr2[1] = {name:"Hege",age:32};
arr2[2] = {name:"Stale",age:52};
arr2[3] = {name:"Kai Jim",age:42};
arr2[4] = {name:"Borge",age:25};
const jani = arr2[0];
jani.age = 19
arr2.splice(1, 0, jani);
console.log(arr2);
Output :
Array(6)
0: {name: "Jani", age: 19}
1: {name: "Jani", age: 19}
2: {name: "Hege", age: 32}
3: {name: "Stale", age: 52}
4: {name: "Kai Jim", age: 42}
5: {name: "Borge", age: 25}
length: 6
Why arr2[0].age === 19 and not 24 ?
This is not an issue with slice. What happens is that objects in JavaScript, like all other non-primitive values, are passed by reference.
So even if you insert an object into array, and update it after that, the variable will have the updated value. I think the most important thing to understand is that jani variable in your case does not have an actual value of the object, but just a reference to it.
like said commentator above Because you're inserting the same element into the array. The object referenced by jani is in the array twice once you splice it in.
and you should create a new object, you can do it by method Object.assign()
easy fix
var jani = Object.assign({}, arr2[0]);
working example
https://jsfiddle.net/fdtgvcLw/
When you assign one variable to another, it's not that both those variables are now linked by reference; you're misunderstanding what "pass by reference" means here.
A variable holding an object does not "directly" hold an object. What it holds is a reference to an object. When you assign that reference from one variable to another, you're making a copy of that reference. Now both variables hold a reference to an object. Modifying the object through that reference changes it for both variables holding a reference to that object.
When you assign a new value to one of the variables, you're just modifying the value that variable holds. The variable now ceases to hold a reference to the object, and instead holds something else. The other variable still holds its reference to the original object, the assignment didn't influence it at all.
Let's visualize:
var objOne = {
x: 1,
y: 2
};
// objOne -> { x: 1, y: 2 }
var objTwo = objOne;
// objOne -> { x: 1, y: 2 } <- objTwo
objTwo.x = 2;
// objOne -> { x: 2, y: 2 } <- objTwo (update object via objTwo variable)
objTwo = {};
// objOne -> { x: 2, y: 2 }, objTwo -> {}
This is how pass-by-reference works. Here objTwo is referring to objOne so whatever you do with objTwo will also happen with objOne.
It is not about splice. its with you have assigning object, and changing it's value to 19.
Then you insert another record to the array.
If you comment the var jani = arr2[0]; and splice it like you have done it before.
It would work.
Here's a simple function I invented to test out JavaScript's argument object versus rest parameters.
function f(a, b, ...theArgs) {
console.log(theArgs);
console.log(arguments);
return arguments;
}
if I call this function, say with these parameters: f(1,2,6,5,8,9,10,'a')
I'll get two console logs and then the argument objects is returned. All great.
However, if I pass this function to:
Array.prototype.slice(f(1,2,6,5,8,9,10,'a'), f.length), I will get an empty array back instead of an array like [6,5,8,9,10,'a']-- it appears that in this case my function is returning an empty array instead of the arguments object.
Why is that? Here's a snapshot of my console:
Array.prototype is the empty array that your code is slicing on, not the result of f.
The first parameter to slice must be a number. In your code, f returns an object. If you want to change the this value of the slice function so that it slices your object instead, use .call:
Array.prototype.slice.call(f(1, 2, 6, 5, 8, 9, 10, 'a'), f.length)
function f(a, b, ...theArgs) {
//console.log(theArgs);
//console.log(arguments);
return arguments;
}
console.log(Array.prototype.slice.call(f(1, 2, 6, 5, 8, 9, 10, 'a'), f.length));
I'm trying to construct an array in JavaScript, but I'm not sure of the correct way to do it, or if this type of array is even possible...
Lets say I have a key for each item in the array starting with 'a' and ending with 'z'. For each item, the array key will correspond with another multidimensional array. These new multidimensional arrays are a series of coordinates (x and y). Each item in the original array can have many sets of coordinates. For example:
How can I construct such an array with Javascript? What is the proper syntax?
Just to add another possible option to your list, on the same lines as #SMcCrohan's answer, mixing objects and arrays.
var coords = {
a: [{
x: 20,
y: 15
},
{
x: 25,
y: 17
}],
b: [{
x: 10,
y: 30
}],
....
};
This assumes you will always use coordinates x and y, It means you can access the values like so:
var value1 = coords.a[1].x; // 25
var value2 = coords.b[0].y; // 30
For the data you've provided:
var arr = {
a: [[20,15],[25,17],[10,45]],
b: [[10,33],[12,2],[14,9],[72,103],[88,12]],
c: [[2,2],[41,21]],
d: [[0,0],[21,2],[44,44],[19,99],[1,1],[100,100]],
e: [[1,1],
f: [[3,40],[41,86]]
}
The first structure you want, a keyed array, isn't an array in JavaScript - it's an object. Objects contain key-value pairs. In this case, the values are arrays, and the objects in those arrays are themselves arrays.
An important thing to note here if you're coming from another language that defines 'regular' multi-dimensional arrays is that there is no expectation or guarantee that the 'rows' of this structure are all the same length.
I've run into the following code in the es-discuss mailing list:
Array.apply(null, { length: 5 }).map(Number.call, Number);
This produces
[0, 1, 2, 3, 4]
Why is this the result of the code? What's happening here?
Understanding this "hack" requires understanding several things:
Why we don't just do Array(5).map(...)
How Function.prototype.apply handles arguments
How Array handles multiple arguments
How the Number function handles arguments
What Function.prototype.call does
They're rather advanced topics in javascript, so this will be more-than-rather long. We'll start from the top. Buckle up!
1. Why not just Array(5).map?
What's an array, really? A regular object, containing integer keys, which map to values. It has other special features, for instance the magical length variable, but at it's core, it's a regular key => value map, just like any other object. Let's play with arrays a little, shall we?
var arr = ['a', 'b', 'c'];
arr.hasOwnProperty(0); //true
arr[0]; //'a'
Object.keys(arr); //['0', '1', '2']
arr.length; //3, implies arr[3] === undefined
//we expand the array by 1 item
arr.length = 4;
arr[3]; //undefined
arr.hasOwnProperty(3); //false
Object.keys(arr); //['0', '1', '2']
We get to the inherent difference between the number of items in the array, arr.length, and the number of key=>value mappings the array has, which can be different than arr.length.
Expanding the array via arr.length does not create any new key=>value mappings, so it's not that the array has undefined values, it does not have these keys. And what happens when you try to access a non-existent property? You get undefined.
Now we can lift our heads a little, and see why functions like arr.map don't walk over these properties. If arr[3] was merely undefined, and the key existed, all these array functions would just go over it like any other value:
//just to remind you
arr; //['a', 'b', 'c', undefined];
arr.length; //4
arr[4] = 'e';
arr; //['a', 'b', 'c', undefined, 'e'];
arr.length; //5
Object.keys(arr); //['0', '1', '2', '4']
arr.map(function (item) { return item.toUpperCase() });
//["A", "B", "C", undefined, "E"]
I intentionally used a method call to further prove the point that the key itself was never there: Calling undefined.toUpperCase would have raised an error, but it didn't. To prove that:
arr[5] = undefined;
arr; //["a", "b", "c", undefined, "e", undefined]
arr.hasOwnProperty(5); //true
arr.map(function (item) { return item.toUpperCase() });
//TypeError: Cannot call method 'toUpperCase' of undefined
And now we get to my point: How Array(N) does things. Section 15.4.2.2 describes the process. There's a bunch of mumbo jumbo we don't care about, but if you manage to read between the lines (or you can just trust me on this one, but don't), it basically boils down to this:
function Array(len) {
var ret = [];
ret.length = len;
return ret;
}
(operates under the assumption (which is checked in the actual spec) that len is a valid uint32, and not just any number of value)
So now you can see why doing Array(5).map(...) wouldn't work - we don't define len items on the array, we don't create the key => value mappings, we simply alter the length property.
Now that we have that out of the way, let's look at the second magical thing:
2. How Function.prototype.apply works
What apply does is basically take an array, and unroll it as a function call's arguments. That means that the following are pretty much the same:
function foo (a, b, c) {
return a + b + c;
}
foo(0, 1, 2); //3
foo.apply(null, [0, 1, 2]); //3
Now, we can ease the process of seeing how apply works by simply logging the arguments special variable:
function log () {
console.log(arguments);
}
log.apply(null, ['mary', 'had', 'a', 'little', 'lamb']);
//["mary", "had", "a", "little", "lamb"]
//arguments is a pseudo-array itself, so we can use it as well
(function () {
log.apply(null, arguments);
})('mary', 'had', 'a', 'little', 'lamb');
//["mary", "had", "a", "little", "lamb"]
//a NodeList, like the one returned from DOM methods, is also a pseudo-array
log.apply(null, document.getElementsByTagName('script'));
//[script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script]
//carefully look at the following two
log.apply(null, Array(5));
//[undefined, undefined, undefined, undefined, undefined]
//note that the above are not undefined keys - but the value undefined itself!
log.apply(null, {length : 5});
//[undefined, undefined, undefined, undefined, undefined]
It's easy to prove my claim in the second-to-last example:
function ahaExclamationMark () {
console.log(arguments.length);
console.log(arguments.hasOwnProperty(0));
}
ahaExclamationMark.apply(null, Array(2)); //2, true
(yes, pun intended). The key => value mapping may not have existed in the array we passed over to apply, but it certainly exists in the arguments variable. It's the same reason the last example works: The keys do not exist on the object we pass, but they do exist in arguments.
Why is that? Let's look at Section 15.3.4.3, where Function.prototype.apply is defined. Mostly things we don't care about, but here's the interesting portion:
Let len be the result of calling the [[Get]] internal method of argArray with argument "length".
Which basically means: argArray.length. The spec then proceeds to do a simple for loop over length items, making a list of corresponding values (list is some internal voodoo, but it's basically an array). In terms of very, very loose code:
Function.prototype.apply = function (thisArg, argArray) {
var len = argArray.length,
argList = [];
for (var i = 0; i < len; i += 1) {
argList[i] = argArray[i];
}
//yeah...
superMagicalFunctionInvocation(this, thisArg, argList);
};
So all we need to mimic an argArray in this case is an object with a length property. And now we can see why the values are undefined, but the keys aren't, on arguments: We create the key=>value mappings.
Phew, so this might not have been shorter than the previous part. But there'll be cake when we finish, so be patient! However, after the following section (which'll be short, I promise) we can begin dissecting the expression. In case you forgot, the question was how does the following work:
Array.apply(null, { length: 5 }).map(Number.call, Number);
3. How Array handles multiple arguments
So! We saw what happens when you pass a length argument to Array, but in the expression, we pass several things as arguments (an array of 5 undefined, to be exact). Section 15.4.2.1 tells us what to do. The last paragraph is all that matters to us, and it's worded really oddly, but it kind of boils down to:
function Array () {
var ret = [];
ret.length = arguments.length;
for (var i = 0; i < arguments.length; i += 1) {
ret[i] = arguments[i];
}
return ret;
}
Array(0, 1, 2); //[0, 1, 2]
Array.apply(null, [0, 1, 2]); //[0, 1, 2]
Array.apply(null, Array(2)); //[undefined, undefined]
Array.apply(null, {length:2}); //[undefined, undefined]
Tada! We get an array of several undefined values, and we return an array of these undefined values.
The first part of the expression
Finally, we can decipher the following:
Array.apply(null, { length: 5 })
We saw that it returns an array containing 5 undefined values, with keys all in existence.
Now, to the second part of the expression:
[undefined, undefined, undefined, undefined, undefined].map(Number.call, Number)
This will be the easier, non-convoluted part, as it doesn't so much rely on obscure hacks.
4. How Number treats input
Doing Number(something) (section 15.7.1) converts something to a number, and that is all. How it does that is a bit convoluted, especially in the cases of strings, but the operation is defined in section 9.3 in case you're interested.
5. Games of Function.prototype.call
call is apply's brother, defined in section 15.3.4.4. Instead of taking an array of arguments, it just takes the arguments it received, and passes them forward.
Things get interesting when you chain more than one call together, crank the weird up to 11:
function log () {
console.log(this, arguments);
}
log.call.call(log, {a:4}, {a:5});
//{a:4}, [{a:5}]
//^---^ ^-----^
// this arguments
This is quite wtf worthy until you grasp what's going on. log.call is just a function, equivalent to any other function's call method, and as such, has a call method on itself as well:
log.call === log.call.call; //true
log.call === Function.call; //true
And what does call do? It accepts a thisArg and a bunch of arguments, and calls its parent function. We can define it via apply (again, very loose code, won't work):
Function.prototype.call = function (thisArg) {
var args = arguments.slice(1); //I wish that'd work
return this.apply(thisArg, args);
};
Let's track how this goes down:
log.call.call(log, {a:4}, {a:5});
this = log.call
thisArg = log
args = [{a:4}, {a:5}]
log.call.apply(log, [{a:4}, {a:5}])
log.call({a:4}, {a:5})
this = log
thisArg = {a:4}
args = [{a:5}]
log.apply({a:4}, [{a:5}])
The later part, or the .map of it all
It's not over yet. Let's see what happens when you supply a function to most array methods:
function log () {
console.log(this, arguments);
}
var arr = ['a', 'b', 'c'];
arr.forEach(log);
//window, ['a', 0, ['a', 'b', 'c']]
//window, ['b', 1, ['a', 'b', 'c']]
//window, ['c', 2, ['a', 'b', 'c']]
//^----^ ^-----------------------^
// this arguments
If we don't provide a this argument ourselves, it defaults to window. Take note of the order in which the arguments are provided to our callback, and let's weird it up all the way to 11 again:
arr.forEach(log.call, log);
//'a', [0, ['a', 'b', 'c']]
//'b', [1, ['a', 'b', 'c']]
//'b', [2, ['a', 'b', 'c']]
// ^ ^
Whoa whoa whoa...let's back up a bit. What's going on here? We can see in section 15.4.4.18, where forEach is defined, the following pretty much happens:
var callback = log.call,
thisArg = log;
for (var i = 0; i < arr.length; i += 1) {
callback.call(thisArg, arr[i], i, arr);
}
So, we get this:
log.call.call(log, arr[i], i, arr);
//After one `.call`, it cascades to:
log.call(arr[i], i, arr);
//Further cascading to:
log(i, arr);
Now we can see how .map(Number.call, Number) works:
Number.call.call(Number, arr[i], i, arr);
Number.call(arr[i], i, arr);
Number(i, arr);
Which returns the transformation of i, the current index, to a number.
In conclusion,
The expression
Array.apply(null, { length: 5 }).map(Number.call, Number);
Works in two parts:
var arr = Array.apply(null, { length: 5 }); //1
arr.map(Number.call, Number); //2
The first part creates an array of 5 undefined items. The second goes over that array and takes its indices, resulting in an array of element indices:
[0, 1, 2, 3, 4]
Disclaimer: This is a very formal description of the above code - this is how I know how to explain it. For a simpler answer - check Zirak's great answer above. This is a more in depth specification in your face and less "aha".
Several things are happening here. Let's break it up a bit.
var arr = Array.apply(null, { length: 5 }); // Create an array of 5 `undefined` values
arr.map(Number.call, Number); // Calculate and return a number based on the index passed
In the first line, the array constructor is called as a function with Function.prototype.apply.
The this value is null which does not matter for the Array constructor (this is the same this as in the context according to 15.3.4.3.2.a.
Then new Array is called being passed an object with a length property - that causes that object to be an array like for all it matters to .apply because of the following clause in .apply:
Let len be the result of calling the [[Get]] internal method of argArray with argument "length".
As such, .apply is passing arguments from 0 to .length , since calling [[Get]] on { length: 5 } with the values 0 to 4 yields undefined the array constructor is called with five arguments whose value is undefined (getting an undeclared property of an object).
The array constructor is called with 0, 2 or more arguments.
The length property of the newly constructed array is set to the number of arguments according to the specification and the values to the same values.
Thus var arr = Array.apply(null, { length: 5 }); creates a list of five undefined values.
Note: Notice the difference here between Array.apply(0,{length: 5}) and Array(5), the first creating five times the primitive value type undefined and the latter creating an empty array of length 5. Specifically, because of .map's behavior (8.b) and specifically [[HasProperty] .
So the code above in a compliant specification is the same as:
var arr = [undefined, undefined, undefined, undefined, undefined];
arr.map(Number.call, Number); // Calculate and return a number based on the index passed
Now off to the second part.
Array.prototype.map calls the callback function (in this case Number.call) on each element of the array and uses the specified this value (in this case setting the this value to `Number).
The second parameter of the callback in map (in this case Number.call) is the index, and the first is the this value.
This means that Number is called with this as undefined (the array value) and the index as the parameter. So it's basically the same as mapping each undefined to its array index (since calling Number performs type conversion, in this case from number to number not changing the index).
Thus, the code above takes the five undefined values and maps each to its index in the array.
Which is why we get the result to our code.
As you said, the first part:
var arr = Array.apply(null, { length: 5 });
creates an array of 5 undefined values.
The second part is calling the map function of the array which takes 2 arguments and returns a new array of the same size.
The first argument which map takes is actually a function to apply on each element in the array, it is expected to be a function which takes 3 arguments and returns a value.
For example:
function foo(a,b,c){
...
return ...
}
if we pass the function foo as the first argument it will be called for each element with
a as the value of the current iterated element
b as the index of the current iterated element
c as the whole original array
The second argument which map takes is being passed to the function which you pass as the first argument. But it would not be a, b, nor c in case of foo, it would be this.
Two examples:
function bar(a,b,c){
return this
}
var arr2 = [3,4,5]
var newArr2 = arr2.map(bar, 9);
//newArr2 is equal to [9,9,9]
function baz(a,b,c){
return b
}
var newArr3 = arr2.map(baz,9);
//newArr3 is equal to [0,1,2]
and another one just to make it clearer:
function qux(a,b,c){
return a
}
var newArr4 = arr2.map(qux,9);
//newArr4 is equal to [3,4,5]
So what about Number.call ?
Number.call is a function that takes 2 arguments, and tries to parse the second argument to a number (I'm not sure what it does with the first argument).
Since the second argument that map is passing is the index, the value that will be placed in the new array at that index is equal to the index. Just like the function baz in the example above. Number.call will try to parse the index - it will naturally return the same value.
The second argument you passed to the map function in your code doesn't actually have an effect on the result. Correct me if I'm wrong, please.
An array is simply an object comprising the 'length' field and some methods (e.g. push). So arr in var arr = { length: 5} is basically the same as an array where the fields 0..4 have the default value which is undefined (i.e. arr[0] === undefined yields true).
As for the second part, map, as the name implies, maps from one array to a new one. It does so by traversing through the original array and invoking the mapping-function on each item.
All that's left is to convince you that the result of mapping-function is the index. The trick is to use the method named 'call'(*) which invokes a function with the small exception that the first param is set to be the 'this' context, and the second becomes the first param (and so on). Coincidentally, when the mapping-function is invoked, the second param is the index.
Last but not least, the method which is invoked is the Number "Class", and as we know in JS, a "Class" is simply a function, and this one (Number) expects the first param to be the value.
(*) found in Function's prototype (and Number is a function).
MASHAL