Native way to merge objects in Javascript - javascript

Javascript's Object doesn't have any native merge operation. If you have two objects, say
{a:1, b:2}
{c:3, d:4}
And want to get
{a:1, b:2, c:3, d:4}
As far as I know, you have to iterate through the objects. That is to say that you decide on either a merge left or merge right strategy and then you do something like (simplified)
for (key in object2) {
object1[key] = object2[key];
}
This is fine. However, Javascript has the call and prototype feature. For instance, turning arguments into an Array can be done with
Array.prototype.slice.call(arguments)
This approach exploits existing native code, and so therefore is less susceptible to programmer folly and should run faster than a non-native implementation.
The question
Is there a trick to use this prototype/call pattern on perhaps the Attribute or Node traversal features of the DOM, or perhaps some of the generic String functions in order to do a native object merge?
The code would look something like this:
var merged = somethingrandom.obscuremethod.call(object1, object2)
And as a result, you'd get a native merge without a traversal.
A possible, sub-optimal solution
If you could use the constructor property of an Object and then coerce one object to have a constructor of another object and then run new over the composite object, you may get a merge for free. But I don't have a firm grasp of the full implications of the constructor feature in javascript to make this call.
Lemma
The same question holds true for Arrays. A common problem is to take, say 7 arrays of numbers, then try to find out the intersection of those arrays. That is to say, which numbers exist in all 7 arrays.
You could concat them together, then do a sort, and then do a traversal, surely. But it would be nice if there is a generic intersect tucked away somewhere that we can coerce an array to doing natively.
Any thoughts?
edit:
Getting half way there
For the array problem, you could do the following:
array.concat(a, b, c).sort().join(':') and then use some tricky RegExp capture and repeat patterns in order to traverse. RegExp implementations, if you don't know, run on a very simple stack-based virtual machine. When you initialize your regular expression that's really a program that gets compiled (RegExp.compile is a deprecated JS method). Then the native runs over the string in a blisteringly fast way. Perhaps you could exploit that for membership thresholds and get better performance...
It still doesn't go all the way though.

My answer to this will be disappointing, but still:
no
The reason for this is simple: Mr Resig's implementation of merge (or "extend" as it's called for objects) in jQuery is doing a loop, just like the one in your question. You can look at it here. And I dare say that if John Resig hasn't found a clever build-in way to do it, then the mere mortals of stackoverflow won't either :)

Using ES6 (ES2015) you can use Object.assign method:
var x = {a:1, b:2};
var y = {c:3, d:4};
var z = Object.assign({},x,y);
Using ES7 (ES2016, Chrome 60+ or Babel) you can use Object spread operator:
var x = {a:1, b:2};
var y = {c:3, d:4};
var z = {...x, ...y};

The million dollar question! I've tried doing this numerous ways, and the loop way described above always seemed the dirtiest. ES6's Object.setPrototypeOf() allows you to delegate a "property override" object to a "default properties" object, pretty much accomplishing what you're trying to do, but using Object.setPrototypeOf() has some serious implications, like disabling the browser's compiler optimizations for the whole script.
Also, in both the loop solution and the Object.setPrototypeOf() solution, you are left with a situation where the "property override" object can mutate the "default properties" object:
defaultObj = {
a: [1, 2]
}
...
overrideObj = {
b: 3
}
Object.setPrototypeOf(overrideObj, defaultObj);
console.log(overrideObj); // {a: [1, 2], b: 3}
// Great!
...
overrideObj.a.push(4);
console.log(defaultObj); // {a: [1, 2, 4]}
// Uh-oh.
You might think this is not a problem, but let's say you're using this object as configuration for a 3rd party lib. You are now handing the control of your default object and everything referenced in it to the 3rd party lib.
A better solution might be to use JSON.stringify and JSON.parse to copy and combine the objects. Here's a Gist with the example:
https://gist.github.com/spikesagal/6f7822466887f19b9c65
HTH

Not that I know of, no. Also, you'll want to write your merge method like this:
function mergeInto(o1, o2) {
if (o1 == null || o2 == null)
return o1;
for (var key in o2)
if (o2.hasOwnProperty(key))
o1[key] = o2[key];
return o1;
}

You can do the following using native JS 1.7, without the need of a framework. See example on fiddle (example only intended for simple objects - not complex nested objects)
var obj1 = {a: "a", b: "b"};
var obj2 = {c: "c", d: "d"};
// The magic: ugly but works perfectly
var value = (JSON.stringify(obj1).concat(JSON.stringify(obj2))).replace("}{", ",");
document.getElementById("lbl1").setAttribute("value", value);
// back to object
var obj3 = JSON.parse(value);
document.getElementById("lbl2").setAttribute("value", obj3.a + " " + obj3.b + " " + obj3.c + " " + obj3.d);

You can combine the spread operator (...) and the Object.assign approach to get a good solution for the case where you have a bunch of objects in an array and want to merge them all into one super object.
const obj1 = {a:1, b:2}
const obj2 = {c:3, d:4}
const objArr = [obj1, obj2]
const mergedObj = Object.assign({}, ...objArr)
> { a: 1, b: 2, c: 3, d: 4 }

No native ways in ECMA-Script, use:
function merge(o1,o2) {
if (typeof(o1)!=='object') o1={};
if (typeof(o2)!=='object') o2={};
for (var k in o2) {
if (o1[k]!==undefined)
alert ('Collision Error'); // TODO
else
o1[k]=o2[k];
}
return o1;
}

Below I've included a deep-merge function I wrote. It will not deep-merge Arrays, only Objects. It will take two objects, and return a third, new object.
var merge = function(o1, o2) {
var o_new = {};
for(p in o1) {
if(o1[p]) {
if(typeof o1[p] == 'object' && !(o1[p] instanceof Array) && o2.hasOwnProperty(p)) {
o_new[p] = merge(o1[p], o2[p]);
}
else {
o_new[p] = o1[p];
}
}
}
for(p in o2) {
if(typeof o2[p] == 'object' && !(o2[p] instanceof Array) && o1.hasOwnProperty(p)) {
o_new[p] = merge(o1[p], o2[p]);
}
else {
o_new[p] = o2[p];
}
}
return o_new;
}

Related

Cloning arrays of objects with Object.assign

I discovered a bug on a project I'm working on that can be replicated by this snippet:
const original = [ { value: 1 } ];
function test() {
const copy = Object.assign([], original);
copy.forEach(obj => obj.value = obj.value + 1);
}
console.log(original[0].value); // -> 1, expected 1
test();
console.log(original[0].value); // -> 2, expected 1
test();
console.log(original[0].value); // -> 3, expected 1
I do not understand why this is the case. In the MDN web docs, the following statements can be found in the deep copy warning section:
For deep cloning, we need to use alternatives, because Object.assign() copies property values.
If the source value is a reference to an object, it only copies the reference value.
How do these notes apply to arrays / in this case? Are array values somehow considered as properties?
Looking back now, the method was probably not intended to work with arrays, so I guess I reap what I sow... but I'd still like to understand what's going on here. The intent was to deep copy the array in order to mutate the objects inside while keeping the original intact.
Are array values somehow considered as properties?
Yes. In JavaScript, arrays are objects (which is why Object.assign works with them), and properties with a special class of names called array indexes (strings defining decimal numbers in standard form with numeric values < 232 - 1) represent the elements of the array. (Naturally, JavaScript engines optimize them into true arrays when they can, but they're defined as objects and performing object operations on them is fully supported.) I found this sufficiently surprising when getting deep into JavaScript that I wrote it up on my anemic old blog.
Given:
const obj = {a: 1};
const arr = [1];
these two operations are the same from a specification viewpoint:
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
Of course, we don't normally write the quotes when accessing array elements by index, normally we'll just do arr[0], but in theory, the number is converted to a string and then the property is looked up by name — although, again, modern JavaScript engines optimize.
const obj = {a: 1};
const arr = [1];
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
console.log(arr[0]);
If you need to clone an array and the objects in it, map + property spread is a useful way to do that, but note the objects are only cloned shallowly (which is often sufficient, but not always):
const result = original.map((value) => ({...value}));
For a full deep copy, see this question's answers.
Here we can use structuredClone for deep copy.

How do you remove duplicates from an array of objects when uniqueness is determined by looking at 2 or more keys at the same time

I've got a long array of objects (>10_000) with duplicate objects I would like to remove.
In order to locate a duplicate I've got to look at two object properties: a, b
There are some elegant answers for removing objects by one property here: JavaScript: Remove duplicates of objects sharing same property value
e.g.
const uniq = _.uniq(arr, ele => ele.value});
Here is what the output of a solution would look like:
const arr = [{a:1, b:1}, {a:1, b:1}, {a:2, b:2}];
const removeDuplcatesByTwoKeys = (arr, ['a', 'b']) => // only elements that are duplicates for both key values;
result: const arr = [{a:2, b:2}];
I've tried _.uniq(arr, ele => ele.value && ele.otherValue}); but this does not work.
Another approach would be creating a map of the existing values keyed by those values e.g.
function unique(arr, keyProps) {
let map = new Map();
const kvArray = arr.map(entry => {
return keyProps.map(k => entry[k]).join('|');
})
kvArray.map(kv => {
if(map.has(kv)) {
const val = map.get(kv)
map.set(kv, val + 1)
} else {
map.set(kv, 1)
}
})
}
Though this would tell you what the duplicates are, what is the best way to remove them from the original array? This feels like a solution that is more complicated than it needs to be.
What is a performant way to remove duplicates by two properties from an array of objects?
You could use _.uniq with both properties as a JSON string. This way each element can be compared with the others through a uniform system.
For example,
const arr = [{a:1, b:1}, {a:1, b:1}, {a:2, b:2}];
const removeDuplcatesByTwoKeys = _.uniq(arr, el => JSON.stringify({a: el.a, b: el.b}));
console.log(removeDuplcatesByTwoKeys)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
This can also be solved via:
const removeDuplcatesByTwoKeys = array.filter((val, index) => {
return array.findIndex((row) => (
row.a === val.a && row.b === val.b
))
})
I've read that findIndex is not performant with large arrays but not 100% on that.
This method would allow you to check against as many keys as needed and not be concerned with order.
One issue with arrays is the O(n) lookup time. Big O Notiation, There is simply no way around that. My first suggestion here would be to look into other methods of storing the data with a O(1) lookup time. In JavaScript your go-to solutions would be using a Map, a Set, or a simple JavaScript Object. Your choice here really depends on your needs.
A Map is a Key Value Pair system. Thus will allow you to set and get a value by a key. This is very similar to a JavaScript object. The primary differences are that a Map is ordered so it may be iterated upon with a guarantee that the result will be ordered by insertion time. Also, a Map's key may be any datatype whereas a JavaScript object may only have a string.
A Set is basically a O(1) lookup array. The limitation here is that you cannot have duplicate values though it is still ordered by insertion time.
If you don't have control over how you receive the data this actually become quite a common interview question. While solving this problem is easy the true challenge comes in solving it in a performant way. The general accepted solution is O(n). You will simply iterate over the array and add either the value or an identifying feature to a Set. When you come across a value that is already in the set you may skip it. At the end of one iteration through the array you will have all unique values. There is simply no way for a catch-all algorithm to solve this problem faster.
For your particular issue I may suggest using a map so that you can use the stringified value of the object as a key. You may also use a set and just parse the JSON when you wish to use the object. A third, and probably ideal solution, is possible if an object contains a unique value such as an id. In this case you may just use this id as the key in the array. This would prevent issues with object property ordering.
const arr = [{a:1, b:1}, {a:1, b:1}, {a:2, b:2}];
const map = new Map();
arr.forEach((val) => {
const stringified = JSON.stringify(val);
if (!map.has(stringified)) {
map.set(stringified, val);
}
});
console.log(map.values()); // MapIterator { { a: 1, b: 1 }, { a: 2, b: 2 } }
I would hesitate to use this solution in browsers as I'm not sure of the adoption of recent features such as maps and sets however in node.js this would be the most performant way of doing it.

Storing things in objects vs arrays in JavaScript

This is more of a general question than a problem I need solved. I'm just a beginner trying to understand the proper way to do things.
What I want to know is whether or not I should only use objects as prototypes (if that's the correct term to use here) or whether or not it's OK to use them to store things.
As an example, in the test project I'm working on, I wanted to store some images for use later. What I currently have is something like:
var Images = {
james: "images/james.png",
karen: "images/karen.png",
mike: "images/mike.png"
};
Because I would know the position, I figure I could also put them in an array and reference the position in the array appropriately:
var images = ["images/james.png", "images/karen.png", "images/mike.png"];
images[0];
Using the object like this works perfectly fine but I'm wondering which is the more appropriate way to do this. Is it situational? Are there any performance reasons to do one over the other? Is there a more accepted way that, as a new programmer, I should get used to?
Thanks in advance for any advice.
Introduction
Unlike PHP, JavaScript does not have associative arrays. The two main data structures in this language are the array literal ([]) and the object literal ({}). Using one or another is not really a matter of style but a matter of need, so your question is relevant.
Let's make an objective comparison...
Array > Object
An array literal (which is indirectly an object) has much more methods than an object literal. Indeed, an object literal is a direct instance of Object and has only access to Object.prototype methods. An array literal is an instance of Array and has access, not only to Array.prototype methods, but also to Object.prototype ones (this is how the prototype chain is set in JavaScript).
let arr = ['Foo', 'Bar', 'Baz'];
let obj = {foo: 'Foo', bar: 'Bar', baz: 'Baz'};
console.log(arr.constructor.name);
console.log(arr.__proto__.__proto__.constructor.name);
console.log(obj.constructor.name);
In ES6, object literals are not iterable (according to the iterable protocol). But arrays are iterable. This means that you can use a for...of loop to traverse an array literal, but it will not work if you try to do so with an object literal (unless you define a [Symbol.iterator] property).
let arr = ['Foo', 'Bar', 'Baz'];
let obj = {foo: 'Foo', bar: 'Bar', baz: 'Baz'};
// OK
for (const item of arr) {
console.log(item);
}
// TypeError
for (const item of obj) {
console.log(item);
}
If you want to make an object literal iterable, you should define the iterator yourself. You could do this using a generator.
let obj = {foo: 'Foo', bar: 'Bar', baz: 'Baz'};
obj[Symbol.iterator] = function* () {
yield obj.foo;
yield obj.bar;
yield obj.baz;
};
// OK
for (const item of obj) {
console.log(item);
}
Array < Object
An object literal is better than an array if, for some reason, you need descriptive keys. In arrays, keys are just numbers, which is not ideal when you want to create an explicit data model.
// This is meaningful
let me = {
firstname: 'Baptiste',
lastname: 'Vannesson',
nickname: 'Bada',
username: 'Badacadabra'
};
console.log('First name:', me.firstname);
console.log('Last name:', me.lastname);
// This is ambiguous
/*
let me = ['Baptiste', 'Vannesson', 'Bada', 'Badacadabra'];
console.log('First name:', me[0]);
console.log('Last name:', me[1]);
*/
An object literal is extremely polyvalent, an array is not. Object literals make it possible to create "idiomatic" classes, namespaces, modules and much more...
let obj = {
attribute: 'Foo',
method() {
return 'Bar';
},
[1 + 2]: 'Baz'
};
console.log(obj.attribute, obj.method(), obj[3]);
Array = Object
Array literals and object literals are not enemies. In fact, they are good friends if you use them together. The JSON format makes intensive use of this powerful friendship:
let people = [
{
"firstname": "Foo",
"lastname": "Bar",
"nicknames": ["foobar", "barfoo"]
},
{
"firstName": "Baz",
"lastname": "Quux",
"nicknames": ["bazquux", "quuxbaz"]
}
];
console.log(people[0].firstname);
console.log(people[0].lastname);
console.log(people[1].nicknames[0]);
In JavaScript, there is a hybrid data structure called array-like object that is extensively used, even though you are not necessarily aware of that. For instance, the good old arguments object within a function is an array-like object. DOM methods like getElementsByClassName() return array-like objects too. As you may imagine, an array-like object is basically a special object literal that behaves like an array literal:
let arrayLikeObject = {
0: 'Foo',
1: 'Bar',
2: 'Baz',
length: 3
};
// At this level we see no difference...
for (let i = 0; i < arrayLikeObject.length; i++) {
console.log(arrayLikeObject[i]);
}
Conclusion
Array literals and object literals have their own strengths and weaknesses, but with all the information provided here, I think you can now make the right decision.
Finally, I suggest you to try the new data structures introduced by ES6: Map, Set, WeakMap, WeakSet. They offer lots of cool features, but detailing them here would bring us too far...
Actually, the way you declared things brings up the "difference between associative arrays and arrays".
An associative array, in JS, is really similar to an object (because it's one):
When you write var a = {x:0, y:1, z:3} you can access x using a.x(object) or a["x"](associative array).
On the other hand, regular arrays can be perceived as associative arrays that use unsigned integers as ID for their indexes.
Therefore, to answer your question, which one should we pick ?
It depends : I would use object whenever I need to put names/labels on thing (typically not for a collection of variables for instance). If the type of the things you want to store is homogeneous you will probably use an array (but you can still go for an object if you really want to), if some of/all your things have a different type than you should go for an object (but in theory you could still go for an array).
Let's see this :
var a = {
x:0,
y:0,
z:0
}
Both x,y,z have a different meaning (components of a point) therefore an object is better (in terms of semantic) to implement a point.
Because var a = [0,0,0] is less meaningful than an object, we will not go for an array in this situation.
var storage = {
one:"someurl",
two:"someurl2",
three:"someurl3",
}
Is correct but we don't need an explicit name for every item, therefore we might choose var storage = ["someurl","someurl2","someurl3"]
Last but not least, the "difficult" choice :
var images = {
cathy: "img/cathy",
bob: "img/bob",
randompelo: "img/randompelo"
}
and
var images = ["img/cathy","img/bob","img/randompelo"]
are correct but the choice is hard. Therefore the question to ask is : "Do we need a meaningful ID ?".
Let's say we work with a database, a meaningful id would be better to avoid dozens of loops each time you wanna do something, on the other hand if it's just a list without any importance (index is not important, ex: create an image for each element of array) maybe we could try and go for an array.
The question to ask when you hesitate between array and object is : Are keys/IDs important in terms of meaning ?
If they are then go for an object, if they're not go for an array.
You're correct that it would be situational, but in general its not a good idea to limit your program by only allowing a finite set of supported options like:
var Images = {
james: "images/james.png",
karen: "images/karen.png",
mike: "images/mike.png"
};
Unless, of course, you happen to know that these will be the only cases which are possible - and you actively do not want to support other cases.
Assuming that you dont want to limit the possibilities, then your array approach would be just fine - although personally I might go with an array of objects with identifiers, so that you arent forced to track the index elsewhere.
Something like:
var userProfiles = [
{"username": "james", "image": "images/james.png"},
{"username": "karen", "image": "images/karen.png"},
{"username": "mike", "image": "images/mike.png"}
];

How to deep copy a custom object in JavaScript?

I've been surfing around here a while and still haven't found an answer that worked for me.
Is there any way to deep copy a non-plain object in JS?
I've tried jQuery.extend(true, {}, this) but it only cloned some of it, the rest remained as a reference to another object.
Here are 3 different methods for copying objects. Each method has pros and cons, so read through and pick the best for your situation
Object.assign method
Use Object.assign, which "is used to copy the values of all enumerable own properties from one or more source objects to a target object". This copies both values and functions. At the time of writing this, browser support is good but not perfect, but this is the best method IMO of the three.
const obj1 = {a:1, b:2};
const obj1Copy = Object.assign(obj1)
Spread operator method
Alternatively, you can use the spread operator to spread from one object into another. Keep in mind that this will copy the values of keys, but if you the value of a key is a memory address (an other nested object or an array) then it will only be a shallow copy.
const obj1 = {a: () => {}, b:2}
const obj1Copy = { ...obj1 }
JSON stringify/parse trick
If the object doesn't have any circular references or functions as values, you can use the json stringify trick:
let myCopy = JSON.parse(JSON.stringify(myObject));
No libraries required, and works very well for most objects.
You can use lodash's cloneDeep function - https://lodash.com/docs/4.16.4#cloneDeep
Example (from docs)
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
A quick method to clone objects deep with performance into consideration.
JSON.parse(JSON.stringify({"foo":"bar"}))
How about performance ? >> [ May be this is the best way to deep copy objects ]. I strongly recommend you to checkout this video from Google Chrome Developers community on Youtube explaining how this method works and performance benchmarks.
Note: Use the JSON.parse method if your objects don't have Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types.
Source : Read this SO answer
Quick tip - React.JS initial state tree can be loaded from localStorage using this solution.
If you are dealing with a class instance you could use something like this.
You wouldn't need to copy the functions as they are delegated to on the prototype.
// myObject constructor
function myObject(foo, bar){
this.foo = foo
this.bar = bar
}
// delegate the functions to a prototype
myObject.prototype.something = function(){
console.log('something')
}
function instanceCopy(obj) {
// copy the object by the constructor
const copy = new obj.constructor()
const keys = Object.keys(obj)
keys.forEach(key => {
copy[key] = obj[key]
})
return copy
}
const myObj = new myObject('foo', 'bar')
const copyObj = instanceCopy(myObj)
console.log('myObj', myObj)
console.log('copyObj', copyObj)
console.log('same ?', copyObj === myObj)
// can we still call the functions
copyObj.something()
<script src="https://codepen.io/synthet1c/pen/WrQapG.js"></script>
You could use the structuredClone method:
const cloned = structuredClone(object)
Anyway, structuredClone allows you to do also other things that you might be interested in.
Check the documentation for further details:
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
Lodash _.cloneDeep() method kills the application performance. So I have come up with basic JavaScript solution. I have added it to my GIT repo. My application performance is back to normal after using my solution.
https://github.com/manideeppabba1991/javascript_util_functions/blob/master/clone_Array_or_Object.js

Why are Objects not Iterable in JavaScript?

Why are objects not iterable by default?
I see questions all the time related to iterating objects, the common solution being to iterate over an object's properties and accessing the values within an object that way. This seems so common that it makes me wonder why objects themselves aren't iterable.
Statements like the ES6 for...of would be nice to use for objects by default. Because these features are only available for special "iterable objects" which don't include {} objects, we have to go through hoops to make this work for objects we want to use it for.
The for...of statement creates a loop Iterating over iterable objects
(including Array, Map, Set, arguments object and so on)...
For example using an ES6 generator function:
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(example)) {
console.log(key);
console.log(value);
for (let [key, value] of entries(value)) {
console.log(key);
console.log(value);
}
}
The above properly logs data in the order I expect it to when I run the code in Firefox (which supports ES6):
By default, {} objects are not iterable, but why? Would the disadvantages outweigh the potential benefits of objects being iterable? What are the issues associated with this?
In addition, because {} objects are different from "Array-like" collections and "iterable objects" such as NodeList, HtmlCollection, and arguments, they can't be converted into Arrays.
For example:
var argumentsArray = Array.prototype.slice.call(arguments);
or be used with Array methods:
Array.prototype.forEach.call(nodeList, function (element) {}).
Besides the questions I have above, I would love to see a working example on how to make {} objects into iterables, especially from those who have mentioned the [Symbol.iterator]. This should allow these new {} "iterable objects" to use statements like for...of. Also, I wonder if making objects iterable allow them to be converted into Arrays.
I tried the below code, but I get a TypeError: can't convert undefined to object.
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
};
for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error
I'll give this a try. Note that I'm not affiliated with ECMA and have no visibility into their decision-making process, so I cannot definitively say why they have or have not done anything. However, I'll state my assumptions and take my best shot.
1. Why add a for...of construct in the first place?
JavaScript already includes a for...in construct that can be used to iterate the properties of an object. However, it's not really a forEach loop, as it enumerates all of the properties on an object and tends to only work predictably in simple cases.
It breaks down in more complex cases (including with arrays, where its use tends to be either discouraged or thoroughly obfuscated by the safeguards needed to for use for...in with an array correctly). You can work around that by using hasOwnProperty (among other things), but that's a bit clunky and inelegant.
So therefore my assumption is that the for...of construct is being added to address the deficiencies associated with the for...in construct, and provide greater utility and flexibility when iterating things. People tend to treat for...in as a forEach loop that can be generally applied to any collection and produce sane results in any possible context, but that's not what happens. The for...of loop fixes that.
I also assume that it's important for existing ES5 code to run under ES6 and produce the same result as it did under ES5, so breaking changes cannot be made, for instance, to the behavior of the for...in construct.
2. How does for...of work?
The reference documentation is useful for this part. Specifically, an object is considered iterable if it defines the Symbol.iterator property.
The property-definition should be a function that returns the items in the collection, one, by, one, and sets a flag indicating whether or not there are more items to fetch. Predefined implementations are provided for some object-types, and it's relatively clear that using for...of simply delegates to the iterator function.
This approach is useful, as it makes it very straightforward to provide your own iterators. I might say the approach could have presented practical issues due to its reliance upon defining a property where previously there was none, except from what I can tell that's not the case as the new property is essentially ignored unless you deliberately go looking for it (i.e. it will not present in for...in loops as a key, etc.). So that's not the case.
Practical non-issues aside, it may have been considered conceptually controversial to start all objects off with a new pre-defined property, or to implicitly say that "every object is a collection".
3. Why are objects not iterable using for...of by default?
My guess is that this is a combination of:
Making all objects iterable by default may have been considered unacceptable because it adds a property where previously there was none, or because an object isn't (necessarily) a collection. As Felix notes, "what does it mean to iterate over a function or a regular expression object"?
Simple objects can already be iterated using for...in, and it's not clear what a built-in iterator implementation could have done differently/better than the existing for...in behavior. So even if #1 is wrong and adding the property was acceptable, it may not have been seen as useful.
Users who want to make their objects iterable can easily do so, by defining the Symbol.iterator property.
The ES6 spec also provides a Map type, which is iterable by default and has some other small advantages over using a plain object as a Map.
There's even an example provided for #3 in the reference documentation:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (var value of myIterable) {
console.log(value);
}
Given that objects can easily be made iterable, that they can already be iterated using for...in, and that there's likely not clear agreement on what a default object iterator should do (if what it does is meant to be somehow different from what for...in does), it seems reasonable enough that objects were not made iterable by default.
Note that your example code can be rewritten using for...in:
for (let levelOneKey in object) {
console.log(levelOneKey); // "example"
console.log(object[levelOneKey]); // {"random":"nest","another":"thing"}
var levelTwoObj = object[levelOneKey];
for (let levelTwoKey in levelTwoObj ) {
console.log(levelTwoKey); // "random"
console.log(levelTwoObj[levelTwoKey]); // "nest"
}
}
...or you can also make your object iterable in the way you want by doing something like the following (or you can make all objects iterable by assigning to Object.prototype[Symbol.iterator] instead):
obj = {
a: '1',
b: { something: 'else' },
c: 4,
d: { nested: { nestedAgain: true }}
};
obj[Symbol.iterator] = function() {
var keys = [];
var ref = this;
for (var key in this) {
//note: can do hasOwnProperty() here, etc.
keys.push(key);
}
return {
next: function() {
if (this._keys && this._obj && this._index < this._keys.length) {
var key = this._keys[this._index];
this._index++;
return { key: key, value: this._obj[key], done: false };
} else {
return { done: true };
}
},
_index: 0,
_keys: keys,
_obj: ref
};
};
You can play with that here (in Chrome, at lease): http://jsfiddle.net/rncr3ppz/5/
Edit
And in response to your updated question, yes, it is possible to convert an iterable to an array, using the spread operator in ES6.
However, this doesn't seem to be working in Chrome yet, or at least I cannot get it to work in my jsFiddle. In theory it should be as simple as:
var array = [...myIterable];
Objects don't implement the iteration protocols in Javascript for very good reasons. There are two levels at which object properties can be iterated over in JavaScript:
the program level
the data level
Program Level Iteration
When you iterate over an object at the program level you examine a portion of the structure of your program. It is a reflective operation. Let's illustrate this statement with an array type, which is usually iterated over at the data level:
const xs = [1,2,3];
xs.f = function f() {};
for (let i in xs) console.log(xs[i]); // logs `f` as well
We just examined the program level of xs. Since arrays store data sequences, we are regularly interested in the data level only. for..in evidently makes no sense in connection with arrays and other "data-oriented" structures in most cases. That is the reason why ES2015 has introduced for..of and the iterable protocol.
Data Level Iteration
Does that mean that we can simply distinguish the data from the program level by distinguishing functions from primitive types? No, because functions can also be data in Javascript:
Array.prototype.sort for instance expects a function to perform a certain sort algorithm
Thunks like () => 1 + 2 are just functional wrappers for lazily evaluated values
Besides primitive values can represent the program level as well:
[].length for instance is a Number but represents the length of an array and thus belongs to the program domain
That means that we can't distinguish the program and data level by merely checking types.
It is important to understand that the implementation of the iteration protocols for plain old Javascript objects would rely on the data level. But as we've just seen, a reliable distinction between data and program level iteration is not possible.
With Arrays this distinction is trivial: Every element with an integer-like key is a data element. Objects have an equivalent feature: The enumerable descriptor. But is it really advisable to rely on this? I believe it is not! The meaning of the enumerable descriptor is too blurry.
Conclusion
There is no meaningful way to implement the iteration protocols for objects, because not every object is a collection.
If object properties were iterable by default, program and data level were mixed-up. Since every composite type in Javascript is based on plain objects this would apply for Array and Map as well.
for..in, Object.keys, Reflect.ownKeys etc. can be used for both reflection and data iteration, a clear distinction is regularly not possible. If you're not careful, you end up quickly with meta programming and weird dependencies. The Map abstract data type effectively ends the conflating of program and data level. I believe Map is the most significant achievement in ES2015, even if Promises are much more exciting.
I was also bothered with this question.
Then I came up with an idea of using Object.entries({...}), it returns an Array which is an Iterable.
Also, Dr. Axel Rauschmayer posted an excellent answer on this.
See Why plain objects are NOT iterable
I guess the question should be "why is there no built-in object iteration?
Adding iterability to objects themselves could conceivably have unintended consequences, and no, there is no way to guarantee order, but writing an iterator is as simple as
function* iterate_object(o) {
var keys = Object.keys(o);
for (var i=0; i<keys.length; i++) {
yield [keys[i], o[keys[i]]];
}
}
Then
for (var [key, val] of iterate_object({a: 1, b: 2})) {
console.log(key, val);
}
a 1
b 2
You can easily make all objects iterable globally:
Object.defineProperty(Object.prototype, Symbol.iterator, {
enumerable: false,
value: function * (){
for(let key in this){
if(this.hasOwnProperty(key)){
yield [key, this[key]];
}
}
}
});
This is the latest approach (which works in chrome canary)
var files = {
'/root': {type: 'directory'},
'/root/example.txt': {type: 'file'}
};
for (let [key, {type}] of Object.entries(files)) {
console.log(type);
}
Yes entries is now a method thats part of Object :)
edit
After looking more into it, it seems you could do the following
Object.prototype[Symbol.iterator] = function * () {
for (const [key, value] of Object.entries(this)) {
yield {key, value}; // or [key, value]
}
};
so you can now do this
for (const {key, value:{type}} of files) {
console.log(key, type);
}
edit2
Back to your original example, if you wanted to use the above prototype method it would like like this
for (const {key, value:item1} of example) {
console.log(key);
console.log(item1);
for (const {key, value:item2} of item1) {
console.log(key);
console.log(item2);
}
}
Technically, this is not an answer to the question why? but I have adapted Jack Slocum’s answer above in light of BT’s comments to something which can be used to make an Object iterable.
var iterableProperties={
enumerable: false,
value: function * () {
for(let key in this) if(this.hasOwnProperty(key)) yield this[key];
}
};
var fruit={
'a': 'apple',
'b': 'banana',
'c': 'cherry'
};
Object.defineProperty(fruit,Symbol.iterator,iterableProperties);
for(let v of fruit) console.log(v);
Not quite as convenient as it should have been, but it’s workable, especially if you have multiple objects:
var instruments={
'a': 'accordion',
'b': 'banjo',
'c': 'cor anglais'
};
Object.defineProperty(instruments,Symbol.iterator,iterableProperties);
for(let v of instruments) console.log(v);
And, because every one is entitled to an opinion, I can’t see why Objects are not already iterable either. If you can polyfill them as above, or use for … in then I can’t see a simple argument.
One possible suggestion is that what is iterable is a type of object, so it is possible that iterable has been limited to a subset of objects just in case some other objects explode in the attempt.

Categories