After Object.create: properties are there, but Object.keys / getOwnPropertyNames = []? - javascript

As the title says, I have hit a conceptual snag in understanding how Object.create() works.
It was my impression that, after using this method, the resulting object would inherit the values of the object passed as argument to Object.create(). In the simple example below, I can create an object and access its inherited values, I can even correctly get the prototype of the new object, however the Object.keys() array is empty!
let obj = Object.create({x: 1, y: 2});
console.log("Keys of obj:");
Object.keys(obj).forEach(function (key) {
console.log(key + ' - ' + obj[key]);
});
console.log("Prototype of obj:");
console.log(Object.getPrototypeOf(obj));
console.log("obj.x = " + obj.x);
Console result:
acg#acg:~/dev/$ node test.js
Keys of obj:
Prototype of obj:
{ x: 1, y: 2 }
obj.x = 1
(Just to eliminate any confusion: I get the same results when substituting Object.getOwnPropertyNames() for Object.keys(), by the way).
Am I misunderstanding something? Why is the keys array empty?
EDIT: Am I right in assuming that Object.keys() and Object.getOwnPropertyNames() only give me the properties directly available on an object, i.e. not inherited?
Then, I think my question is: is there a function that gives me an object's properties? (inherited or otherwise)
EDIT2: Nope -- got to traverse the prototype chain manually, as shown here:
Is it possible to get the non-enumerable inherited property names of an object?
EDIT3: According to answer from Pol Martin below, one can use a for..in construct to loop over all properties (including those inherited).
Also see https://stackoverflow.com/a/208439/7705625

Object.create has two arguments. The first one is the prototype of the created object, and a second optional parameter is an object of property descriptors.
Object.create(proto[, propertiesObject])
If you create the object passing the properties object as the first argument,
let obj = Object.create({x: 1, y: 2});
this one will become the prototype of your new object.
As Object.keys() returns an array of only its own enumerable properties, the ones you passed when constructing it won't be listed.
To create the object the way you intended, yo can use Object.assign:
let obj = Object.assign({}, {x: 1, y: 2});

Related

Cloning any javascript object by copying all own properties

If I wanted to clone any javascript object (that's not null), I would think I could just copy all of its own properties (enumerable and non-enumerable) -- using Object.getOwnPropertyNames -- onto a new empty object.
But I've noticed that an example of a deep cloning function provided by Dojo toolkit (https://davidwalsh.name/javascript-clone) treats RegExp, Date, and Node objects as special cases, and lodash.cloneDeep also has a lot of logic that is a lot more complicated than simply copying properties, including having some special cases of its own and apparently not supporting all types of objects: (https://github.com/lodash/lodash/blob/master/.internal/baseClone.js).
Why is simply copying the object properties not sufficient? What else is there to a javascript object besides its properties that I don't know about?
EDIT: to be clear, I'm talking about deep cloning an object. Sorry for the confusion.
If the top level properties are all value objects like strings and numbers then just copying the top level properties is fine for a clone of an object. If there are any reference objects such as dates, arrays or other objects then all your are doing is copying a reference from one object to another. If you change the reference object on the clone you will mutate the original object.
Take a look at my clone function at https://stackblitz.com/edit/typescript-qmzgf7
If it is an array it clones every item in the array, if it is a date it creates a new date with the same time, if it is an object it clones every property else if just copies the property.
The cloned object can now be mutated without worrying about effects it might have on the original object.
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: (typeof obj === 'object') && obj
? Object.getOwnPropertyNames(obj).reduce((o, prop) => ({ ...o, [prop]: clone(obj[prop]) }), {})
: obj;
let original = { prop1: "Original", objProp: { prop1: "Original" } };
let swallowCopy = { ...original };
let clonedObj = clone(original);
clonedObj.prop1 = "Changed";
clonedObj.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
swallowCopy.prop1 = "Changed";
swallowCopy.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
Notice how modifying the property on the object property shallow copy causes the original to change as well.
The easiest way to clone an object in JS is by using the ... spread operator.
Let's say you have this object:
const object = { foo: 1, bar: 2 }
To clone it, you can simply declare:
const objectClone = {...object}.
This will create all the properties present in the original object onto the clone, as well as their values.
Now the problem is, if you have any object nested in there, the copies will be made by reference. Suppose the original object is this instead:
const student = { studentID: 1, tests: { test1: 90, test2: 95}}
If you create a copy of that object by using the spread operator(or Object.assign, spread is just syntactic sugar), the nested object will actually point to the object inside the original object! So repeating this:
const studentClone = {...student}
And now you edit a property of the nested object inside the clone:
studentClone.tests.test1 = 80
This will change the value in both clone, and original object, as the nested object is really just pointing to 1 object in memory.
Now what those utilities, like _.cloneDeep will do, is iterate through all inner objects in the object you're cloning, and repeat the process. You could technically do it yourself, but you wouldn't be able to do it on objects with many nested objects easily. Something like this:
const studentClone = {...studentClone, tests: {...studentClone.tests}}
This would create new objects, with no reference problems.
Hope this helped!
EDIT: Just adding, object spreading would only work properly for prototype objects, of course. Each instantiated objects,such as arrays, Date objects etc, would have their own way of cloning.
Arrays can be copied similarly, through [...array]. It does follow the same rules regarding to references. For dates, you can simply pass the original date object into the Date constructor again:
const clonedDate = new Date(date)
This is where the third-party utilities will come in handy, as they'll usually handle most use cases.
This answer does a good job of explaining two of the problems with cloning a normal JavaScript object: prototype properties and circular references. But to answer your question regarding certain built-in types, the TL;DR answer is that there are 'under the hood' properties that you have no programmatic access to.
Consider:
let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
Empty array? Why? Just to make sure we're not crazy
foo.map(x => x + 1); // [2, 3]
The reason why map (and the other array methods) fail to work is that an Array isn't simply an object: it has internal slot properties for the stuff you put in it that you don't get to see as the JavaScript programmer. As another example, every JavaScript object has an internal [[Class]] property that says what kind of object it is. Fortunately for us, there's a loophole in the spec that allows us indirect access to it: the good ol Object.prototype.toString.call hack. So let's see what that has to say about various stuff:
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(3); // [object Number]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(/\w/); // [object RegExp]
Object.prototype.toString.call(JSON); // [object JSON]
Object.prototype.toString.call(Math); // [object Math]
Let's see what it says about our foo and bar:
Object.prototype.toString.call(foo); // [object Array]
Object.prototype.toString.call(bar); // [object Object] Doh!
There's no way to 'convert' a random object to an Array... or a Date... or an HTMLElement... or a regex. Now, there are in fact ways to clone all of those things, but they require special logic: you can't just copy properties, or even set the prototype, because they have internal logic you can't access or directly replicate.
In normal everyday JavaScript programming we don't worry too much about this stuff, it's the kind of thing that's generally of interest to library authors (or language implementers). We everyday working stiffs just use a library to cover the edge cases and call it a day. But every once in a while the abstractions we use leak and the ugly bubbles through. This is however a great illustration of why you should probably use battle-tested libraries rather than trying to roll your own.
An object in javascript includes fields and functions together, and every field could be another object (Like Date type). If you copy a date field, it will be a reference type assignment.
Example:
var obj1 = { myField : new Date('2018/9/17') };
var obj2 = {};
obj2.myField = obj1.myField;
Now, if we change "obj2.myField" like this:
obj2.myField.setDate(obj2.myField.getDate() + 2);
console.log(obj1.myField); // Result =====> Wed Sep 19 2018 00:00:00 GMT+0430
As you see, obj1 and obj2 still are linked.
Correct way to copy a date field:
obj2.myField = new Date(obj1.myField.getTime());
Most native objects(like you have mentioned - I don't know for is the correct naming for them; maybe built-in?) are treated as "simple": it does not make sense to copy Date object property-by-property. In the same time they all are mutable in some way.
let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well
So you either should handle that exceptions(special cases) explicitly or take a risk of side effects for some cases.

Can't list window.document properties - why?

Running this JavaScript lists in Firefox 60.2 only one property ("location"), but there are many others, like "document.title" etc.
window.console.log("List props: " + Object.keys(window.document).sort().join(' / '));
Why is it this way? Safety? How is this done technically?
How can I list all properties?
Object.keys(o) returns the own, enumerable properties of o.
Own: properties defined directly on o, not on its prototype chain:
Enumerable: a flag that controls if a given property is included when listing an object's properties.
In this case most of the keys you expect are defined on another object in in document's prototype chain:
document.hasOwnProperty("title"); // false
document.__proto__.__proto__.hasOwnProperty("title"); // true
document.__proto__.__proto__.propertyIsEnumerable("title"); // true
You can use a for... in loop to find the enumerable properties that are defined on an object or its prototype chain:
for (let key in window.document) {
console.log(key);
}
The reason is that Object.keys() returns returns an array of strings that represent all the enumerable properties of the given object.
Try this to see which properties of document are enumerable
for(let key in document){
let isEnumerable = document.propertyIsEnumerable(key);
console.log(`docment['${key}'] isEnumerable?:${isEnumerable}`);
}
However as the previous answer has stated you can use a for-in loop to get all properties in an array sort them and join them
I couldn't find an official reason for it not working with window.document but it seems you can reproduce this behavior with other variables as well.
The issue seems to be Object.keys() not returning everything from these variables.
If you're still trying to get all properties of document, you can still use
var props = [];
for(var prop in window.document) {
props.push(prop);
}
console.log("List props: " + props.sort().join('/'));
Which will do the exact same thing as your approach.

Accessing Javascript Object Keys

I'm having the hardest figuring out how to this (seems so simple).
I have a Javascript Object as shown here
Output of console.log(data):
{"prevExists":false,"pubKey":"b5","ID":"5f1"}
I'm trying to access the different key value pairs.
When I try the expected methods, I get back undefined.
I have tried:
var pubKey = "pubKey";
data.pubKey
data[pubkey];
data["pubKey"];
I know I'm missing something really obvious here.
You have several ways of accessing keys, depending on which keys you're talking about.
In your example, any of those would work:
var data = {
"prevExists":false,
"pubKey":"b5",
"ID":"5f1"
};
// Access all keys of enumerable string-keyed properties
Object.keys(data).forEach((key) => console.log(key,data[key]));
// Access all keys of enumerable and non-enumerable string-keyed properties
Object.getOwnPropertyNames(data).forEach((key) => console.log(key,data[key]));
// Access all keys of enumerable string-keyed properties of your object, its prototype, and all the prototype chain...
for (let key in data)
console.log(key,data[key]);
If you want to have a better understanding of what is an object's property, you can have a look at this recent answer I wrote on the topic.
You can use Object.keys and a foreach loop to access the properties on the object.
var data = {"prevExists":false,"key":"b5","ID":"5f1"};
Object.keys(data).forEach(function(key) {
console.log('key - ' + key + ' :: value - ' + data[key]);
});
First you need to create a reference to your object. Like this:
var myObj = { "prevExists": false, "key": "b5", "ID": "5f1" };
Then, you can access the elements using their keys:
console.log(myObj["prevExists"]);
Console exit:
false
Good luck!
Use the Object.keys method
var data = {"prevExists":false,"pubKey":"b5","ID":"5f1"}
console.log(Object.keys(data));
Object.keys()
The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
You are confusing yourself with the line var pubKey="pubKey".
There are 2 ways to access object parameters:
const data = {"prevExists":false,"pubKey":"b5","ID":"5f1"};
// var pubKey = "pubKey"; This line is not needed
1) data.pubKey
If you use the dot operator (.), then you reference it with the key name.
2) data["pubKey"];
If you use brackets ([]), then you use the string that matches the key.
If you add the line:
const pubKey = "pubKey";
, then data[pubKey] will also work, because it evaluates to data["pubKey"]

Use Array Prototype Functions with non-arrays

As we know we can use Array prototype methods with function arguments just like below.
function abc() {
// As 'arguments' is a array like object which don't have Array prototype functions.
Array.prototype.push.call(arguments, 30);
console.log(arguments);
}
abc(10, 20); // Output is: [10, 20, 30]
So like wise I tried to use push on DOM element classList like below which gives me an error "Cannot set property length of [object Object] which has only a getter".
var bodyClassList = document.body.classList;
Array.prototype.push.call(bodyClassList, 'myClass');
Note: I have just tried to learn concepts so that's why I used push even it has builtin add() method.
So my question is:
On which objects we can use Array.prototype methods?
Thanks in advance.
Array.prototype methods are quite generic methods that work on all kinds of objects. They're just getting and setting indexed properties and the .length of the object they are called on. (And, with ES6, the .constructor property of Array instances if they need to create a new instance). You can check the spec to see what push does exactly.
So basically, to answer your question…
On which objects we can use Array.prototype methods?
…on all objects. That is, objects that don't throw exceptions when they are being operated on. Notice that push itself does not throw the error.
Some examples:
var x = {0:0, length:1};
Array.prototype.push.call(x, 1);
console.log(x); // {0:0, 1:1, length:2}
console.log(Array.prototype.slice.call(x)); // [0, 1]
var y = {0:0, get length() { throw new Error("boo!"); }}
Array.prototype.push.call(y, 1); // throws
var z = {get 0() { throw new Error("boo!"); }, length: 1};
Array.prototype.push.call(z, 1);
console.log(z); // {0:…, 1:1, length:2}
console.log(Array.prototype.slice.call(z)); // throws
The arguments object is very much like x. The classList is very much like y.

Learning Array.prototype and calculating its length

first: I recently knew that Array.prototype is itself an array([]).But an amazing thing is that it is not the instance of Array object .How is that possible?What is the reason for this?
Second:Array.prototype has many properties but when u log i.e console.log(Array.prototype.length) ,the output is '0'.What is the reason here?I tried this aswell ,the result is same
var x=[];
x['a']="b";
x['b']="c";
console.log(x.length)//output:0
It would be great if u let me know the difference in element and property of an array
I recently knew that Array.prototype is itself an array ([]).
You are right, it is an array. The specification says in §15.4.4, Properties of the Array Prototype Object:
The Array prototype object is itself an array; its [[Class]] is "Array", and it has a length property (whose initial value is +0) and the special [[DefineOwnProperty]] internal method described in 15.4.5.1.
But an amazing thing is that it is not the instance of Array object. How is that possible? What is the reason for this?
If you tried Array.prototype instanceof Array then the result will indeed be false. Why? Because of the way the instanceof operator works. It compares the prototype of an object with the value of the prototype property of the constructor function.
I.e. in this case it does
Object.getPrototypeOf(Array.prototype) === Array.prototype
As we can already see in this comparison, we are trying to test whether Array.prototype is its own prototype, which is impossible. The specification also mentions in the same paragraph:
The value of the [[Prototype]] internal property of the Array prototype object is the standard built-in Object prototype object (15.2.4).
That is, Object.getPrototypeOf(Array.prototype) === Object.prototype and Object.prototype !== Array.prototype. Hence instanceof yields false, but Array.prototype is an array nonetheless.
Array.prototype has many properties but when u log i.e console.log(Array.prototype.length), the output is '0'.
0 being the value of Array.prototype.length is defined in specification (see above).
It would be great if u let me know the difference in element and property of an array
An element of an array is a property with a property name that is a positive 32-bit integer (or 0). A property is any other property which does not have such a property name. Those are not considered by any array operations.
The specification provides a more precise description in §15.4, Array Objects:
Array objects give special treatment to a certain class of property names. A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232−1. A property whose property name is an array index is also called an element.
So you see, if a property name is converted to (the string representation of) an unsigned 32-bit integer and still has the same value, then it is an array index and the associated value is an element of the array.
The specification continuous with
The value of the length property is numerically greater than the name of every property whose name is an array index;
We just learned which property names are considered to be array indexes, those that can be converted to unsigned integers. By that definition, "a" is not an array index, so
var x = [];
x['a'] = 42;
does not change the length property. But "3" is an array index, so
x["3"] = 42;
changes the length property to 4.
First of all objects in JavaScript inherit from other objects, not from constructor functions. Hence the statement "But an amazing thing is that it is not the instance of Array object" is wrong.
Say you have an object called rectangle as follows:
var rectangle = {
height: 5,
width: 10
};
rectangle.area = function () {
return this.width * this.height;
};
Now I can calculate the area of this rectangle by calling the method rectangle.area. However say I wanted to create a new rectangle with a different width and height. This is what I would do:
var rectangle2 = Object.create(rectangle);
rectangle2.height = 8;
rectangle2.width = 20;
alert(rectangle2.area());
Here the object rectangle2 inherits from the object rectangle. So you see objects actually inherit from other objects, not from constructor functions. Read the following thread for more details:
Questions regarding prototype function
When you precede a function call with the new keyword JavaScript creates a new object which inherits from the prototype of the function. Hence the instance actually inherits from the prototype.
An object a only inherits from an object b if the object b is in the prototype chain of object a. That's the reason we have the isPrototypeOf method.
The next thing to remember is that the instanceof operator doesn't really mean anything. It can be implemented in JavaScript as follows:
function instanceOf(obj, func) {
return Object.prototype.isPrototypeOf.call(func.prototype, obj);
}
Hence as you can clearly see an object qualifies as an "instance" of a function only if it inherits from the prototype of that function.
That's the reason why Array.prototype instanceof Array returns false - an object can't inherit from itself.
For more information about how the instanceof operator works read the following thread:
JavaScript inheritance and the constructor property
Finally the length property of an array is always one more than the greatest numeric index of the array. For example:
var a = [];
alert(a.length); // 0
a[99999] = null;
alert(a.length); // 100000
That's the reason Array.prototype.length is 0 - Array.prototype has no numeric keys. However if you assign it a numeric key then the length property will change accordingly:
Array.prototype[99999] = null;
alert(Array.prototype.length); // 100000
The same applies for x - the properties "a" and "b" are not numeric. Hence they don't affect the length of the array.
BTW if you're interested in prototypal inheritance then you should read my blog post on Why Prototypal Inheritance Matters.
when you create a variable as
var x=[];
x[0] = 0; or x[x.length]=0; or x.push(0);
its an array and it will have length property.
members of array can be referred using index(which is always numeric i.e.0,1,2, ...)
But when you create a variable as
var x={};
x.key1= "value1"; or x["key1"]="value1";
it becomes an object(JSON object). members of object will be always referred using keys.
like x.key1, x,key2 etc...
you can not access array members by x.0 or x.1 etc...
hope it helps you
To your first point: Array is an object of type function.
console.log(Array);
function Array() { [native code] }
Array.prototype on the other hand, is an internal implementation of the methods shared between instances.
So,
console.log(Array.prototype);
[]
That's why console.log(Array.prototype.length) returns 0. It's an empty array.
Don't think of Array.protoype as an instance of Array. It's just an internal implementation on which you can call any method of the prototype object.
An instance can be created with the new operator. And all instances inherit the prototype object of the constructor function, in this case, Array.
var arr = new Array();
Hope it clarifies your first point.

Categories