I have a foreach loop where I create a new temp array, then run a nested foreach loop. I'm then trying to access the temp array inside the nested foreach loop, but it's coming back with a "variable not available" error.
let final = {
array: []
};
myArray.forEach(item =>
{
let newObject = { items: [] };
item.subArray.forEach(subItem =>
{
var subObject = { prop: subItem.prop };
// Error here: "newObject is not available"
newObject.items.push(subObject);
});
// Error here: "final is not available"
final.array.push(newObject);
});
I know I can provide this to the array by providing it as an argument (eg: item.subArray.forEach(subItem => {},this);)
but this doesn't help me because tempArray doesn't exist at the class level.
I have the same problem when I try to assign my temp array to the "final" array declared outside the foreach.
Is there a way I can access the parent scope from within the foreach?
I should point out this code exists within a function defined on a class. I'm basically trying to aggregate properties with a certain value from within the subarray
Screenshot showing the issue: http://i.imgur.com/HWCz0Ed.png
(The code visible in the image is within the first forEach loop)
Update: I figured this out, it was an issue between using let and var. See my answer below for details.
The code you pasted into the question must not be your real code. if it was you would have had no problem accessing finalArray.
The results of both your snippets are very different.
the first will give you an array of all the properties of the sub item of the last item.
the seconds will give you an array of arrays where each array contains the properties of the sub item
If I understand correctly what you want is to get a single array containing all the properties of all the sub items. so you want to map each item to an array of sub-item properties and then flatten the result into a single array.
How about this?
var items = [
{subitems:[
{prop:1},
{prop:2},
]},
{subitems:[
{prop:3},
{prop:4},
]},
]
var result = items.map(function(item){
return item.subitems.map(function(subitem){
return subitem.prop;
})
}).reduce(function(prev,curr){
return prev.concat(curr);
},[]);
console.log(result);
Update: I finally figured this out. In my actual code I was creating newObject using TypeScript's let keyword. I changed it to var instead and it started working.
Chalk that up to me not understanding the difference between let (block scope) and var (global scope) - d'oh!
The solution listed below also worked for me, but simply changing let to var has meant that my original code works perfectly.
I solved this by using map() instead of forEach():
var final = {
array: []
};
var finalArray = myArray.map(function (item)
{
let newObject = { items: [] };
var tempArray = item.subArray.map(function (subItem)
{
var subObject = { prop: subItem.prop };
return subObject;
});
newObject.items = tempArray;
return newObject;
});
final.array = finalArray;
Related
Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
I don't know if this has already been proposed or asked before; Google searches returned only a myriad number of questions related to the current functionality of Array.push().
Here's an example implementation of this functionality, feel free to correct it:
;(function() {
var _push = Array.prototype.push;
Array.prototype.push = function() {
return this[_push.apply(this, arguments) - 1];
}
}());
You would then be able to do something like this:
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray.push({}));
Where someFunction modifies the object passed in as the second parameter, for example. Now the contents of someArray are [{"someKey": "hello world"}].
Are there any drawbacks to this approach?
See my detailed answer here
TLDR;
You can get the return value of the mutated array, when you instead add an element using array.concat[].
concat is a way of "adding" or "joining" two arrays together. The awesome thing about this method, is that it has a return value of the resultant array, so it can be chained.
newArray = oldArray.concat[newItem];
This also allows you to chain functions together
updatedArray = oldArray.filter((item) => {
item.id !== updatedItem.id).concat[updatedItem]};
Where item = {id: someID, value: someUpdatedValue}
The main thing to notice is, that you need to pass an array to concat.
So make sure that you put your value to be "pushed" inside a couple of square brackets, and you're good to go.
This will give you the functionality you expected from push()
You can use the + operator to "add" two arrays together, or by passing the arrays to join as parameters to concat().
let arrayAB = arrayA + arrayB;
let arrayCD = concat(arrayC, arrayD);
Note that by using the concat method, you can take advantage of "chaining" commands before and after concat.
Are there any substantial reasons why modifying Array.push() to return the object pushed rather than the length of the new array might be a bad idea?
Of course there is one: Other code will expect Array::push to behave as defined in the specification, i.e. to return the new length. And other developers will find your code incomprehensible if you did redefine builtin functions to behave unexpectedly.
At least choose a different name for the method.
You would then be able to do something like this: someFunction(value, someArray.push({}));
Uh, what? Yeah, my second point already strikes :-)
However, even if you didn't use push this does not get across what you want to do. The composition that you should express is "add an object which consist of a key and a value to an array". With a more functional style, let someFunction return this object, and you can write
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
return obj;
}
someArray.push(someFunction(value, {}));
Just as a historical note -- There was an older version of JavaScript -- JavaScript version 1.2 -- that handled a number of array functions quite differently.
In particular to this question, Array.push did return the item, not the length of the array.
That said, 1.2 has been not been used for decades now -- but some very old references might still refer to this behavior.
http://web.archive.org/web/20010408055419/developer.netscape.com/docs/manuals/communicator/jsguide/js1_2.htm
By the coming of ES6, it is recommended to extend array class in the proper way , then , override push method :
class XArray extends Array {
push() {
super.push(...arguments);
return (arguments.length === 1) ? arguments[0] : arguments;
}
}
//---- Application
let list = [1, 3, 7,5];
list = new XArray(...list);
console.log(
'Push one item : ',list.push(4)
);
console.log(
'Push multi-items :', list.push(-9, 2)
);
console.log(
'Check length :' , list.length
)
Method push() returns the last element added, which makes it very inconvenient when creating short functions/reducers. Also, push() - is a rather archaic stuff in JS. On ahother hand we have spread operator [...] which is faster and does what you needs: it exactly returns an array.
// to concat arrays
const a = [1,2,3];
const b = [...a, 4, 5];
console.log(b) // [1, 2, 3, 4, 5];
// to concat and get a length
const arrA = [1,2,3,4,5];
const arrB = [6,7,8];
console.log([0, ...arrA, ...arrB, 9].length); // 10
// to reduce
const arr = ["red", "green", "blue"];
const liArr = arr.reduce( (acc,cur) => [...acc, `<li style='color:${cur}'>${cur}</li>`],[]);
console.log(liArr);
//[ "<li style='color:red'>red</li>",
//"<li style='color:green'>green</li>",
//"<li style='color:blue'>blue</li>" ]
var arr = [];
var element = Math.random();
assert(element === arr[arr.push(element)-1]);
How about doing someArray[someArray.length]={} instead of someArray.push({})? The value of an assignment is the value being assigned.
var someArray = [],
value = "hello world";
function someFunction(value, obj) {
obj["someKey"] = value;
}
someFunction(value, someArray[someArray.length]={});
console.log(someArray)
I am working on project where I need to maintain an array from json data returned from API, json can have tree, I have following code which is working fine but I wan to remove if conditions before assigning values to array elements
// data contains json
let newArray = []
for(let d in data){
for(let x in data[d]){
if(typeof(newArray[d]) === 'undefined'){
newArray[d] = []
}
if(typeof(newArray[d][data[d][x]['id']]) === 'undefined'){
newArray[d][data[d][x]['id']] = []
}
newArray[d][data[d][x]['id']]['price'] = data[d][x]['price']
newArray[d][data[d][x]['id']]['discount'] = data[d][x]['discount']
}
}
In above code I have to check the array first and declare it as array if its not otherwise it returns undefined error, is there any way to get rid of there conditions and extend array as per requirement ?
You can you new ES6 spread operator like this
newAraay[d] = [...newArray,...Array(data[d][x]['id']),[...Array('price',data[d][x]['price'])]]
Like here in this snippet I am directly doing abc[1][3][4] = "new value" without explicitly initialising them
let abc = [];
abc[1]= [...abc,...Array(3),[...Array(4),'new inserted value']]
console.log(abc);
newArray[d] = newArray[d] || []
You can understand this operation in this post
Or use Lodash Library
https://lodash.com/docs/4.17.11#set
First, I am adding two same rows in array and later I need to modify only the last one, adding new property to it. The way I do that:
for(var index in arrayOne) {
var arrayOneItem = arrayOne[index];
var new_row = {
address: arrayOne[index].address,
date: arrayOne[index].date,
category: arrayOne[index].category,
};
rows.push(new_row);
if(arrayOne[index].refund_status == 'refunded') {
rows.push(new_row);
rows[rows.length - 1].refund_status = 'refunded';
}
}
But the problem is that the code inside if statement does not only modify last row, but also the one before it, so the refund_status = 'refunded' is added both to the last and one before last row. Why is this happening and is there a way to modify the last row only?
When you are using the same object twice it's best to create a copy (shallow in this case) using Object.assign(). This will avoid referencing the same object from multiple variables or array indexes in your case.
eg.
rows.push(new_row);
becomes
rows.push(Object.assign({}, new_row));
This is because the object you push into the array is passed by reference and not by value, thus when you change the original object you will change both references to it in the array, see example below:
let someArray = [];
let someObj = {foo: "bar"};
someArray.push(someObj);
someArray.push(someObj);
someArray[0].foo = "baz";
console.log(someArray[1]);
To avoid this, you would need to clone the values of the object to create a new one. This question has some ways to do so, using JSON.parse and JSON.stringify is the shortest way to deep-copy an object without an external library, see example below:
let someArray = [];
let someObj = {foo: "bar"};
someArray.push(someObj);
let newObj = JSON.parse(JSON.stringify(someObj));
someArray.push(newObj);
someArray[0].foo = "baz";
console.log(someArray[1]);
Because you are changing property of an object, and object in javaScript is accessed through a link not a separate instance. In another words, you have the same object in memory and you change its property. It means, new_row is object you create and push it several times, and it's the same.
You can avoid it by copying it when pushing second times:
if(arrayOne[index].refund_status == 'refunded') {
rows.push({ ...new_row });
rows[rows.length - 1].refund_status = 'refunded';
}
where { ...new_row } basically creates new copy.
When you do rows[rows.length - 1].refund_status = 'refunded'; only second last will change.
Another solution i'd suggest is a bit more accurate:
const rows = []; // empty
const arrayOne = []; // SOME DATA HERE as I understand
const refundedStatus = ;
arrayOne.forEach(element=> {
rows.push(element);
if (value.refund_status === 'refunded') {
rows[rows.length].refund_status = 'refunded';
rows.push({ ...element});
}
});
I am running eslint and it is recommended to return a value whenever an arrow function(lambda function) is used. Well that makes sense. However, I come across a case that is hard to walk around.
Dict = {}
Instances = [/* an array of items where items is a dictionary that contains data */]
Instances.map((item) => {
Dict[item.name] = item.url;
});
My goal is to get the data from the Instances array and fill the dictionary Dict with it. I am using the array function to assign key value pair to the dictionary, but that violates the rule of the arrow function.
Is there any iteratools or functions other than map that would help me to achieve the goal, and avoid the rule violation?
Edit: This does not adhere to Airbnb's ES6 Style Guide.
My goal is to get the data from the Instances array and fill the dictionary with it.
Use .reduce
.. and just pass an empty object as the accumulator, filling it up as you iterate through your array.
const instances = [
{ name: 'foo', url: 'https://google.com' },
{ name: 'bar', url: 'https://stackoverflow.com' }
]
const result = instances.reduce((dict, item) => {
dict[item.name] = item.url
return dict
}, {})
console.log(result)
Why not .map?
Array.map always returns a new Array and is meant for mapping each array element to another format.
If your resulting data structure shouldn't be an Array, with the same length as the Array you are operating on, you should avoid using it.
Why .reduce instead of .forEach?
I use forEach only for doing "work" rather than transforming data. Transforming data is almost always achievable with just map and/or reduce.
Here's what I mean by "work":
const users = [userInstance, userInstance, userInstance]
users.forEach(user => user.sendEmail('Hello World'))
Use forEach instead of map.
The point of map is to modify each item in an array and put the modified versions in a new array.
forEach just runs a function on each item.
If you are looking for ES6 solution to fill dictionary object this could help and should pass ESLint also:-
const dict = Instances.reduce((map, obj) => (map[obj.name] = obj.url, map), {});
update
const dict = Instances.reduce((map, obj) => {
let mapClone = {};
mapClone = Object.assign({}, map);
mapClone[obj.name] = obj.url;
return mapClone;
}, {});
I'm pretty new to javascript but I'm trying to push a specified number of objects to an array using the following code. When I check the console I see only one object is pushed to the array. What should I be doing differently? Thanks!
var albums = {};
function collection(numberOfAlbums) {
array = [];
array.push(albums);
return array;
};
console.log(collection(12));
From your code:
array.push(albums);
would add the same object each time (assuming you had added a loop) which isn't what you want.
This will add a new empty object for each iteration of numberOfAlbums:
function collection(numberOfAlbums) {
for (var array = [], i = 0; i < numberOfAlbums; i++) {
array.push({});
}
return array;
};
Here's another way using map. Array.apply trick from here.
function collection(numberOfAlbums) {
var arr = Array.apply(null, Array(numberOfAlbums));
return arr.map(function (el) { return {}; });
};
I could give you the code but that is not learning. So here are the steps:
use numberOfAlbums as an argument in a function.
create an empty array.
use numberOfAlbums in for-loops in that for-loops push albums. ==array.push(albums)== do not use {}curly brackets around albums.
return the array.