Extending Object with a Non-Enumerable Property - javascript

I was reading the readme for should.js and got to the "OMG IT EXTENDS OBJECT???!?!#" section which says
Yes, yes it does, with a single getter should,
and no it won't break your code,
because it does this properly with a non-enumerable property.
I've always heard you shouldn't extend the object prototype but never really understood why.
Can someone explain why, in general, extending object is bad (what can it break)? And also explain what a non-enumerable property is and how this helps?

The issue is with extending Object.prototype. If the extension is enumerable, then it will be encountered in every enumeration of every object when using for-in.
Object.prototype.foo = "bar";
var empty_object = {};
for (var p in empty_object) {
alert(p + " " + empty_object[p]);
}
Clearly this can mess with your code. You can use .hasOwnProperty() in every enumeration of every object property, but there's a performance impact. Also sometimes you actually want to include inherited properties in your enumeration, but this makes it impossible, or at least very tough.
You should also note that just because the property is marked non-enumerable, that doesn't mean that it won't break your code.
What if you have an object, and you need to get the value of the .should property if it exists? (Where .should is expected to have some other meaning in the code.)
var val = my_object.should;
if (val) {
// do something
} else {
// do something else
}
Trouble is that the .should property will now always return the value of the inherited property if there isn't one directly on my_object.
This issue exists for the native Object.prototype extensions, but at least those names are commonly known, and therefore habitually avoided.
It's good that the library documented this extension, but it's still pretty short-sighted to claim that it won't break your code.
Least they could do would be to make it an optional extension.

Related

"Object.prototype" injecting values ​into the iteration

First of all, I want to say that I know that extending global methods is not a good practice, but there are situations where a developer needs to avoid duplicating objects due to size or other reasons, and global functions can help a lot in such situations.(at least that's what I believe)
I raised this topic because I think it's strange that this "bug" (example below) still exists in JavaScript, because if I'm not mistaken about 5 years ago I needed to traverse a window object with a loop and I ended up finding it, but since so much time has passed and it still exists I think it might be normal for Object.prototype
Can anyone tell me if it's a forgotten bug or is it a normal occurrence? can't this be exploited as code injection along with this?
Object.prototype.foo = function(){
for(index in this) console.log(index)
}
var A = [1,2,3];
//returns a non-iterable index at the end
A.foo();
function bar(attr){
var B = {a:1, b:2,c:3}
for(index in B) console.log(index);
for(index in attr) console.log(index);
}
//all the 'in' iterations will start printing the index value at the end
bar([1,2,3,4,5]);
for(index in {x:1, y:2,z:3}) console.log(index);
This is completely normal and expected.
for..in iterates over all enumerable own properties on the object in question. Then it does the same for that object's internal prototype, and so on - until it reaches the beginning of the prototype chain (which is often Object.prototype).
If you put an enumerable property onto Object.prototype, then any object (that inherits from Object.prototype) you iterate over with for..in will eventually iterate over that property you added.
can't this be exploited as code injection along with this?
I suppose - malicious code can modify Object.prototype and do a whole lot of other nasty things, so take care not to run anything that isn't trustworthy.
Accidental prototype pollution (even occasionally from user input) isn't an uncommon problem that libraries sometimes run into. A good rule of thumb is to use static key names everywhere - if you need something dynamic, use a Map instead of an object.
The behavior you're observing is normal. The properties defined on Object.prototype will be prototype linked to by its instances. The for..in will iterate over all enumerable properties of an object, whether owned or inherited.
You can access foo at A.__proto__.__proto__.foo.
A is an instance of Array[], which is an instance of the global Object{}.
From MDN Web Docs:
The enumerable property attribute defines whether the property is picked by Object.assign() or spread operator. For non-Symbol properties it also defines whether it shows up in a for...in loop and Object.keys() or not.
You can define the property as an innumerable attribute by setting the enumerable flag to false.
Object.defineProperty(Object.prototype, "foo", {
value: function(){
for(index in this) console.log(index);
},
configurable: true,
enumerable: false
});
You could freeze the the Object.prototype by:
Object.freeze(Object.prototype);
Object.freeze(Object);
to prevent attackers from polluting it.

Is it possible to use Symbol.species for plain objects?

The MDN gives the following working example of Symbol.species:
class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
The ECMAScript 2015 specifications says:
A function valued property that is the constructor function that is used to create derived objects.
The way I understand it it would be mainly used to Duck-Type in some way custom objects in order to trick the instanceof operator.
It seems very useful but I have not managed to use it on plain objects (FF 44.0a2):
let obj = {
get [Symbol.species]() {
return Array
}
}
console.log(obj instanceof Array) //false =(
console.log(obj instanceof Object) //true
Is there any way to use Symbol.species on plain objects in order to trick the instanceof operator?
The way I understand it it would be mainly used to Duck-Type in some way custom objects in order to trick the instanceof operator.
Nope, that's what Symbol.hasInstance is good for (though it makes tricky constructors, e.g. for mixins, not tricky instances).
The point of Symbol.species is to let built-in methods determine the proper type for derived objects. Whenever a function is supposed to return a new instance of the same kind, it usually instantiates a new this.constructor, which might be a subclass of the class that defined the method. But you might not always want this when subclassing, and that's where Symbol.species comes in.
The example given by MDN is quite good, you just must not miss the distinction between a and mapped:
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
Object.getPrototypeOf(a) == MyArray.prototype; // true
Object.getPrototypeOf(mapped) == Array.prototype; // true
(As you know, instanceof is was just sugar for the reverse of isPrototypeOf)
So what happens here is that the Array map method is called on an instance of MyArray, and creates a derived object that is now an instance of Array - because a.constructor[Symbol.species] said so. Without it, map would have created another MyArray.
This trick would work with plain objects just as well:
var b = {
length: 0,
map: Array.prototype.map,
constructor: { // yes, usually a function
[Symbol.species]: MyArray
}
};
var mapped = b.map(x => x*x);
console.log(mapped instanceof MyArray); // true
I can't say whether it's any useful in this example, though :-)
Good question. I admit that I'm not 100% sure, but I've gotten the impression that the answer is no.
I used two methods to determine this answer:
Finding an environment that clearly supports all of the features necessary to test this, and running the code in that environment directly
Referencing the spec
The first point wasn't easy to test, and I wasn't entirely sure that I successfully did it. You used Firefox 44, but I decided to test another environment due to the fact that Kangax's support table says it only supports the existence of Symbol.species, and none of the Array-like features. To see this, you can expand the Symbol.species section in the link above.
In this case, even Kangax is a bit confusing. I would expect there to be non-Array specific tests for Symbol.species aside from the existence test, but maybe I'm just not knowledge enough to understand that those tests cover all of the functionality of Symbols. I'm not sure!
While browsing the table, I saw that Edge supports all of the Symbol.species features. I loaded up my Windows VM, hopped on over to Edge 20, and pasted your code.
In Edge 20, the code had the same result as your test in Firefox 44.
So this began to give me confidence that the feature doesn't work in the way that you want it to. Just to make sure that Edge had its JavaScript in order, I decided to run the code from the MDN article you linked, and...I got a syntax error! It appears Edge is still updating its Class implementation due to late-in-the-game changes to the Class spec, so it's not enabled.
Their current implementation is, however, available through an experimental flag. So I flipped that on and was able to reproduce the result that MDN got.
And that was the extent of my JavaScript environment testing. I don't have 100% confidence in this test, but I would say that I'm fairly confidence in the result.
So onto the second method I used to determine my result: the specification.
I admit that I'm no expert at the ECMAScript specification. I do my best to understand it, but it is likely that I might misinterpret some of the information in there. The reason for this is because I'm not an ECMAScript master, so some of the technical jargon used is just way over my head.
Nevertheless, I can sometimes get the gist of what is being said. Here's a snippet that's about the Symbol ##species:
Methods that create derived collection objects should call ##species to determine the constructor to use to create the derived objects. Subclass constructor may over-ride ##species to change the default constructor assignment.
The phrase "derived objects" is used quite frequently in describing this symbol, but it's not defined anywhere. One might interpret it to be a synonym of sorts with instantiated. If that's a correct interpretation, then this can likely only be used with constructors or classes.
Either way, it sounds as if ##species is an active Symbol, rather than a passive one. By this I mean that it sounds like it's a function that's actually called during the creation of an Object, which is then what determines the instanceof value, rather than a function that's called at the time of instanceof being called.
But anyway, take that interpretation with a grain of salt. Once again, I'm not 100% certain of any of this. I considered posting this as a comment rather than an answer, but it ended up getting quite long.

Objects with their own intrinsic value. Another way to do getters

I wanted to be able to create an object that had it's own intrinsic value. That way I could use it in an expression as if it was a regular variable. It turned out to be quite easy - so easy I am wondering if I am missing something. All it seems to take is to make sure the object contains a method "valueOf". Like so:
var p = {
a: 12;
valueOf: function(){return this.a+12}
}
alert(p/3); // =8
It's probably a good idea to include a "toString" method too. What seems to be happening is that without the valueOf method, a reference to the object goes up the prototype chain until it finds the method on Object, which just returns "object Object". By providing a local one, the method can return anything.
It seems to work in all browsers that I tried.
It is really another way to provide a getter-like functionality. But a little better because getters can only be applied to properties of objects. This way you can use the object itself.
What do you think? Is this safe? Any pitfalls?
Well I started using this pattern and found one issue. If you create an object p like the one above, you have to guess how the interpreter will view a reference to the object.
For example, if P is used in arithmetic expression, it calls the object's valueOf() method to get its value. But when you try to access a property of p such as p.a, then it treats p as an object and retrieves the property as you expect.
In a getter, by contrast, accessing p.a when there is a getter on p would first run the getter and then try to get the property 'a' of the result. Subtly different, but important.
It's all quite correct and seems to "guess" correctly what to do. But using the pattern does rely on knowing this behavior, which AFAIK is not documented anywhere.

Pitfalls using DOM-Javascript VS pure Javascript objects?

Background
First of, I would like to mention, that a SO-search for figuring out differences of
(host objects) DOM-Javascript Objects (var domJSobj = document.createElement("div")) and
(native objects) pure Javascript Objects (var pureJSobj = {})
has been done, but still I cannot claim that I found very much (great is, that there is this question-answers: what-is-the-difference-between-native-objects-and-host-objects). Maybe I overlooked something here, then a comment is very appreciated.
I also read MDN Mozillas Docu on DOM and Javascript to get some idea before asking here.
Question
Being a little "glueless" I started playing in the Javascript Console and I encountered some first pitfall (arising from expectations I have regarding the behaviour of (native objects) which are not true for (host objects)). To be clear many things -at first sight- seem similar:
var nativeObject = {}; //create new object
nativeObject.someAttribute = "something"; //set some attribute
nativeObject.someMethod = function() { return this.someAttribute; } // define some method
console.log(nativeObject.someAttribute); // "something"
console.log(nativeObject.someMethod()); // "something"
var hostObject = document.createElement("div"); //create a "div" one type of host object
hostObject.someAttribute = "something"; //set some attribute
hostObject.someMethod = function() { return this.someAttribute; } // define some method
console.log(hostObject.someAttribute); // "something"
console.log(hostObject.someMethod()); // "something"
For me it seems at first sigth that a (host object) is just as usable as a (native object) and has already more:
console.log(nativeObject.tagName); // undefined
console.log(hostObject.tagName); // "DIV"
as it has the all the nice DOM-attributes already there.
My Question is about a list of pitfalls (possible troubles) that can arrise if I decide to (mis)use a (host object) instead of simple (native object)?
already I am aware of this:
speed considerations (I assume that native objects are created more quickly)
memory usage consideratoins (native objects might be better here too)
Anyways I cannot see much trouble (if those considerations are not very important in a case) yet? This is the reason for the question.
I think answers to this question would be best showing/telling one potential pitfall, that is a case or situation in which it is bad or impossible to use a (host object) over a (native object). In this way this question should adhere to the SO-quality requirement of being passed on real facts and not provoking mere opinion-based-discussion.
For me, the pitfall of using a DOM-based object is that from the moment it's created it already has dozens of properties. Your document.createElement("div") object has an id property and a title property and a style property that is an object of its own, etc. If you want to use a DOM-based object to store arbitrary data, you need to be careful not to name your property something that already exists or you may get unexpected results; for example in my tests in Chrome, the code hostObject.id = null would actually cause hostObject.id to be "" rather than true null; or hostObject.id = false would cause it to be "false" (i.e. the string, not true false) for the hostObject variable defined in your code.
Try adding this to your code:
for (property in document.createElement("div"))
console.log(property);
In Chrome, I see 134 properties listed. That's a lot of landmines to potentially avoid. And beyond that, the properties differ between browsers. The same code in Firefox lists 192 properties.
I would only create a DOM object if you actually intend to use it as a DOM object. Then the properties have some purpose; otherwise they're just potential bugs in your code. Like the comments say, KISS; I mean, sure, I could decide to save the integer value 123456789 within a Date object:
myInt = new Date(123456789);
myInt.getTime(); // returns 123456789
But what's the point of that? Why not just do myInt = 123456789? Just as I wouldn't use a Date object to store integer data, don't use a DOM object to store arbitrary data.

Why doesn't my object inherit the keys functions?

If I have an object:
A = {a:true}
Why do I have to use:
Object.keys(A)
and not:
A.keys()
If keys is a method of Object, and everything inherits from Object, shouldn't A be able to call keys?
Object.keys is a so-to-say "static" method attached strictly to the Object function, not to its instances.
For it to be inherited, it would need to be defined as Object.prototype.keys.
You can certainly add it yourself if you so desire:
Object.prototype.keys = function () {
return Object.keys(this);
};
Just note, as Rocket mentioned in the comments, "own" properties take precedence over prototype properties:
var foo = {};
foo.keys(); // Array of enumerable key names, if any
var bar = { keys: true };
bar.keys(); // TypeError: not a function
The real answer is "because that's how the ES committee members designed it." But specifically, the second comment on your original question (by #RobW) is exactly what they wanted to avoid.
Moreover, I would ask that you remove the "accepted answer" designation from #JonathonLonowski's answer, because he is clearly giving (and avidly but unwisely defending) advice (modifying built-in prototype's) which most of the web community knows to be of poor form. The ONLY time when it's OK to modify a built-in's prototype is when you are absolutely certain that no other code will trip over it... the only time that is true is you write 100% of all code that runs (or ever will run) on a page. The number of times that is true is so low that this advice is considered not-good-practice.

Categories