I am puzzled why this following bit of code will return mutations of both the local and global array:
var globalarray = [1,2,3];
function test(){
let localarray = globalarray;
localarray.push(4);
console.log(localarray);
console.log(globalarray);
}
setInterval(test, 2000);
Returns:
[1,2,3,4] for both
My impression was that localarray would be a copy of globalarray. I saw another answer that said in order to make a copy of an array you need to use .slice().reverse(), which seems like a workaround. Why does it not just create a new local copy? Is there a simple and efficient way to make a local copy of a global array? Otherwise it seems like making multiple mutations to a global array is terrible for performance.
The reason for this in your code is because you are simply telling your test function to point to the globalarray with the = operator. This is because in JavaScript, variable assignments do not inherently "copy" objects into the new variables; this might seem confusing, so just think of the = operator as a sign that points your code to the location of an object.
The only times that the = operator is making new copies is when you are working with primitive types. In those cases, you cannot inherently change what those objects are, so = is sufficient to make a copy like you would expect.
The reason for the .slice().reverse() is to work around the problem you are seeing. Another way you could do this is by using let localarray = globalarray.map(e => e), or as samee suggested, by using let localarray = [...globalarray]. The .map operator takes the function given to it as the first argument and applies it to every element, and then it stores the result in a different array at another location that is not the same as globalarray.
Keep in mind that these methods, and any others that might be suggested to you, are shorthand ways of doing
let localarray = new Array(globalarray.length);
for (let i = 0; i < globalarray.length; i++) {
localarray[i] = globalarray[i];
}
// localarray can now be freely modified because it does not point to the same array as globalarray
Also keep in mind that if you need to also create copies of the elements inside of the arrays, then you will have to use more comprehensive copying code. There are libraries that can do this sort of heavy-duty copying if you really need it.
In JavaScript (as in many other languages), objects are passed by reference. Arrays are also passed by reference (because an array is actually a type of object). So when you say: let localarrray = globalarray, you are actually setting localarray to a pointer that resolves to globalarray.
There are several strategies for obtaining a true copy. If you're trying to get a fresh copy of the original array, the Array prototype function of .map() is one of the most targeted tools for the job. It would look like this:
let localarray = globalarray.map(element => element);
Simple way to clone an array is just
let localarray = globalarray.slice();
I do it a different way to deep cloning:
clone: function() {
var clone = undefined;
var instance = this;
if ( XScript.is.xobject(instance) ) {
clone = {};
for ( var prop in instance ) {
if ( instance.hasOwnProperty(prop) ) {
var p = instance[prop];
if ( XScript.is.xobject(p) ) p = p.clone();
clone[prop] = p;
}//END IF this
}//END FOR prop
return clone;
}//END IF xobject
if ( XScript.is.xarray(instance) ) {
clone = instance.slice(0);
return clone;
}
return clone;
}//END FUNCTION clone
This clone will require you attaching the clone object to the object prototype and check to see if its an array, object, or other. I am not changing the function to fit all your needs because one should learn to somewhat how to change it and what to do instead of copy pasta. Plus it is just an example.
Related
I've attempted to define a randomize method to Array.prototype like so :
Array.prototype.randomize = function() { // Yes, this method does cause the array to have 'holes', but its good enough for this example/demonstration
var r = new Array(this.length); // Possible but somewhat unwanted to randomize the array in its place, therefore it has to be set to this new array later on.
this.forEach(function(e) {
r[Math.floor(Math.random() * r.length)] = e;
});
// how do I set 'this' to the variable r? (In order to change the array to the new and randomized array 'r')
return r;
}
This method does return the randomized array, but how do I change the array itself as well?
As the comments say, changing an array in place in place is a better way to shuffle.
But if you did need to replace all of the elements in one go, you could use Array#splice:
Array.prototype.randomize = function() {
var r = /* the algorithm to get a replacement array... */;
this.splice(0, r.length, ...r);
return this;
}
... is the spread operator. It's part of ES2015.
Spread operator compatibility table
not possible to randomize the array in its place
Wrong.
how do I set 'this' to the variable array 'r' in order to change the array to the new one?
That's impossible in JavaScript. You cannot overwrite an object, not via the this reference and not via a normal variable, you have to actually mutate its properties. If you want to overwrite the variable in which the array reference is stored, you need to explicitly assign to that variable; you cannot do it via a method (since JS does not allow pass-by-reference) and you can only overwrite this reference, not all variables that might contain it.
I have the following code.When I access w[1][0], I want only the object at that one location to be changed. However, all the objects change instead. I'm assuming this is because at some level, they are all pointing to the same object. How would I fix this?
var createArray = function(dim,init){//takes an array of dimensions and the initial value of each element
if(dim.length > 0){
var x = new Array();
for(var i = 0; i < dim[0]; i++)
x[i] = createArray(dim.slice(1),init)
return x;
}
return init;
}
var w = createArray([2,2],{top: false,left: false});
console.log(w);
w[1][0].left = true;
console.log(w);
In JavaScript, objects are passed by a reference, not by value. That means that if you pass the same object to a few variables, they all point to the same place in memory. This is why your init object is being changed everywhere.
To prevent this, you need to clone the object before assigning it to your variables. One of the simplest built-in methods is to use JSON, like this:
var copy = JSON.parse(JSON.stringify(original));
So in your case that would be it:
x[i] = createArray(dim.slice(1), JSON.parse(JSON.stringify(init)));
w[1][0].left = true;
This line is changing the only instance of the object. See, this line:
var w = createArray([2,2],{top: false,left: false});
is the only line in your code that creates a new object (of the type you're interested in) via the {} literal there. All other set operations (newVar = init) only copy a reference to that same object.
That sounds annoying, but it has a lot of good uses in code. To fix it, you'll want to create a copy each time, to replace the final line of createArray
var myCopy = {};
for (var key in origObject) {
if (origObject.hasOwnProperty(key)) {
myCopy[key] = origObject[key];
}
}
return myCopy;
Be warned - this is a "shallow copy", so it will only copy the top-level properties, and any objects a level deep will still be references. Many JavaScript libraries have functions to create deep copies for you.
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
How can I manipulate the prototype of a predefined object (for example an Array) so that it does something when creating instances of that object?
Simply I want to alert('an array was created!') whenever an Array is instantiated.
You can set a new method on an array by adding it to the Array.prototype object:
Array.prototype.fizz = function () {
alert('works');
};
var arr = [];
arr.fizz();
However, this only allows you to create new methods, this does not allow you to extend existing methods*, nor does it allow you to override the Array constructor.
Be careful adding new methods to existing types. This can adversely affect the entire scripting environment, and cause unexpected behavior in some libraries. Although some might call it "bad practice", it's quite common to use polyfills for cross-browser compatibility, such as by creating Array.prototype.indexOf.
There is no such thing as a "newed" array, the word is "instantiated":
var a1, a2;
a1 = []; //a1 was instantiated with a new Array
a2 = new Array(); //a2 was also instantiated with a new Array
There is no cross-browser means of overriding the Array constructor.
* it's possible to wrap an existing method in a function so that, when called, the existing method performs its original functionality, in addition to the new functionality. Although this might be referred to as extending an existing method, it is in fact creating a new method.
You can try to override Array with your own function. It seems to work when doing new Array, but not when doing [].
(function() {
var _ac = Array;
Array = function() {
alert('an array was newed!');
return _ac.apply(this, arguments);
};
}());
DEMO: http://jsfiddle.net/DAg9A/
I would suggest the you just create an array namespace for it:
array = {};
array.create = function() {
alert("Created an array");
return [];
}
So whenever you create an array you use: array.create(); .
You should not, and in this case can not, change native functionality. You have to be in charge of every array creation.
This is how I do it now. It feels like a hazzle...
var broken_posts = new Object();
broken_posts.empty = new Array();
broken_posts.one = new Array();
var broken_posts = { empty: [], one: [] };
var broken_posts = {
empty: [],
one: []
};
The answers by Andru and Thilo are both correct, but perhaps some information on why is in order:
Avoid direct calls to the Array constructor: it's confusing and misleading. var a = new Array(5); returns [undefined,undefined,undefined,undefined,undefined], whereas var b = new Array('5'); returns ['5']. Or even var c = new Array(5,5); => [5,5].
Same applies for the direct object constructor. There's really no reason to create an object calling the basic object constructor. The only times you should use the keyword new is when creating a date object, or calling a self-made constructor function (and even in that case, there's no real need for the new keyword, there are alternative design patterns). Using the object literal {} is more common, allows for direct assignment of properties (and even methods). Besides, It's so much easier to create your objects in a JIT sort of way. Not only does it require less lines of code, with correct use of closures, or just for the one call, an object can get GC'ed once you're finished with it.
function iWantAnObject(obj)
{
if (obj instanceof Object)
{
return true;
}
return false;
}//function returns, obj is GC'ed
iWantAnObject({just:'some',rand:'Object',withA:function(){console.log('a method';}});
As opposed to this scenario:
var tempObj = new Object();
tempObj.just = 'some';//etc...
//etc...
iWantAnObjct(tempObj);
//tempObj isn't yet GC'ed
In the last example chances are: you accidentally create global variables, have various objects in memory that are no longer required, and, last but not least: isn't very in tune with the prototypal nature of JS.
I would prefer to use CoffeeScript instead.
Then you can do it this way:
broken_posts =
empty: []
one: []