Why can't javascript copy an object? - javascript

Or can it? This may sound like a dumb question... and I'm really hoping it is because my initial Googling about lead me to believe that there is no simple way to copy an object in JavaScript.
Say you have the following:
var allFood = ['fish', 'greens', 'fruit', 'candy', 'soda', 'cookies'];
var moreFood = allFood;
var diffFood = allFood;
moreFood.splice(3, 0, "grain", "juice");
diffFood.splice(3, 3, "tacos", "meat");
document.write(allFood + "<br/>");
document.write(diffFood);
Now they all equal "fish,greens,fruit,tacos,meat,soda,cookies" which is annoying, for lack of a better word.
Could you explain why JavaScript has this limitation?
And yeah, I read this: What is the most efficient way to deep clone an object in JavaScript? which seems a little cumbersome and actually failed when I tried it with the above example...

For your specific example, i.e. for arrays with simple elements, the slice method can do it:
var allFood = ['fish', 'greens', 'fruit', 'candy', 'soda', 'cookies'];
var moreFood = allFood.slice();
var diffFood = allFood.slice();
This method creates a so-called shallow copy of the array. So it does the job for arrays with primitives, such as strings, numbers and booleans.
If an array element is an object, that object reference is copied, and so the arrays share the original object:
var a = [{x: 1}];
var b = a.slice();
// both arrays reference the same object:
console.log(a[0] === b[0]); // output: true
// so...
b[0].x = 2;
console.log(a[0].x); // output: 2

You can read about Javascript's pass-by-reference and pass-by-value behaviour here.
For cloning javascript object you can use lodash library.
var allFood = ['fish', 'greens', 'fruit', 'candy', 'soda', 'cookies'];
var moreFood = lodash.cloneDeep(allFood);
Now changes you make to allFood wouldn't affect moreFood and vice versa.

A simple way to clone objects available in es6, though current supported by most browsers is Object.assign
var original = [1, 2, 3];
var copy = [];
Object.assign(copy, original);
copy.push(2);
//=> [1, 2, 3, 2]
console.log(original);
// => [1, 2, 3];

Related

JavaScript Array to Set

MDN references JavaScript's Set collection abstraction. I've got an array of objects that I'd like to convert to a set so that I am able to remove (.delete()) various elements by name:
var array = [
{name: "malcom", dogType: "four-legged"},
{name: "peabody", dogType: "three-legged"},
{name: "pablo", dogType: "two-legged"}
];
How do I convert this array to a set? More specifically, is it possible to do this without iterating over the above array? The documentation is relatively lacking (sufficient for instantiated sets; not for conversions - if possible).
I may also be thinking of the conversion to a Map, for removal by key. What I am trying to accomplish is an iterable collection that can be accessed or modified via accessing the elements primarily via a key (as opposed to index).
Conversion from an array to the other being the ultimate goal.
Just pass the array to the Set constructor. The Set constructor accepts an iterable parameter. The Array object implements the iterable protocol, so its a valid parameter.
var arr = [55, 44, 65];
var set = new Set(arr);
console.log(set.size === arr.length);
console.log(set.has(65));
See here
If you start out with:
let array = [
{name: "malcom", dogType: "four-legged"},
{name: "peabody", dogType: "three-legged"},
{name: "pablo", dogType: "two-legged"}
];
And you want a set of, say, names, you would do:
let namesSet = new Set(array.map(item => item.name));
By definition "A Set is a collection of values, where each value may occur only once." So, if your array has repeated values then only one value among the repeated values will be added to your Set.
var arr = [1, 2, 3];
var set = new Set(arr);
console.log(set); // {1,2,3}
var arr = [1, 2, 1];
var set = new Set(arr);
console.log(set); // {1,2}
So, do not convert to set if you have repeated values in your array.
const categoryWithDuplicates =[
'breakfast', 'lunch',
'shakes', 'breakfast',
'lunch', 'shakes',
'breakfast', 'lunch',
'shakes'
]
const categoryWithoutDuplicates =[...new Set(categoryWithDuplicates)];
console.log(categoryWithoutDuplicates)
Output: [ 'breakfast', 'lunch', 'shakes' ]
What levi said about passing it into the constructor is correct, but you could also use an object.
I think what Veverke is trying to say is that you could easily use the delete keyword on an object to achieve the same effect.
I think you're confused by the terminology; properties are components of the object that you can use as named indices (if you want to think of it that way).
Try something like this:
var obj = {
"bob": "dole",
"mr.": "peabody",
"darkwing": "duck"
};
Then, you could just do this:
delete obj["bob"];
The structure of the object would then be this:
{
"mr.": "peabody",
"darkwing": "duck"
}
Which has the same effect.
var yourSetAsArray = Array.from(new Set([1, 2, 2, 3, 3, 4, 4]));
// returns [1, 2, 3, 4]

Convert ES6 Iterable to Array

Say you have an array-like Javascript ES6 Iterable that you know in advance will be finite in length, what's the best way to convert that to a Javascript Array?
The reason for doing so is that many js libraries such as underscore and lodash only support Arrays, so if you wish to use any of their functions on an Iterable, it must first be converted to an Array.
In python you can just use the list() function. Is there an equivalent in ES6?
You can use Array.from or spread syntax (...).
Example:
const x = new Set([ 1, 2, 3, 4 ]);
const y = Array.from(x);
console.log(y); // = [ 1, 2, 3, 4 ]
const z = [ ...x ];
console.log(z); // = [ 1, 2, 3, 4 ]
Summary:
Array.from() function, it takes an iterable as in input and returns an array of the iterable.
Spread syntax: ... in combination with an array literal.
const map = new Map([[ 1, 'one' ],[ 2, 'two' ]]);
const newArr1 = [ ...map ]; // create an Array literal and use the spread syntax on it
const newArr2 = Array.from( map ); //
console.log(newArr1, newArr2);
Caveat when copying arrays:
Be cognizant of the fact that via these methods above only a shallow copy is created when we want to copy an array. An example will clarify the potential issue:
let arr = [1, 2, ['a', 'b']];
let newArr = [ ...arr ];
console.log(newArr);
arr[2][0] = 'change';
console.log(newArr);
Here because of the nested array the reference is copied and no new array is created. Therefore if we mutate the inner array of the old array, this change will be reflected in the new array (because they refer to the same array, the reference was copied).
Solution for caveat:
We can resolve the issue of having shallow copies by creating a deep clone of the array using JSON.parse(JSON.stringify(array)). For example:
let arr = [1, 2, ['a', 'b']]
let newArr = Array.from(arr);
let deepCloneArr = JSON.parse(JSON.stringify(arr));
arr[2][0] = 'change';
console.log(newArr, deepCloneArr)
You can use the Array.from method, which is being added in ES6, but only supports arrays and iterable objects like Maps and Sets (also coming in ES6). For regular objects, you can use Underscore's toArray method or lodash's toArray method, since both libraries actually have great support for objects, not just arrays. If you are already using underscore or lodash, then luckily they can handle the problem for you, alongside adding various functional concepts like map and reduce for your objects.
The following approach is tested for Maps:
const MyMap = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
const MyArray = [...MyMap].map(item => {
return {[item[0]]: item[1]}
});
console.info( MyArray ); //[{"a", 1}, {"b", 2}, {"c": 3}]
<Your_Array> = [].concat.apply([], Array.from( <Your_IterableIterator> ));
You could also do the following, but both approaches are certainly not recommendable (merely a proof-of-concept for completeness):
let arr = [];
for (let elem of gen(...)){
arr.push(elem);
}
Or "the hard way" using ES5 + generator function (Fiddle works in current Firefox):
var squares = function* (n) {
for (var i = 0; i < n; i++) {
yield i * i;
}
};
var arr = [];
var gen = squares(10);
var g;
while (true) {
g = gen.next();
if (g.done) {
break;
}
arr.push(g.value);
}

Should I use curly brackets {} or square brackets [] in this case?

Currently I have an array using an increasing index:
var idx = 1;
var a = [];
a[idx++] = "apple";
a[idx++] = "orange";
...
console.log(a[2]);
And only accessing it by [], not using array specific functions, like length, indexOf, ...
Apparently following is also working in this case:
var a = {};
So, which one should I prefer in such case? For example any performance difference between them?
[ ] denotes an array. Arrays only hold values:
var a1 = [1, 2, 3, 4]
As #Qantas pointed out, array can hold more than just values. An array can even contain another array and/or object:
var a2 = [1, 2, ["apple", "orange"], {one: "grape", two: "banana"}];
{ } denotes an object. Objects have key-value pairs like
var a3 = {one: 1, two: 2}
In your case, it's really a matter of how you would like to be able to access the data. If you are only interested in knowing "apple", "pear", etc. Go ahead and use an array. You can access it via it's index
a1[0]; // outputs 1
a1[1]; // outputs 2
or you can iterate over it with a loop. If you use the curly braces, (given the example I gave) you could access it with
a3.one; // outputs 1
a3["two"]; // outputs 2
It's really up to you on how it would best fit your needs in this case. For a more extensive discussion see this article.
The difference is using square brackets will create an Array object while using curly brackets creates a plain object. For example:
a = [];
a[1] = 'a';
b = {};
b[1] = 'b';
a.length; // returns 2
b.length; // is undefined
a.push('z'); // add 'z' to the end of a
b.push('z'); // generates an error - undefined is not a function
// because plain objects don't have a push method
Read the MDN documentation on Array objects to know more about arrays: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

Reference in slice in javascript does not exit.

I am getting back to JS after a very long break and as I was going over "Jump Start JavaScript" book. In part of it says:
if your array contains an array as one of its items, it will be
copied by reference. In other words, if the original array changes, so
will the copy.
So I tried to do in code but JS doesn't work! (correct me if I'm wrong)
Then in SOF I saw this thread:
Reference to slice of an array
Should I consider this as mistake/error in the book?!
Thanks,
AK
It's unfortunate that the phrase by reference gets thrown around like it does, because it has a perfectly clear meaning that is completely different from one the being (mis-) used here. All values in JavaScript are copied/passed by value. When dealing with objects, that value is a reference to the object. But it's still a value that is copied.
When you make a shallow copy of an array using slice, the elements that are references to objects have those same references copied to the new array. That is, the same position in both arrays will refer to the same object. So, yes, changing one changes the other because there is no other; they are the same object.
Proof:
var arr = [[7, 6, 5], 4, 5];
var copy = arr.slice();
arr[0][0] = 88;
console.log(copy[0][0]); // 88
Tangent on pass-by-reference
If you can't write a function like this:
function swap(arg1, arg2) {
var temp = arg1;
arg1 = arg2;
arg2 = temp;
}
...that actually swaps the values of two variables:
var one = [1, 2, 3];
var two = ["a", "b", "c"];
swap(one, two);
console.log(one) // same as before
...then your language does not support pass by reference.
More pass-by-value
Wait, doesn't the following demonstrate that JavaScript must be pass-by-reference:
function Test(arg1) {
this.arg = arg1;
}
var one = [1, 2, 3];
var x = new Test(one);
x.arg[0] = 5;
console.log(one); // it's been updated!
No! If you think this then you still haven't understood what pass by reference actually is. Yes, a reference to the original array was passed to Test, but it was passed by value. In other words, x.arg refers to the same object in memory as one but it is not an alias for one. To prove this, you simply need to assign something new to x.arg:
x.arg = ["new array"];
This does not affect one. These two variables are independent. JavaScript is strictly pass-by-value. There are no exceptions.
In JavaScript it´s also correct to initialize an Array with the "new" keyword:
var arr = new Array();
arr.push("0. Index");
arr.push("1. Index");
document.body.appendChild(document.createTextNode(JSON.stringify(arr)));
In every programming language I know, the new keyword stands for creating an object. And also in every programming language I know, an object is Call-By-Reference and not Call-By-Value. So the book tells you the truth. That´s a JavaScript special: Because PHP arrays are always Copy-By-Value, if you do not use explicit the & for making it to a reference.
To clear this this disussion finally, execute this code below in you browser and compare the used memory size. A 2nd reference to an already existing object should cause lower memory usage than 2 seperated arrays:
First this one:
var arr1 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
}
var arr2 = arr2;
Second this one:
var arr1 = new Array();
var arr2 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
arr2.push(i);
}
Or simply try this one and see the results:
var arr1 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
}
var arr2 = arr1;
arr2[5] = "test";
document.body.appendChild(document.createTextNode("arr2 => " + JSON.stringify(arr2)));
document.body.appendChild(document.createTextNode("arr1 => " + JSON.stringify(arr1)));
If the output prints on the 5th index for both arrays "test" instead of 5, it´s for sure Call-By-Reference, as your book said. If not, it´s Copy-By-Value.
var subarray = [1, 2, 3];
var main_array = ['a', subarray, 'b'];
var slice = main_array.slice(0, 2);
console.log(slice); => ['a', [1, 2, 3]]
subarray[0] = 10;
console.log(slice); => ['a', [10, 2, 3]]

Is there a multidimensional array type in Javascript?

I have programmed in Microsoft Small Basic in the past, which can have arrays like this:
Array[1][1] = "Hello"
Array[1][2] = "Hi"
Array[1][2] = "Hey"
Now, in Javascript, I know how to create a single array (var Array = New Array()) but are there any array types like the ones above?
There are no true multidimensional arrays in JavaScript. But you can create an array of arrays like you have done.
JavaScript's arrays are just objects with a special length property and a different prototype chain.
Yes, you need to create an array of arrays:
var x = new Array(3);
x[0] = new Array(3);
x[1] = new Array(3);
x[2] = new Array(3);
x[0][0] = "Hello";
etc.
Remember that indexing is zero-based.
Edit
Or:
var x=[];
x[0] = [];
x[1] = [];
x[2] = [];
...
x[0][0] = "Hello";
etc.
You can achieve this:
var o = [[1,2,3],[4,5,6]];
Also you can use the fact that objects in javascript are dictionaries:
var o;
o["0"] = {'0':1, '1':2, '1':3};
var x = o["0"]["1"]; //returns 2
The easiest way would be to just declare an array, and initialize it with a bunch of other arrays. For example:
var mArray = [
[1,2,3],
[4,5,6]
];
window.alert(mArray[1][1]); //Displays 5
As others have pointed out, this is not actually a multi-dimentional array in the standard sense. It's just an array that happens to contain other arrays. You could just as easily have an array that had 3 other arrays, an int, a string, a function, and an object. JavaScript is cool like that.
You can create arrays statically in JS like this:
var arr = [
[1, 2, 3, 4],
[8, 6, 7, 8]
];
Note that since this is not a true "multidimentional array", just an "array of arrays" the "inner arrays" do not have to be the same length, or even the same type. Like so:
var arr = [
[1, 2, 3, 4],
["a", "b"]
];

Categories