I've been using structuredClone for deep cloning, instead of the cumbersome JSON.parse(JSON.stringify(foo)). However, in the list of supported types, MDN says:
Object: but only plain objects (e.g. from object literals).
I'm not sure what would be a non-plain object, an object instantiated by a class? It makes little sense, because structuredClone seems to work on those as well.
In short: what kind of objects are not supported by structuredClone?
I'm not sure what would be a non-plain object, an object instantiated by a class?
Correct. Objects instantiated by a class can be cloned, but they'll be plain objects in the cloned version, so information may be lost.
class X {
prop = 1
static staticProp = 2
method() { return 3 }
}
const x = new X()
const clone = structuredClone(x)
console.log(clone) // { prop: 1 }
console.log(clone instanceof X) // false
console.log(clone.constructor.name) // Object
console.log(clone.constructor.staticProp) // undefined
console.log(clone.method()) // Uncaught TypeError: clone.method is not a function
Related
I'm defining a JavaScript function like this:
function F() {
}
It gets some more properties and methods through a prototype object. When it's time to use the function, I'd like to see the object name, like for Array or other built-in objects. But I always see "Object" as the object type.
var i = new F();
console.log(i);
// Object { }
// wanted: something with "F" instead of "Object"
var a = [];
console.log(a);
// Array []
console.log(document);
// HTMLDocument about:newtab
What can I do to get the desired output?
I found several other questions here but none had the desired effect. I'm not too experienced with the whole prototype and new thing in JavaScript so I don't know the correct terms to find relevant results.
My function F must still be callable as a regular function, so I don't think I am interested in creating a class as supported in newer web browsers.
My code targets any web browsers that's in wide use today, which is probably Chrome, Firefox, Safari and a bunch of Android apps with unknown internals (Samsung?).
The display name of objects is controlled by their .toString() behavior. The default implementation of .toString() uses the value returned by the Symbol.toStringTag property (see here), which will be found on the Object prototype if not defined explicitly for your type. Thus it will have the value "Object" unless you override it.
function F() { }
let i = new F();
console.log(i.toString()); //[object Object]
F.prototype[Symbol.toStringTag] = "F";
console.log(i.toString()); //[object F]
For the following code, why the propB of myObj is updated? And why test.childObj doesn't have the own property propB?
var myObj = { propA: '', propB: [] }
var fatherObj = {
childObj: null,
init: function() {
this.childObj = Object.create(myObj);
this.childObj.propA = 'A';
this.childObj.propB.push(2);
}
}
var test = Object.create(fatherObj);
test.init();
console.log(myObj.propB.length);
console.log(test.childObj.hasOwnProperty('propA'));
console.log(test.childObj.hasOwnProperty('propB'));
Using Object.create you do not copy an object, but you create a new object that inherits the passed one:
this.childObj { } -> myObj { propA: "", propB: [] }
Now when you get a property, it gets looked up in the inheritance chain. As it can't be found in the child object, it gets looked up in myObj. That is usually not a problem, as setting an objects property directly sets it on the object itself without using the inheritance chain, therefore this:
this.childObj.propA += "test";
looks up propA on myObj but sets propA on the childObj. With reference types however, you do not rewrite the property, therefore this:
this.childObj.propB.push(1);
looks up propB in myObj, and pushes to that, the result is:
this.childObj { propA: "test" } -> myObj { propA: "", propB: [1] }
To resolve that, the childObj has to have its own propB array:
this.childObj.propB = this.childObj.propB.slice();
That results in:
this.childObj { propA: "test", propB: [1] } -> myObj { propA: "", propB: [1] }
and now pushing to propB pushes to the array in childObj.
That is because myObj becomes a prototype of newly created test object (caused by creating it via Object.create.
Due to you just modify underlying prop propB of childObj (and not assign the new value like for propA) you only modify the prop of prototype. Please read more about inheritance in javascript.
By using Object.create(myObj);, myObj becomes the prototype of childObj. If a property is not found in an object and is found in the prototype, the one from the prototype is used. Also note that hasOwnProperty tells if the objects owns the property itself, and will return false if it exists only in the prototype and not in the object. By assigning directly a property on an object, you set it on the object, not on the prototype, but when you modify the property propB with push, the property is not found directly in childObject, so you modify the prototype.
You have to be careful with that, as all objects created this way will share the same prototype object and by modifying one, you will modify it for all instances.
You have also to be extra careful because it can be tricky to know in javascript where in the prototype chain your property come from, as myObj also have a prototype.
var myObj = { propA: '', propB: [] }
var fatherObj = {
childObj: null,
init: function() {
this.childObj = Object.create(myObj);
this.childObj.propA = 'A';
this.childObj.propB.push(2);
}
}
var test = Object.create(fatherObj);
test.init();
console.log('test: ', test);
console.log('test prototype: ', test.__proto__);
console.log('test.childObj: ', test.childObj);
console.log('test.childObj prototype: ', test.childObj.__proto__);
console.log(test.childObj.hasOwnProperty('propA'));
console.log(test.childObj.hasOwnProperty('propB'));
Javascript inheritance does not work like in most other languages. When using var x = Object.create(someObj), the new object x is actually empty (it has no properties of its own) along with a reference to its prototype: the separate object someObj.
Any property or function that you then try to access from x which does not resolve there, will be looked up in someObj - or even higher up, if someObj also has a prototype object, etc.
Things can get confusing when inherited properties are modifiable objects, as in your example. As we have seen, trying to modify the array x.propB by pushing an item to it, will actually modify the inherited array contained in someObj.
I'm strongly convinced that it is bad style to have modifiable objects (both arrays and regular objects) in prototype objects. Instead, prototype objects should contain only functions and no data, or at least no data beyond simple strings, numbers and booleans which are not modifiable.
To summarize:
Functions are useful to inherit, they are not modifiable (you can't change the function body), and can still be overridden/replaced if needed.
Data becomes shared data by all inheritors, unless/until they override it by a re-assignment, which is a burden in itself.
I try to understand the following sentence at github airnb/javascript
https://github.com/airbnb/javascript#objects--prototype-builtins
Why? These methods may be shadowed by properties on the object in
question
What is meant with "shadowed" in this case?
For easier reference here the full section:
3.7 Do not call Object.prototype methods directly, such as hasOwnProperty, propertyIsEnumerable, and isPrototypeOf.
Why? These methods may be shadowed by properties on the object in
question - consider { hasOwnProperty: false } - or, the object may be
a null object (Object.create(null)).
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object,key));
// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));
When you creating an object
const anObject = {};
it almost always has the Object in prototype chain. It allow the created object to have access to functions defined in Object like hasOwnProperty.
Shadowed means a method or a property that defined in the created object has the same name as those functions or properties that in prototype chain.
Example of shadowing:
const obj = {};
obj.hasOwnProperty // => ƒ hasOwnProperty() { [native code] }
obj.hasOwnProperty = 'don\'t do this!';
obj.hasOwnProperty // => "don't do this!"
Consider some example below:
const object = { hasOwnProperty: false }
console.log(object.hasOwnProperty(key)) // Error: hasOwnProperty is not a function
or
const object = null
console.log(object.hasOwnProperty(key)) // Error: Can not read property of null
So you can understand shallowed in this case is your object methods in prototype is shallowed by an object property (has the same name)
See an example. Here I have created an function with name hasOwnProperty directly on the my object. So it hides the parent version of the hasOwnProperty. In it I write a logic which will return everytime true. So anybody who will use my object and tries to detect if it has some property in it (he/she doesn't know I have shadowed the base on) he can have a logic errors in his/her code. Because JS will try to find the function first in the object and call that version which actually does another work.
But when you call this method from the Object.prototype, which is the correct version of the method, and pass the context to it, it will work correctly, because it will call the Object.prototypes method with name hasOwnProperty and just pass this object as the context of the method. So from here is the warning that you must use this methods from the prototype.
Actually you can also change the Object.prototype version of the method, but there is a rule, Not change the prototypes of the built in objects.
const object = {
hasOwnProperty: function() {
return true;
}
};
console.log(object.hasOwnProperty('name'));
console.log(Object.prototype.hasOwnProperty.call(object, 'name'));
The first log says that there is a property name in the object, but it isn't. This may cause a logic error. The second one uses the correct version and gives a correct result.
I'm working on an AngularJS SPA and I'm using prototypes in order to add behavior to objects that are incoming through AJAX as JSON. Let's say I just got a timetable x from an AJAX call.
I've defined Timetable.prototype.SomeMethod = function() and I use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf in order to set the prototype of x to TimeTable.prototype. I have the polyfill in place too.
If I call x.SomeMethod() this works in IE > 9, FF, Chrome etc. However, IE 9 gives me a headache and says throws an error stating 'x does not have property or member SomeMethod'.
Debugging in IE shows me that the _proto_ of x has SomeMethod() in the list of functions, however, calling x.SomeMethod() gives the same error as described.
How can I make this work in IE9 ?
More comment than answer
The main problem with "extending" a random object retrieved from some other environment is that javascript doesn't really allow random property names, e.g. the random object may have a property name that shadows an inherited property. You might consider the following.
Use the random object purely as data and pass it to methods that access the data and do what you want, e.g.
function getName(obj) {
return obj.name;
}
So when calling methods you pass the object to a function that acts on the object and you are free to add and modify properties directly on the object.
Another is to create an instance with the methods you want and copy the object's properties to it, but then you still have the issue of not allowing random property names. But that can be mitigated by using names for inherited properties that are unlikely to clash, e.g. prefixed with _ or __ (which is a bit ugly), or use a naming convention like getSomething, setSomething, calcLength and so on.
So if obj represents data for a person, you might do:
// Setup
function Person(obj){
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
this[p] = obj[p];
}
}
}
Person.prototype.getName = function(){
return this.name;
};
// Object generated from JSON
var dataFred = {name:'fred'};
// Create a new Person based on data
var p = new Person(dataFred);
You might even use the data object to create instances from various consructors, e.g. a data object might represent multiple people, or a person and their address, which might create two related objects.
This is how I solved it at the end:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
if (!isIE9()) {
obj.__proto__ = proto;
} else {
/** IE9 fix - copy object methods from the protype to the new object **/
for (var prop in proto) {
obj[prop] = proto[prop];
}
}
return obj;
};
var isIE9 = function() {
return navigator.appVersion.indexOf("MSIE 9") > 0;
};
This is a purely trivial question for academic value:
If I create a new object, either by doing:
var o = { x:5, y:6 };
or
var o = Object.create({ x:5, y:6 });
when I query the o.prototype property, I get undefined. I thought that any newly created object automatically inherits the Object.prototype prototype.
Furthermore, invoking toString(), (a method of Object.prototype) on this object works just fine, implying that o does inherit from Object.prototype. So why do I get undefined?
There is a difference between instances and their constructors.
When creating an object like {a: 1}, you're creating an instance of the Object constructor. Object.prototype is indeed available, and all functions inside that prototype are available:
var o = {a: 1};
o.hasOwnProperty === Object.prototype.hasOwnProperty; // true
But Object.create does something different. It creates an instance (an object), but inserts an additional prototype chain:
var o = {a: 1};
var p = Object.create(o);
The chain will be:
Object.prototype - o - p
This means that:
p.hasOwnProperty === Object.prototype.hasOwnProperty; // true
p.a === o.a; // true
To get the prototype "under" an instance, you can use Object.getPrototypeOf:
var o = {a: 1};
var p = Object.create(o);
Object.getPrototypeOf(p) === o; // true
Object.getPrototypeOf(o) === Object.prototype; // true
(Previously, you could access an instance's prototype with o.__proto__, but this has been deprecated.)
Note that you could also access the prototype as follows:
o.constructor === Object; // true
So:
o.constructor.prototype === Object.prototype // true
o.constructor.prototype === Object.getPrototypeOf(o); // true
This fails for Object.create-created objects because they do not have a constructor (or rather, their constructor is Object and not what you passed to Object.create because the constructor function is absent).
Not a direct answer, but knowledge that everybody, who deal with inheritance in Javascript, should have.
Prototype inheritance in Javascript is a tricky concept. Up until now, it has been impossible to create an empty object (by empty I mean lacking even properties form Object via prototype). So this means that creating a new object always had a link to the original Object prototype. However, according to the specification, the prototype chain of an object instance isn't visible, but some vendors have decided to implement their own proprietary object properties so that you could follow it, but it's highly recommended not to use it in production code.
The following sample code demonstrates just two ways of creating an object instance.
var someObject = {};
var otherObject = new Object();
var thirdObject = Object.create({});
Even though you don't manually add object properties to empty curly braces, you still get automatically added prototype chain. The same goes for the second example. To visualize it better, you can type those lines into Chrome console and then enter either someObject, otherObject or thirdObject to see details. Chrome shows the prototype chain by adding a proprietary property __proto__ which you can expand to see what is inherited and where it's from. If you executed something like
Object.prototype.sayHello = function() {
alert('hello');
};
you would be able to call it on all instances, by executing otherObject.sayHello().
However, using something that was implemented quite recently (therefore not supported by all browsers), you can actually create a truly empty object instance (doesn't inherit even from Object itself).
var emptyObject = Object.create(null);
When you enter it into Chrome console and then expand the emptyObject to see it's prototype chain, you can see that it doesn't exist. So even if you implemented the sayHello function to Object prototype, it would be impossible to call emptyObject.sayHello() since emptyObject does not inherit from Object prototype.
Hope it helps a bit with the general idea.
JavaScript has two types of objects: function object and non-function object. Conceptually, all objects have a prototype (NOT A PROTOTYPE PROPERTY). Internally, JavaScript names an object's prototype as [[Prototype]].
There are two approaches to get any object (including non-function object)'s [[prototype]]: the Object.getPrototypeOf() method and the __proto__ property. The __proto__ property is supported by many browsers and Node.js. It is to be standardized in ECMAScript 6.
Only a function (a callable) object has the prototype property. This prototype property is a regular property that has no direct relationship with the function's own [[prototype]]. When used as a constructor ( after the new operator), the function's prototype property will be assigned to the [[Prototype]] of a newly created object. In a non-function object, the prototype property is undefined . For example,
var objectOne = {x: 5}, objectTwo = Object.create({y: 6});
Both objectOne and objectTwo are non-function objects therefore they don't have a prototype property.