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]]
Related
If I do this:
a = []
b = [1, 2, 3]
a = b
a will then refer to b
I want to copy all of the values in b into a without changing any memory address/references.
You could push the values without changing the reference from a or b.
var a = [],
b = [1, 2, 3];
a.push(...b);
If you want to populate a in-place, without ever discarding the initial array, then you can simply loop over b and add all the items in a:
var a = []
var b = [1, 2, 3]
var firstA = a; //get a initial element
b.forEach(function(item) {
this.push(item)
}, a);// <-- pass `a` as the `this` context
console.log("`a` is unchanged", a === firstA);
console.log("`a` is not `b`", a !== b);
console.log(a);
let a = b.map(x => x);
The .map() function will create a new array with values generated from the source elements via the passed-in callback function.
As noted in a comment
let a = b.slice();
is pretty much exactly the same.
Basically this saves you the step of
let a = [];
since one brand-new empty array is as good as another. However, if you really have your heart set on using the empty array explicitly initialized, you could use
b.forEach(x, i) { a[i] = x; });
A= b.slice(). It will create a shadow copy of element without changing original one. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Using Descriptive way
//Declare variables
var arr1 = [1,2,3];
arr2 = [];
//Immediate Invoked Functions
(function(){
for(var i = 0 ; i<arr1.length ; i++){
arr2.push(3 * arr1[i]);
}
//logging the values
console.log(arr2);
console.log(arr1);
})();
Using Functional Programming and First Class function
//Declare variables
var arr1 = [1,2,3];
//creating a function to push values
function mapEachArrayValue(arr,fn){
var newArr=[];
for(var i=0; i<3 ; i++){
newArr.push(fn(arr1[i]));
}
return newArr;
}
var arr2 = mapEachArrayValue(arr1,function(item){
return 3 * item;
})
why do we need to use functional programming in this example? It's works fine with normal descriptive way.
You're doing this wrong. You can shorten this using the inbuilt map function. For example,
let arr1 = [1, 2, 3]
let arr2 = arr1.map(item => item * 3)
console.log(arr2) // [3, 6, 9]
This is much more concise. This doesn't use any external iterators. This also doesn't mutate the original array(though you also don't mutate it in your example). This is more readable if you know what map is.
So this is how map works: You call map on an Array and pass it a function. This is where a function being first class comes into picture. You're passing it to another function. Map basically takes this function and the array and calls this function on each entry in the array. The value your function returns is stored in another array at the corresponding indices and this newly constructed array is returned by map.
Head over to MDN Docs to learn more about map, how it works and some more examples. There are other functions like filter, reduce, etc that once you learn are very hard to not use while writing JS. Read through all the examples for these three functions and try to apply them when you code.
I'll just put this code here so that you can see how concise and expressive it can be to use these functions once you understand their syntax and what they do.
let myArr = [1,2,3,4,5,6,7,8,9]
let sumOfDoubleOfOddNumbers = myArr.filter(num => num % 2)
.map(num => num * 2)
.reduce((acc, currVal) => acc + currVal, 0);
First we filter out all numbers that do not pass the test. Then we double all these filtered numbers. Finally using a reducer and a starting value we reduced this array down to a final value. In this process, we used 3 different functions that were passed as arguments(reiterating on the functions being first class citizens point).
I'm trying to solve a freeCodeCamp exercise with this goal:
Write a function that takes two or more arrays and returns a new array
of unique values in the order of the original provided arrays.
In other words, all values present from all arrays should be included
in their original order, but with no duplicates in the final array.
The unique numbers should be sorted by their original order, but the
final array should not be sorted in numerical order.
So what I do is concatenate all the arguments into a single array called everything. I then search the array for duplicates, then search the arguments for these duplicates and .splice() them out.
So far everything works as expected, but the last number of the last argument does not get removed and I can't really figure out why.
Can anybody please point out what I'm doing wrong? Please keep in mind that I'm trying to learn, so obvious things probably won't be obvious to me and need to be pointed out. Thanks in advance.
function unite(arr1, arr2, arr3) {
var everything = [];
//concat all arrays except the first one
for(var x = 0; x < arguments.length; x++) {
for(var y = 0; y < arguments[x].length; y++) {
everything.push(arguments[x][y]);
}
}
//function that returns duplicates
function returnUnique(arr) {
return arr.reduce(function(dupes, val, i) {
if (arr.indexOf(val) !== i && dupes.indexOf(val) === -1) {
dupes.push(val);
}
return dupes;
}, []);
}
//return duplicates
var dupes = returnUnique(everything);
//remove duplicates from all arguments except the first one
for(var n = 1; n < arguments.length; n++) {
for(var m = 0; m < dupes.length; m++) {
if(arguments[n].hasOwnProperty(dupes[m])) {
arguments[n].splice(arguments[n].indexOf(dupes[m]), 1);
}
}
}
//return concatenation of the reduced arguments
return arr1.concat(arr2).concat(arr3);
}
//this returns [1, 3, 2, 5, 4, 2]
unite([1, 3, 2], [5, 2, 1, 4], [2, 1]);
Looks like you overcomplicated it a bit ;)
function unite() {
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
}
res = unite([1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]);
document.write('<pre>'+JSON.stringify(res));
Explanations
We split the problem into two steps:
combine arguments into one big array
remove non-unique elements from this big array
This part handles the first step:
[].concat.apply([], arguments)
The built-in method someArray.concat(array1, array2 etc) appends given arrays to the target. For example,
[1,2,3].concat([4,5],[6],[7,8]) == [1,2,3,4,5,6,7,8]
If our function had fixed arguments, we could call concat directly:
function unite(array1, array2, array3) {
var combined = [].concat(array1, array2, array3);
// or
var combined = array1.concat(array2, array3);
but as we don't know how many args we're going to receive, we have to use apply.
someFunction.apply(thisObject, [arg1, arg2, etc])
is the same as
thisObject.someFunction(arg1, arg2, etc)
so the above line
var combined = [].concat(array1, array2, array3);
can be written as
var combined = concat.apply([], [array1, array2, array3]);
or simply
var combined = concat.apply([], arguments);
where arguments is a special array-like object that contains all function arguments (actual parameters).
Actually, last two lines are not going to work, because concat isn't a plain function, it's a method of Array objects and therefore a member of Array.prototype structure. We have to tell the JS engine where to find concat. We can use Array.prototype directly:
var combined = Array.prototype.concat.apply([], arguments);
or create a new, unrelated, array object and pull concat from there:
var combined = [].concat.apply([], arguments);
This prototype method is slightly more efficient (since we're not creating a dummy object), but also more verbose.
Anyways, the first step is now complete. To eliminate duplicates, we use the following method:
combined.filter(function(elem, index) {
return combined.indexOf(elem) === index;
})
For explanations and alternatives see this post.
Finally, we get rid of the temporary variable (combined) and chain "combine" and "dedupe" calls together:
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
using the 3rd argument ("this array") of filter because we don't have a variable anymore.
Simple, isn't it? ;) Let us know if you have questions.
Finally, a small exercise if you're interested:
Write combine and dedupe as separate functions. Create a function compose that takes two functions a and b and returns a new function that runs these functions in reverse order, so that compose(a,b)(argument) will be the same as b(a(argument)). Replace the above definition of unite with unite = compose(combine, dedupe) and make sure it works exactly the same.
You can also try this :
var Data = [[1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]]
var UniqueValues = []
for (var i = 0; i < Data.length; i++) {
UniqueValues = [...new Set(UniqueValues.concat(Data[i]))]
}
console.log(UniqueValues)
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];
I have put together a small test, can someone explain why does JavaScript do the following?
Why will testArr inherit the modifications of newArray?
var TestClass = function() {
this.testArr = [10,11];
this.addToArray = function() {
var newArray = this.testArr;
newArray.push(12);
}
}
var testClass = new TestClass();
testClass.addToArray();
console.log(testClass.testArr); // will be [10, 11, 12]
Because they're the same array. Variables contain references to arrays. You're assigning that reference to a new variable, and now both variables point to the same array.
Here's a much simpler test which reproduces the behavior:
x = [1, 2] // the value of x is a reference to an array
y = x // the value of y is a reference to the same array
y.push(3) // modify the array pointed to by both variables
console.log(x) // [1, 2, 3]
If you want to create a new array, you need to clone the array.
With following statement
var newArray = this.testArr;
You will not copy the values from inside testArr. You will create a reference to this array. When you make an adjustment to a reference you will automatically change the referenced source too.
When you want to copy the values from testArr to another variable you can do this instead.
var newArray = this.testArr.slice();
This will copy the values from testArr inside newArray.