Passing JavaScript arrays to functions - javascript

A general question on JavaScript. If I have a function which modifies an array, such as:
var some_array = [];
function a_function(input, array){
//do stuff to array
return array;
}
In most languages:
var some_array = [];
some_array = a_function(input, some_array);
console.log(some_array);
works, however in js the below works as well:
var some_array = [];
a_function(input, some_array);
console.log(some_array);
Is this correct and how is this working?

Arrays in JS are objects and are passed into functions by value, where that value is a reference to the array. In other words, an array passed as an argument to a function still points to the same memory as the outer array.
This means that changing the array contents within the function changes the array passed from outside the function.
function f(arr) {
arr.push(1);
return arr;
}
var array = [];
// both function calls bellow add an element to the same array and then return a reference to that array.
// adds an element to the array and returns it.
// It overrides the previous reference to the array with a
// new reference to the same array.
array = f(array);
console.log(array); // [1]
// adds an element to the array and ignores the returned reference.
f(array);
console.log(array); // [1, 1]

Related

How to pass a mix of multiple Arrays and individual arg to another function and return them (GAS/JS)?

I have a main function which do bunch of operations and the output is pushed into arrays along with other multiple values declared in individual variables. Then I need to pass them to another sub function for other bunch of operations then return the updated arrays. I realized that it will not work if I pass the array name only like I pass individual variables as arguments to another function.
function mainfn(){
var sheet=SpreadsheetApp.openById("THIS_SHEET_ID").getSheetByName("wshop");
var sheetdata=sheet.getDataRange().getValues();
var lrow=sheet.getLastRow();
var var1=sheetdata[lrow-1][2];
var var2=sheetdata[lrow-1][3];
var arr1=[],arr2=[];
//some operationsA
arr1.push(1,"str1","3%",5,6,"str2"); //output OpsA being stored in array named arr1
//some operationsB
arr2.push(3,5,6,8,0,1); //output OpsB being stored in array named arr2
mysubfunction(var1,var2,arr1,arr2); // calling subfunction and passing arguments
var arr3=[],var arr4=[];
var arr3=mysubfunction()[0]; // supposedly getting the arr1 updated elements and put into arr3
var arr4=mysubfunction()[1]; // supposedly getting the arr2 updated elements and put into arr4
// do some other stuff over the array elements
}
function mysubfunction(var1,var2,arr1,arr2){
//some operationsC that updates the elements in both arrays
var result=[arr1,arr2];
return result;
}
Please help to correct my code on the passing and returning multiple arrays. Thanks
Use destructuring assignment:
var [arr3, arr4] = mysubfunction(var1,var2,arr1,arr2);// calling subfunction and getting the returned arrays
Alternatively,
var resultArr = mysubfunction(var1,var2,arr1,arr2);
var arr3 = resultArr[0];
var arr4 = resultArr[1];

How to achieve map functionality with the use of filter in JavaScript, as we can use reduce as map and filter?

Suppose I have an array var arr = [1,2,3] and if I do var result = arr.filter(callback) I want value of result would be [2,4,6] by the use of filter. I want to only define callback function in order to do so. It can be easily done with map but I want to use only filter.
Array.prototype.filter only filters existing values in an array, effectively creating a new array that can hold the same values, their subset, or an empty array, based on the filtering function.
You could do it using filter():
const arr = [1, 2, 3];
arr.filter((c, i) => {
arr[i] = +arr[i] * 2;
return true;
});
console.log(arr)
we are always returning true, so filter() makes no sense in this case.
As stated many times, there is no reason why you should do it.
It is impossible to do it like map because map returns a new array. You can either alter on the original array or you have to make a clone of the original array so you do not change it.
// this modifies the orginal array
var arr1 = [1,2,3]
arr1.filter((v,index,arr)=>{
arr[index] = v * 2;
return true
})
console.log(arr1)
// or you can clone it and do the same thing
// this modifies the cloned array
var arr2 = [1,2,3]
var arr3 = arr2.slice()
arr3.filter((v,index,arr)=>{
arr[index] = v * 2;
return true
})
console.log(arr2, arr3)
So no, you can not recreate map with filter since you HAVE to modify the original array or cheat and use a copy of the array.
So I'm not sure I understand the second part of your question, but as for the first part:
The callback for filter has three arguments, two of which are optional.
The first argument is the current element in the traversal, and the second and third arguments (the optional ones) are the 0-based index of the current element, and a reference to the original array.
This third parameter is useful for what you're trying to do.
let myArr = [1, 2, 3];
myArr.filter((el, ind, orig) => {
orig[ind] = orig[ind] + 1; // Or, whatever map logic you want to use
return true; // since a Boolean condition must be returned here
});
This way you can do it without even even having to break the scope of the function!
If you want to do it without necessarily having a variable to originally call filter on (you do have to pass an array), you can use the prototype and the call method:
Array.prototype.filter.call([1, 2, 3], (el, ind, orig) => {
orig[ind] = orig[ind] + 1;
return true;
});

Why can't i pass `push` as a foreach function?

var array = [];
document.querySelectorAll("a").forEach(array.push); //Uncaught TypeError: Cannot convert undefined or null to object
Why does this fail? what does this error mean? It seems perfectly reasonable to pass an array method as a function, what am I not understanding/seeing?
array.push loses context (its this), so you need to pass a function with context captured.
But, even if what you wanted worked - you still would not get the result you want, since NodeList:forEach passes 3 arguments to the callback function, so you would fill your array with elements, indexes and the list of nodes.
So a solution would be to do
var array = [];
document.querySelectorAll("a").forEach(e => array.push(e));
You can rebind the this context
var arr = []; document.querySelectorAll("a").forEach(arr.push.bind(arr));
console.log(arr);
one
two
three
BUT the big issue here is the fact that push() when given multiple arguments, will add all of those arguments to the array.
So push(1,2,3) will add 1, 2, and 3 to the array. So the above code would have 3 links, but it will add 9 entries into the array because forEach has 3 arguments element, index, array. So you will need to use a function to do it. Or just use Array.from() to create the array.
var arr = [];
document.querySelectorAll('a').forEach(el => arr.push(el))
console.log(arr);
var arr2 = Array.from(document.querySelectorAll('a'))
console.log(arr2);
one
two
three
The problem at first instance is not passing array.push, the problem is you are iterating over a NodeLists and this structure is not an array, you can use Array.from in next way:
const array =Array.from(document.querySelectorAll("a"));
When you extract a function from an object, it loses its context, so when you call it and it accesses this, its original value has been lost.
To fix the issue you need to use Function.prototype.bind() to keep the this reference pointing to the right object.
You can see the problem and how bind works in this example:
const obj = {
prop: 'there',
print(prefix) {
console.log(`${ prefix }: Hello ${ this.prop }.`);
}
};
obj.print('obj.print');
// Context lost:
const extractedPrint = obj.print;
extractedPrint('extractedPrint');
// Context preserved to the original object:
const bindedPrint = obj.print.bind(obj);
bindedPrint('bindedPrint');
// Context replaced:
const alsoBindedPrint = obj.print.bind({ prop: 'bro' });
alsoBindedPrint('alsoBindedPrint');
Wondering where is this pointing when it's "lost"? It points to window:
const obj = {
prop: 'there',
print(prefix) {
console.log(`${ prefix }: Hello ${ this.prop }.`);
}
};
const extractedPrint = obj.print;
window.prop = 'window';
extractedPrint('extractedPrint');
In your case, you need to make sure that when push is called by forEach its context is preserved, that is, its this value should still be referencing the original array:
links.forEach(array.push.bind(array));
Anyway, that won't work as expected because NodeList.prototype.forEach() calls its callback with 3 arguments: currentValue, currentIndexand listObj and Array.prototype.push() accepts multiple arguments at once, so you could do:
const array = [];
const links = document.querySelectorAll('a');
links.forEach(array.push.bind(array));
console.log(array.length);
<a>1</a>
<a>2</a>
<a>3</a>
But for each Node or your NodeList, you would be calling push with 3 arguments instead of 1, ending up getting some unwanted elements on the list.
To convert a NodeList to an Array you could use Array.from() instead:
console.log(Array.from(document.querySelectorAll('a')).length);
<a>1</a>
<a>2</a>
<a>3</a>
Although there are other ways to do it, like pushing all the elements one by one defining your own callback:
const links = document.querySelectorAll('a');
const arr = [];
// Note we only define a single argument, so we ignore the other 2:
links.forEach((link) => {
arr.push(link);
});
console.log(arr);
<a>1</a>
<a>2</a>
<a>3</a>
Or the the same thing using a loop:
const links = document.querySelectorAll('a');
const arr = [];
for (const link of links) {
arr.push(link);
}
// Also valid:
// for (let i = 0; i < links.length; ++i) {
// arr.push(links[i]);
// }
console.log(arr);
// Note this won't work, as NodeList has some other iterable
// properties apart from the indexes:
const arr2 = [];
for(const i in links) {
arr2.push(links[i])
}
console.log(arr2);
<a>1</a>
<a>2</a>
<a>3</a>
I'm not 100% sure what you're trying to do, but I'm going to guess that you'd like to push the elements returned by querySelectorAll into 'array'. In which case, you can't just pass the function push to forEach, you must call push on array with each element as the argument, like so:
document.querySelectorAll("a").forEach(item => array.push(item));
What are you trying to push in your example?
Maybe you are missing some parameters?
Try it out:
document.querySelectorAll("a").forEach(function(item) {
array.push(item);
});

Can't change array passed inside function

Let's say we have an array a = [1,2,3,4,5] and I want to zero it [0,0,0,0,0] by using function.
This works:
function clear (arr) {
arr.forEach((element, index) => {arr[index] = 0});
}
But this don't:
function clear2 (arr) {
arr = [...arr].fill(0);
}
Just arr.fill(0) inside clear2() works, but it's running inside Vue.js so I can't assign array elements by index, I need to reassign array var completely to remain reactivity.
Why clear2() doesn't work? Arrays as a case of Objects should be passed by reference, so what am I doing wrong?
Update: I use it as a on-click method in Vue so I can't return from it and want to pass different arrays as a parameter.
With
arr = [...arr].fill(0);
you're creating a copy of the original array then chaning that copy's values and then setting the parameter's reference value to the new array (you're not updating the parameter's inner state)
with
arr.forEach((element, index) => {arr[index] = 0});
you're actually updating the array parameter inner state
You could use fill directly, because
The fill method is a mutable method, it will change this object itself, and return it, not just return a copy of it.
function clear(array) {
array.fill(0);
}
var array = [1, 2, 3, 4];
clear(array);
console.log(array);

What gets passed to the .map() iterator function on an array?

I have a function that takes in an array of objects and a string, which represents a property. The function should return an array containing that property from each object.
Here is my sample code:
function pluck(array, property) {
var newArr = [];
array.map(function(paints){
return newArr.push(paints[property]);
});
return newArr;
}
This returns a new array and it works. But when the function is taking in an array of objects as one of the arguments...what gets passed to the anonymous iterator function in the map method? The value of the key?
How would it iterate over an array of objects using the map method?
The comments on your question explain what is happening in your code.
When you call map on an array, the mapping function is provided with 3 arguments, which are used always, sometimes and rarely in that order.
const result = items.map(function(element, index, items) {
// do the mapping
});
The function is called for each element of the original array items in turn, and the result of the function placed in the same index position of the result array.
The function arguments are:
element - this is the current element from the array
index - the current index
items - the original array on which map was called
Your pluck function could be written as:
function pluck(arr, prop) {
return arr.map(item => item[prop]);
}

Categories