How to I achieve the following functionality?
I have an array:
a = [1, 2, 3, 4, 5]
b = [a[1], a[2], a[3]] //This array should be some kind of "array of references"
Any change in the array b should be applied to array a, as well.
The problem is that primitive values (String, Number, Boolean, undefined and null), work by value, and they are non-mutable.
If you use objects as the array elements you can get the desired behavior:
var a = [{value: 1}, {value:2}, {value:3}, {num:4}];
var b = [a[1], a[2], a[3]];
alert(a[1].value); // 2
b[0].value = "foo";
alert(a[1].value); // "foo"
Related
Can someone tell why object merges values but array does not
See the code block below:
const a = {'a': 1, 'b': 2}
const b = {'b': 4, 'c': 3}
console.log({...a, ...b})
This Outputs
{ a: 1, b: 4, c: 3 }
But when I use the code below:
const c = [1,2]
const d = [2,3]
console.log([...c, ...d])
This outputs
[ 1, 2, 2, 3 ]
Why object merges properties...
It doesn't merge properties, it merges objects. Notice the value of b in your result: It's 4 (the value from the b object), not some merged value of 2 (from the a object) and 4 (from the b object). Each property from each source object is just copied into the target object (with later objects overwriting properties from earlier objects), the properties themselves are not merged together.
But fundamentally, object property spread and iterable spread are just completely different things with different purposes and different semantics, because objects and arrays are different beasts (at least conceptually; arrays actually are objects in JavaScript). Properties have names which are an intrinsic part of the property. Array elements just have indexes, and it's normal for values to be moved around an array (moved to different indexes). The two different definitions of spread are each useful for the data type they're defined for.
If you want to treat an array like an object, though, you can since arrays are objects in JavaScript. (Although in this case it isn't useful.) Here's an example (I've changed c's element values so it's clear what's coming from where):
const c = ["a", "b"];
const d = [2, 3];
console.log(Object.assign([], c, d));
In that case, since d has values for both indexes 0 and 1, none of c's elements are in the result. But:
const c = ["a", "b", "c", "d", "e"];
const d = [2, 3];
console.log(Object.assign([], c, d));
Short answer
When using the spread operator, Regular Objects are ASSIGNED.
When using the spread operator, Arrays are CONCATENATED.
Long Answer
I believe the source of your confusion is that every array in JavaScript is just an object belonging to the Array constructor. So why doesn't joining two or more arrays with the spread operator work the same way as objects do?
Let's analyze what is happening in case of the Object
const a = {'a': 1, 'b': 2};
const b = {'b': 4, 'c': 3};
console.log({...a, ...b}); // Output: { a: 1, b: 4, c: 3 }
console.log(Object.assign({}, a, b)); // Output: { a: 1, b: 4, c: 3 }
console.log({...b, ...a}); // Output: { a: 1, b: 2, c: 3 }
console.log(Object.assign({}, b, a)); // Output: { a: 1, b: 2, c: 3 }
An object is a data structure holding key:value pairs.
Object assignment overwrites the keys with the latest values.
The key b occurs in more than one object and is overwritten with it's latest value. As you can see, if you change the order of the objects spread/assigned, the resulting value of the value of b changes based on the latest object having b.
Now let's come to the Array.
const c = [1,2];
const d = [2,3];
console.log([...c, ...d]); // Output: [ 1, 2, 2, 3 ]
console.log(c.concat(d)); // Output: [ 1, 2, 2, 3 ]
console.log(Object.assign({}, c, d)); // Output: { '0': 2, '1': 3 }
console.log(Object.values(Object.assign({}, c, d))); // Output: [ 2, 3 ]
An array is an object created with the Array constructor which outputs the array as a collection of the values assigned to its keys.
Array concatenation simply joins the arrays.
As you can see above, Object.assign still works on an array because the array is technically an object and it behaves exactly how Object.assign is supposed to work. The keys in this case are simply what we call "index" in an array. This is why when you do array[index] it returns the value, it's the same as object[key] that returns a value. If keys exist, the Object.assign replaces the keys/index with the latest values, else it adds the key-value pair to the object.
Conclusion:
Thus, the difference is how the spread operator works for objects and arrays.
In Objects, spread does Object.assign.
In Arrays, spread does Array concatenation => arrayA.concat(arrayB, arrayC, ...)
Bonus: Set
However, if you want the array to return only unique values, you have to use the Set data structure.
const c = [1,2];
const d = [2,3];
console.log([...new Set([...c, ...d])]); // Output: [1, 2, 3]
console.log(Array.from(new Set(a.concat(b)))); // Output: [1, 2, 3]
UPDATE:
Many asked why not using [arr[0], arr[1]]. The problem is I have to pass this array to a method, which I don't have access Angular Material Table. And I don't want to call the method over and over again.
I already processed the arr array and I don't want to process pointer array to reflect the new data, which I already know where it is.
The Nina Scholz answer seems to solve the problem.
Is there a way to use "pointers" like C in Javascript?
What I want to do is:
I have an array with objects
const arr = [
{prop: 3},
{prop: 4},
];
And I want to have an array to point to the positions of this array
const pointer = [arr[0], arr[1]]; // I want pointer to point to be an array containing the first and second elements of arr
This will get a reference to the {prop: 3} and {prop: 4} objects, which is not what I want, because, if I do:
arr.splice(0, 0, {prop: 1}); // arr => [{prop:1},{prop:3},{prop:4}]
console.log(pointer); // [{prop: 3},{prop: 4}]
As you can see, pointer holds a reference to the objects {prop:3} and {prop:4}.
How can I achieve pointer to hold reference to the position 0 of the array, instead of the object stored in it? So, on this example, pointer => [{prop:1},{prop:3}]?
I can't call pointer = [arr[0], arr[1]] all the time because arr will change constantly and asynchronously.
Is there a "reactive" way to handle arrays?
If your pointers are always to the same array, you can simply store the indexes.
const pointer = [0, 1];
Then you would use:
console.log(pointer.map(ptr => arr[ptr]));
If your pointers can point to different arrays, you can make the elements of pointer be objects that contain references to the array along with their indexes.
const pointer = [{a: arr, i: 0}, {a: arr1, i: 1}];
console.log(pointer.map(({a, i}) => a[i]));
Interesting aside: several decades ago I used a C implementation for Symbolics Lisp Machines. This is basically how it represented C pointers.
You could use a getter function and return the element of the actual object.
const arr = [{ prop: 3 }, { prop: 4 }];
const pointer = [];
Object.defineProperty(pointer, 0, { get() { return arr[0]; } });
Object.defineProperty(pointer, 1, { get() { return arr[1]; } });
arr.splice(0, 0, { prop: 1 });
console.log(pointer);
You can use a Proxy (not supported by IE) with a get trap:
const arr = [{ prop: 3 }, { prop: 4 }];
const pointer = new Proxy([], {
get(target, prop, receiver) {
// if the prop is a string that can be converted to a number
// return the corresponding value from the arr
if(typeof prop === 'string' && !isNaN(Number(prop))) return arr[target[prop]];
return Reflect.get(target, prop, receiver);
}
});
pointer.push(0, 1);
console.log(pointer);
arr.splice(0, 0, { prop: 1 });
console.log(pointer);
I want to generate an empty array of a given length and the populate it with some numbers. One way to generate an array with four sequential numerical elements is :
var x = Array.apply(null, {length: 4}).map(function(item, index){return index;})
But when I saw Array.apply(null, {length: 4}) I thought I could instead replace it with new Array(4) but that is not the case. Doing a quick test yields the following:
>> console.log((new Array(4)))
<< [ <4 empty items> ]
>> console.log(Array.apply(null, {length: 4}))
<< [ undefined, undefined, undefined, undefined ]
Which means I can .map the latter but not the former.
What is the difference then between new Array and Array.apply(null, {}) which I thought were both creating an array object with given length?
apply takes a context as the first parameter and an arraylike list of arguments as a second. Then it calls the function (Array) with the iterable as arguments.
Array.apply(null, [1, 2])
// Same as
Array(1, 2)
// Or
[1, 2]
Now if you pass an object as an arraylike, it will still iterate it like this:
function apply(context, args) {
for(var i = 0; i < args.length; i++) {
/*...*/ args[i];
}
}
So if you pass { length: 4 } it will iterate four times and take undefined as an argument, so it results in something like:
Array.apply(null, { length: 4 })
// Same as
Array(undefined, undefined, undefined)
Therefore the arrays slots are not empty, but they are undefined, and as map only skips empty slots it will go over every entry of the second array.
By the way, the same can be achieved a bit more readable:
Array.from({length: 4 }, (_, i) => i)
// [0, 1, 2, 3]
The answer requires a bit of drilling down into the mechanics of Array objects.
new Array(4) creates an array with length 4 but no items (Sparse Array)
Array.apply(null, {length: 4}) creates an array with 4 undefined elements.
The second one uses a few tricks:
apply calls a function with given context and arguments provided in an array.
Array, when called directly as a function creates an array from the elements it got as arguments, e.g:
\> Array(1,2,3)
[ 1, 2, 3 ]
\> Array(...[1,2,3])
[ 1, 2, 3 ]
\> Array(...new Array(4))
[ undefined, undefined, undefined, undefined ]
So why is Array.apply(null, {length: 4}) equivalent to Array.apply(null, new Array(4)?
apply parses the arguments array by looking at the length and then taking the relevant arguments. {length: 4}.length is 4, so it takes
{length:4}[0]
{length:4}[1]
{length:4}[2]
{length:4}[3]
which are all undefined.
The difference is that
new Array(4) does not initialize the slots in the array. It only sets the length property of the array. Where as Array.apply(null, {length: 4}) initializes each slot to undefined.
I am trying to use Array.prototype.push.apply to merge two lists.
c = Array.prototype.push.apply(a, b);
However, this does not merge the arrays when the second one is [].
for instance if
a = ['x', 'y', 'z']
b = []
c will be 3
Why is this happening?
Shouldn't [] be treated like any array?
Just use Array.prototype.concat:
c = a.concat(b);
It is perfectly correct, because Array.push() will return the length of the new array.
If you want a new array which has the concatenated value then use Array.concat() instead.
What you may have been trying to achieve is using push.apply to append b to a. However this method means that you don't have to create a new array c to hold the result.
var a = [1, 2, 3, 4], b = [5];
a.push.apply(a, b); // a = [1, 2, 3, 4, 5]
Your real problem is the .apply, it ask the contetx (a) and an array of values (b), if you pass an empty array it acts like you have passed no values...
Try this:
c = Array.prototype.push.call(a, b);
//c = 4
I have an array of objects like this:
[
{
p1: 1
p2: 2
},
{
p1: 3
p2: 4
}
]
I would like to flatten this into an array (maintaining order) for each property on the object:
[1, 3] and [2, 4]
Preferably these would be stored in a dictionary with the property as the key:
{
p1: [1, 3],
p2: [2, 4]
}
Is there a non brute force method of doing this in javascript? I am already using both jQuery and Underscore.js if those libraries are any help here.
My current plan is to iterate through all of the objects in the array and manually add each property value to its corresponding array. I'm interested to know if there is more interesting way of doing this.
If the properties are reliably the same on each object, you can use underscore like this.
var keys = _.keys(arr[0]);
var newObj = _.reduce(keys,function(obj,key){
obj[key] = _.pluck(arr,key)
},{});
//newObj will now be in the requested format
If the properties are different on different objects, you'll need to do some additional logic to get the complete list of properties to iterate over.
Something like that:
var result = data.reduce(function(acc, x) {
Object.keys(x).forEach(function(k) {
acc[k] = (acc[k] || []).concat([x[k]])
})
return acc
},{})
console.log(result)
//^ {
// p1: [1, 3],
// p2: [2, 4]
// }
But note that object keys have no order, and the result is up to each implementation. You may have to sort the resulting arrays if you need a specific order.