Original array not getting updated when changed in referenced variable - javascript

For my work, I need to access nested object array. I do not want to access it every time with full path. So I wanted to shorten the reference by assigning the actual reference to a variable.
I tried to find out existing answers, but didn't get answer for this scenario.
What I have done:
Assigned the reference of array to a variable, modified the referenced value. But the original array is not getting modified.
Below is a demo code for what I want to achieve.
let obj = {
innerObj1: {
arr: [2,3,4,5,6]
}
}
var ref = obj.innerObj1.arr;
console.log(ref);
// output [2,3,4,5,6]
ref = ref.filter(n => n%2 == 0);
console.log(ref);
// output [2,4,6]
//Original obj
console.log(obj.innerObj1.arr)
// output [2,3,4,5,6]

This is because filter method returns a new array and you are overriding it with the new value.
As stated earlier it creates a new array, it means ref variable is not referring to old array anymore. It is referring to new array created by filter method.
You can simply use for, while or do while loop to resolve your this issue.
I hope it will help you. Please find working example here:
let obj = {
innerObj1: {
arr: [2,3,4,5,6]
}
}
var ref = obj.innerObj1.arr;
console.log(ref);
// output [2,3,4,5,6]
for(let index=0; index < ref.length; index++) {
if(ref[index] % 2 === 0) {
ref[index] = ref[index]
} else{
ref.splice(index,1);
}
}
console.log(ref);
// output [2,4,6]
//Original obj
console.log(obj.innerObj1.arr)
// output [2,3,4,5,6]

when we do
var ref = obj.innerObj1.arr;
we are having a pointer to obj.innerObj1.arr
ref is reference to array when we do ref.filter(n => n%2 == 0);
to get what we want we have to do
obj.innerObj1.arr = ref.filter(n => n%2 == 0);

Just access specific indices within ref:
let obj = {
innerObj1: {
arr: [2, 3, 4, 5, 6]
}
}
const ref = obj.innerObj1.arr;
console.log(ref);
// output [2, 3, 4, 5, 6]
for(let i = 0; i < ref.length; i++) {
ref[i] = ref[i] % 2 == 0;
}
// Original obj
console.log(obj.innerObj1.arr)
// output [true, false, true, false, true]

Related

copy array of objects

I'm having issues creating a copy of an object array. I can't get the new reference to point to a new independent array.
function OBJ1(name, tags) {
this.myname = name;
this.mytags = tags;
this.myvalue = 0;
}
function OBJ2(arg1) {
this.arg1 = arg1;
this.myarray = [];
}
var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();
for (i = 0; i < 2; i++) {
result = createarray2();
}
function createarray1() {
var myarray = [];
myarray.push(new OBJ1("NAME", [1, 2, 3]));
myarray.push(new OBJ1("others", [1, 2, 3]));
myarray.push(new OBJ1("total", [1, 2, 3]));
return myarray;
}
function createarray2() {
var newarray = $.extend(true, [], OBJ1_array); // newarray should refer to a new array, not the same one as OBJ1_array
OBJ1_array[0].myname = "CHANGED";
console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}
Output:
testscript.js:45 categories (3) [{…}, {…}, {…}]0: {myname: "CHANGED", mytags: Array(3), myvalue: 0}1: {myname: "others", mytags: Array(3), myvalue: 0}2: {myname: "total", mytags: Array(3), myvalue: 0}length: 3__proto__: Array(0)
testscript.js:46 newArray (3) [{…}, {…}, {…}]0: {myname: "CHANGED", mytags: Array(3), myvalue: 0}1: {myname: "others", mytags: Array(3), myvalue: 0}2: {myname: "total", mytags: Array(3), myvalue: 0}length: 3__proto__: Array(0)
I expected OBJ1_array[0].myname="CHANGED"; to have no effect on the newly created array newArray.
Things I've tried and didn't work:
var newArray = OBJ1_array.map(a => ({...a}));
var newarray=$.extend(true,[],OBJ1_array);
How can I solve this issue?
The $.extend documentation says the following:
Undefined properties are not copied. However, properties inherited from the object's prototype will be copied over. Properties that are an object constructed via new MyCustomObject(args), or built-in JavaScript types such as Date or RegExp, are not re-constructed and will appear as plain Objects in the resulting object or array.
This means that the array with all plain object in it will be deeply merged/copied. However objects created with the new keyword will not be reconstructed. This leaves us with the following scenario:
The array copy works just fine, however since the elements in the array are created using the new keyword they are not further merged. When altering the array itself (pushing, popping, etc.) you can see that the array is indeed a copy.
The issue here is that you access one of the elements in the array and change the object (created with the new keyword). Both arrays still point to the same object, thus when reading from the other array which hold the same object reference you will also see this change.
To resolve this issue you have to also make a copy of each object in the array. Depending on your use-case you might be able to use Object.assign or Object.create have a look at the documentation before using them blindly.
I've also created a minimal example of the problem you face to give you some better understanding of the issue.
// setup
var array1, array2, array3, array4;
function Dummy(name) { this.name = name }
// test #1 - using plain objects
array1 = [{ name: 'Foo' }];
array2 = $.extend(true, [], array1);
array1[0].name = 'Bar';
console.log(array1[0].name, array2[0].name);
// test #2 - using the `new` keyword
array3 = [new Dummy('Foo')];
array4 = $.extend(true, [], array3);
array3[0].name = 'Bar';
console.log(array3[0].name, array4[0].name);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
The problem is in your loop and OBJ1 function. first time the OBJ1_array is ok but when you come second time its valued already changed..
you can try this code
function OBJ1(name, tags) {
return {myname:name, tags:tags}
//this.myvalue = 0;
}
function OBJ2(arg1) {
this.arg1 = arg1;
this.myarray = [];
}
var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();
for (i = 0; i < 2; i++) {
let tempArr = $.extend(true, [], OBJ1_array);
result = createarray2();
OBJ1_array = tempArr;
}
function createarray1() {
let myarray = [];
myarray.push(new OBJ1("NAME", [1, 2, 3]));
myarray.push(new OBJ1("others", [1, 2, 3]));
myarray.push(new OBJ1("total", [1, 2, 3]));
return myarray;
}
function createarray2() {
let newarray =$.extend(true, [], OBJ1_array);// newarray should refer to a new array, not the same one as OBJ1_array
OBJ1_array[0].myname = "CHANGED";
console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}
Updated the answer. Easiest way to achieve what you want is to use JSON.stringify with JSON.parse to create a unlinked copy of array of objects.
const OBJ1 = (name, tags) => ({
myname: name,
mytags: tags,
myvalue: 0,
})
function createarray1() {
var myarray=[];
myarray.push(OBJ1("NAME", [1,2,3]));
myarray.push(OBJ1("others", [1,2,3]));
myarray.push(OBJ1("total", [1,2,3]));
return myarray;
}
const arr = createarray1()
// here you create a copy of array
const newArr = JSON.parse(JSON.stringify(arr))
// apply changes directly to the copy
newArr[0].myname = 'Something else'
console.log(newArr)
console.log(arr)
Arrays and Objects are reference types, which means that when you make a copy by assignment, you are simply copying the reference and not the underlying array/object. In your case, when copying the array, you copy all of the object references, which will still point to the objects in your original array. You need to clone the objects too for it to work.
Use Array.map() to iterate over your array and copy each item.
Use Object.create() to make a shallow clone of each object. This function takes a prototype and property descriptors to create a new object. You can use Object.getPrototypeOf() Object.getOwnPropertyDescriptors() to pass it the prototype and property descriptors of your input object.
function OBJ1(name) {
this.myname = name;
}
const array1 = [new OBJ1("NAME")];
const array2 = array1.map(obj =>
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
);
array2[0].myname = 'Jack';
console.log(array1[0].myname);
console.log(array2[0].myname);
I think you need a deep cloning of your object. please use below function
function clone(src) {
var ret=(src instanceof Array ? [] : {});
for(var key in src)
{
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key];
if(val && typeof(val)=='object') { val=clone(val); }
ret[key]=val;
}
return ret;
}
function OBJ1(name, tags) {
this.myname = name;
this.mytags = tags;
this.myvalue = 0;
}
function OBJ2(arg1) {
this.arg1 = arg1;
this.myarray = [];
}
var OBJ1_array = [];
var result_array2 = null;
var result;
OBJ1_array = createarray1();
for (i = 0; i < 2; i++) {
result = createarray2();
}
function createarray1() {
var myarray = [];
myarray.push(new OBJ1("NAME", [1, 2, 3]));
myarray.push(new OBJ1("others", [1, 2, 3]));
myarray.push(new OBJ1("total", [1, 2, 3]));
return myarray;
}
function createarray2() {
var newarray = clone(OBJ1_array) ; // newarray should refer to a new array, not the same one as OBJ1_array
OBJ1_array[0].myname = "CHANGED";
console.log("categories", JSON.parse(JSON.stringify(OBJ1_array)));
console.log("newarray", JSON.parse(JSON.stringify(newarray)));
}
Much Simpler Approach
var cloneOfOBJ1_array = JSON.parse(JSON.stringify(OBJ1_array));
solved cloning of an array of objects with Object.assign
const newArray = myArray.map(a => Object.assign({}, a));
or even shorter with spread syntax
const newArray = myArray.map(a => ({...a}));

JavaScript Variables Not Defining Correctly Inside Function

I'm new to JS and am trying to create a simple 'swap array elements if array A element is bigger than array B element' function. In the swapIndexes function, I don't understand why I can't define the variables as shown in the comments. For example, it works if it state arrA[c] rather than let a = arrA[c].
Why does this happen? Can anyone give some beginner tips on how best to go about something like this? My code feels verbose. Thanks for any help here.
var arrA = [0, 1, 2, 7, 6],
arrB = [0, 1, 2, 5, 7],
indexesToSwap = [],
aValuesToSwap = [],
bValuesToSwap = [],
needSwapping = false;
arrA.forEach(getSwappableIndexesAndValues);
indexesToSwap.forEach(swapIndexes);
function getSwappableIndexesAndValues(c, i) {
let b = arrB[i];
if (c > b) {
needSwapping = true;
indexesToSwap.push(i);
aValuesToSwap.push(b);
bValuesToSwap.push(c);
}
}
function swapIndexes(c, i) {
//let a = arrA[c]; fails why???
//let b = arrB[c]; fails why???
//a = aValuesToSwap[i]; fails why???
//b = bValuesToSwap[i]; fails why???
arrA[c] = aValuesToSwap[i];
arrB[c] = bValuesToSwap[i];
}
console.log(arrA);
console.log(arrB);
In javascript, when you create a variable from a given index in an array, This will create a new memory space containing a copy of the value at this index. The newly created variable will not point to the content of the array and thus, modifying this variable will not modify the content of the array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
indexesToSwap has all the information you need to swap. The swap value arrays (aValuesToSwap, bValuesToSwap) are greatly complicating matters and are wholly unnecessary.
Regardless of the values to swap arrays, swapping is a fundamental operation and typically involves a simple temporary, e.g.
temp = arrA[i];
arrA[i] = arrB[i];
arrB[i] = temp;
Discarding the complexities, here's an alternative to the function getSwappableIndexesAndValues
function getSwappableIndexes(c, i) {
if (c > arrB[i])
indexesToSwap.push(i);
}
And a simplified swap function
function swapIndexes(c, i) {
let temp = arrA[c];
arrA[c] = arrB[c];
arrB[c] = temp;
}
I have to say further though that the use of Array.forEach wildly complicates the entire solution. Unless this is an assignment, using a simple for-loop is best here.
// swaps values between arrays where the value in
// array a is greater than the value in array b
//
function swapIfGreaterThan(a,b) {
for(let i = 0; i < a.length && i < b.length; i++) {
if(a[i] > b[i]) {
let temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
}
var arrA = [0, 1, 2, 7, 6],
arrB = [0, 1, 2, 5, 7],
indexesToSwap = [],
aValuesToSwap = [],
bValuesToSwap = [],
needSwapping = false;
arrA.forEach(getSwappableIndexesAndValues);
indexesToSwap.forEach(swapIndexes);
function getSwappableIndexesAndValues(c, i) {
let b = arrB[i];
if (c > b) {
needSwapping = true;
indexesToSwap.push(i);
aValuesToSwap.push(b);
bValuesToSwap.push(c);
}
}
function swapIndexes(c, i) {
//let a = arrA[c]; fails why???
//let b = arrB[c]; fails why???
//a = aValuesToSwap[i]; fails why???
//b = bValuesToSwap[i]; fails why???
arrA[c] = bValuesToSwap[i];
arrB[c] =aValuesToSwap[i];
console.log( arrA[c], arrB[c]);
console.log( aValuesToSwap[i], bValuesToSwap[i]);
}
console.log(arrA);
console.log(arrB);
It is not possible array values and primitive data type values are different. If you try with the array of the object your attempt will be correct.

How to delete elements from an array from different indexes by single method?

I am trying to write the logic to delete element by passing indexArr(that contains indexes to delete) to a method:
var a = [0,1,2,3,4,5];
Array.prototype.removeElem = function(indexArr){
this.filter(function(value,index,arr){
return indexArr.indexOf(index)>-1 ? arr[index] = undefined:false
}
});
}
a.removeElem([2,3]); //passing indexes in form of array
console.log(a.join('').split('')); //removing undefined values
As you can see, I am removing undefined values after the execution of removeElem() method, but I want to do that in the method itself.
To modify an original array, you can use while() loop from last to first index combined with splice() method:
var a = [0,1,2,3,4,5];
Array.prototype.removeElem = function(indexArr){
var length = this.length;
while(length--) {
if(indexArr.indexOf(length) > -1) {
this.splice(length, 1);
}
}
}
console.log(a.removeElem([0,1]));
console.log(a);
No need to use Array.filter(). Try something like this :
let a = [0, 1, 2, 3, 4, 5];
Array.prototype.removeElem = function(indexArr) {
let removedElem = 0;
for (let i = 0; i < indexArr.length; i++) {
this.splice(indexArr[i] - removedElem, 1)
removedElem += 1;
};
return this;
}
console.log(a.removeElem([2, 3]));
simple :)
EDIT:
var a = [0,1,2,3,4,5];
Array.prototype.removeElem = function(indexArr){
indexArr.sort();
i=indexArr.length;
while(i--)
{
a.splice(indexArr[i],1);
}
}
a.removeElem([2,3]); //passing indexes in form of array
console.log(a);
you can make use of reduce for this.
check the following code snippet
var a = [0, 1, 2, 3, 4, 5];
Array.prototype.removeElem = function(indexArr) {
return this.reduce((result, key, index) => {
if (indexArr.indexOf(index) === -1) {
result.push(key)
}
return result
}, [])
}
var result = a.removeElem([2, 3]); //passing indexes in form of array
console.log(result.join('').split(''));
Hope it helps
use splice method to add and remove element from array

recursively putting array elements in their own array

I'm trying to create a function that puts each array element in its own array, recursively.
I think my base case is correct, but my recursive call doesn't appear to be working. any insight?
function ownList(arr){
if (arr.length === 1) {
arr[0] = [arr[0]];
return;
} else {
return arr[0].concat(ownList(arr.slice(1)));
}
}
var arr = [1,2,3]
console.log(ownList(arr))// returns []
//should return [[1],[2],[3]]
Here I'm trying to put each pair in it's own list (recursive only). This code below is correct (update)
function ownListPair(arr){
if (arr.length === 0)
return arr;
else if(arr.length === 1)
return [[arr[0], 0]];
else
return [[arr[0], arr[1]]].concat(ownListPair(arr.slice(2)));
}
// var arr = [3,6,8,1,5]
var arr = [2,7,8,3,1,4]
//returns [ [ 2, 7 ], [ 8, 3 ], [ 1, 4 ]]
console.log(ownListPair(arr))
I prefer this solution for several reasons:
function ownList(a) {
return a.length == 0
? []
: [[a[0]]].concat(ownList(a.slice(1)))
}
It's shorter and more concise
It works for empty arrays as well
The actual wrapping happens only once in the last line. Treating length == 1 separately -- as suggested by others -- is not necessary.
It would more appropriate to make a length of 0 be the null case. Then you just have to get the brackets right. The thing on the left side of the concat should be an array consisting of the array containing the first element.
function ownList(arr) {
return arr.length ? [[arr[0]]].concat(ownList(arr.slice(1))) : [];
}
Here's an alternative, take your pick:
function ownList(arr) {
return arr.length ? [[arr.shift()]] . concat(ownList(arr)) : [];
}
Using a bit of ES6 magic for readability:
function ownList([head, ...tail]) {
return head === undefined ? [] : [[head]] . concat(ownList(tail));
}
Here the [head, ...tail] is using parameter destructuring which pulls the argument apart into its first element (head) and an array of remaining ones (tail).
Instead of concat you could also use the array constructor:
function ownList([head, ...tail]) {
return head === undefined ? [] : Array([head], ...ownList(tail));
}
I think your basic assumption is wrong. What you need to do is check if each item in the array is an array, if not just add the item to the new array, if so have the function run itself on the array item.
That is recursion.
This code does that kind of recursion...
function ownList(arr)
{
var newArr = [];
var length = arr.length;
for (var i = 0; i < length; i++) {
if (typeof(arr[i]) === 'object') {
newArr.push(ownList(arr[i]));
continue;
}
newArr.push([arr[i]]);
}
return newArr;
}
var arr = [1, 2, 3];
console.log(ownList(arr));
Would something like this work:
var arr = [1, 2, 3, ["a", "b", "c", ["str"]]],
result = [];
function flatten(input){
input.forEach(function(el){
if(Array.isArray(el)){
flatten(el)
}else{
result.push([el]);
}
});
}
flatten(arr);
console.log(JSON.stringify(result));
//[[1],[2],[3],["a"],["b"],["c"],["str"]]
JSBIN
Edit:
var result = [];
function flatten(input){
if (input.length === 0){
console.log( "result", result ); //[[1],[2],[3],["a"],["b"],["c"],["str"]]
return;
}
//if zeroth el of input !array, push to result
if (!Array.isArray(input[0])){
result.push(input.splice(0, 1));
flatten(input);
}else{
flatten(input[0]); //else, give input[0] back to flatten
}
}
window.onload = function(){
var arr = [1, 2, 3, ["a", "b", "c", ["str"]]];
flatten(arr);
}
JSBIN
After struggling through this today, turns out that this works :)
function ownList(arr){
//base case:
if (arr.length === 1) {
return [arr];
}
//recurse
//have to do two brackets here --> (arr.slice(0,1)) since length > 1
return [arr.slice(0,1)].concat(ownList(arr.slice(1)));
}
var arr = [1,2,3]
console.log(ownList(arr))// returns [[1],[2],[3]]

Recognize the last iteration in a Javascript object

I have an object that I'm iterating
for (el in object) {
// Some work here
}
I want to know when is the last iteration, inside the iteration, so I can do
for (el in object) {
// Some work here
if (last_iteration) {
// Do something
}
}
Any straightforward way to do it?
I know I'm late but I just ran into this and fixed it like this:
let i = 0;
const object = { a: 1, b: 2 };
const length = Object.keys(object).length;
for (el in object) {
const last = i === length - 1; // true if last, false if not last
console.log(i, el, last);
i++;
}
Update: A few years later, i++ at the end of a loop really irks me.
const object = { a: 1, b: 2 };
const length = Object.keys(object).length;
for (const [key, isLast] of Object.keys(object)
.map((key, i) => [key, i === length - 1])) {
console.log(key, isLast);
}
or
const object = { a: 1, b: 2 };
const length = Object.keys(object).length;
Object.keys(object)
.map((key, i) => [key, i === length - 1]))
.map(([key, isLast]) => {
console.log(key, isLast);
})
You can do something like this:
var first = true;
var prev;
for (var el in object) {
// Some work here
if (first) {
first = false;
} else {
doSomething(prev, object[prev]);
}
prev = el;
}
if (prev !== undefined) { // There was at least one element
doSomethingElse(prev, object[prev]); // Prev is now last of all elements
}
This is in case you want to process all but the last element in one way (doSomething) and process the last element in another way (doSomethingElse).
If you want to process all the elements in one way (doSomething) and want to have extra processing for the last element only (doSomethingExtra), you can do:
var prev;
for (var el in object) {
// Some work here
doSomething(el, object[el]);
prev = el;
}
if (prev !== undefined) { // There was at least one element
doSomethingExtra(prev, object[prev]); // Prev is now last of all elements
}
To make it even shorter, you can do similar to what Török Gábor did in the gist he provided, by reusing el variable, i.e.:
var el;
for (el in object) {
// Some work here
doSomething(el, object[el]);
}
if (el !== undefined) { // There was at least one element
doSomethingExtra(el, object[el]); // El is now last of all elements
}
Hope this helps.
If the keys are not numerical, this works:
let anObject = {'one': 1, 'two': 2, 'three': 3, 'lastKey': 4};
let objectKeys = Object.keys(anObject);
let lastObjectKey = objectKeys.slice(-1).toString();
console.log(lastObjectKey); // 'lastKey'
The Object.keys() method returns an array of a given object's own enumerable property names, iterated in the same order that a normal loop would.
Example with numerical keys causing reordering:
let anObject2 = {3: 3, 2: 2, 'notLastKey': 4, 1: 'lastKey'};
let objectKeys2 = Object.keys(anObject2);
console.log(objectKeys2); // ["1", "2", "3", "notLastKey"]
let lastObjectKey2 = objectKeys2.slice(-1).toString();
console.log(lastObjectKey2); // "notLastKey"
Note that this will only work if the object you are iterating over is an array (has numeric keys)
var a = [1,2,3,4,5];
for (i in a) {
if(a[+i+1] === undefined)
console.log('the last one is: ' + a[i]);
}
Note that the + sign before i is necessary since if omitted, it will do a string concatenation, the keys resulting in 01, 12, 23, etc
as said already, there is no distinct order for properties, so last enumerated property is only known afterwards.
var object = { a: 'b', c: 42 };
for ( var string in object ) ;
alert( object[string] ); // last property name is still here

Categories