JavaScript Delete() Only Affects The Referenced Object Regardless Of Prototype Chain
Yesterday, I blogged about extending the window (global) object in JavaScript. In that post, I used the delete() function (or keyword if you like) to alter various parts of the resulting prototype chain in order to create a dynamic runtime environment. That approach was possible because of a feature that I learned from Cody Lindley: the delete() function only affects the referenced object, regardless of the prototype chain that contains said object. Unlike property references, which will travel up the prototype chain looking for values, a call to the delete() function will only affect the given reference.
To see this behavior in action, take a look at the following demo. We're going to create a prototype chain that has a "name" property defined on several objects within that prototype chain. Notice how calls to delete() interact with subsequent property references:
<!DOCTYPE html>
<html>
<head>
<title>Delete() Only Affects Referenced Object In Prototype Chain</title>
<script type="text/javascript">
// Create a "super" object that another object will extend
// (via the prototype chain).
var superObject = {
name: "Super Object"
};
// Create an object that extends the super object.
myObject = Object.create( superObject );
// Check to see what the current name value is.
console.log( "1 - Name:", myObject.name );
// Now, overwrite the name value - this will store the name
// property in the lowerst object in the prototype chain
// (myObject).
myObject.name = "My Object";
// Check to see what the current name value is.
console.log( "2 - Name:", myObject.name );
// Now, delete the name property.
delete( myObject.name );
// Check to see what the current name value is.
console.log( "3 - Name:", myObject.name );
// The previous delete removed the "name" property from the
// lowest object in the prototype chain (the one referenced
// by the myObject variable). Now, try to call delete again.
delete( myObject.name );
// Check to see what the current name value is.
console.log( "4 - Name:", myObject.name );
// Now, delete the name from the super object reference.
delete( superObject.name );
// Check to see what the current name value is.
console.log( "5 - Name:", myObject.name );
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
As you can see, we getting and setting the name property on the various object within this prototype chain. When we run the above code, we get the following console output:
1 - Name: Super Object
2 - Name: My Object
3 - Name: Super Object
4 - Name: Super Object
5 - Name: undefined
Really, the loggings of interest in the above output are line items 3 and 4. In 3, we had just called delete() on myObject. This removed the "name" property from the myObject instance, leaving the "name" property on the superObject instance intact. As such, a reference to the name property in 3 resulted in "Super Object."
In line item 4, we had just called delete() again on the myObject instance. A subsequent reference to the "name" property, however, resulted in "Super Object." As you can see, multiple invokations of delete() on the myObject reference do not alter "name" properties located higher up in the prototype chain. In fact, it is only after we call delete() directly on the superObject instance that we are able to completely remove the "name" property from the object (as an abstract whole).
This behavior leaves us in an interesting position: on the one hand, property references will travel up the prototype chain looking for values; but, on the other hand, property deletes will only affect the directly referenced object. As such, it's not hard to imagine someone implementing this kind of logic and getting unexpected results:
if (someObject.property){
delete( someObject.property );
}
If the containing algorithm expected the delete() to change the outcome of subsequent calls to the same piece of control flow, the structure of the prototype chain could potentially throw a monkey wrench into the logic. Depending on what your code is doing, a good way to deal with this asymmetric traversal mechanism would be to use the Object.hasOwnProperty() method in conjunction with the property reference:
if (
someObject.hasOwnProperty( "property" ) &&
someObject.property
){
delete( someObject.property );
}
This way, the delete() works at the given object level and the hasOwnProperty() method ensures that the property lookup will also work at the given object level.
This asymmetric prototype traversal behavior has never actually caused me any problems. Typically, I only use delete() in conjunction with objects that exist outside of any complex prototype chain (ie. object literals specifically designed for value indexing). But, understanding this JavaScript behavior will certainly make me a bit more cognizant about when it's a good idea to apply the hasOwnProperty() method.
Want to use code from this post? Check out the license.
Reader Comments
@Ben,
How about:
That's off the top of my head. Probably syntactically incorrect. And apologies to those who are addicted to one-statement braces. And I don't think I'll ever figure out how to indent things right in your code blocks here. But you get the idea.
You don't want to do anything if the prop string isn't the name of a property somewhere in the prototype chain. If it is, however, that makes the rest of it a well-founded function. You'll eventually hit the delete.
Sorry if steroids is a touchy prefix for a bodybuilder. :-)
@WebManWalking,
I haven't run this, but I think this may throw you into an infinite loop. Unless you can get access to the object's prototype reference, it doesn't look like you'll ever be able to progress up the prototype chain. I think you would need to do something like this (pseudo code):
Of course, there is no function "getPrototype()" and things like __proto__ are not cross-browser compatible. I have seen, however, in some of the MVC frameworks, that they actually add a prototype feature into the object properties during class creation.
@Ben, you're right. I forgot to move up the prototype chain, which would have required a lot of cross-browser feature sniffing. Thanks for the correction.
You got the idea, though. That's the main thing.
@WebManWalking,
No problem, I figured that was what you meant. It would be cool if gaining access to the prototype chain was a bit easier in general.
@Ben,
From the ECMAScript version 5 spec:
15.2.3.2 Object.getPrototypeOf ( O )
When the getPrototypeOf function is called with argument O, the following steps are taken:
1. If Type(O) is not Object throw a TypeError exception.
2. Return the value of the [[Prototype]] internal property of O.
Here's the document:
http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf
I just now tried the following in the Big Five browsers:
javascript:alert(Object.getPrototypeOf(Object.create({name:"val"})));
The inner Object.create assured that I was referencing an object that had a prototype. Here were the results:
(1) Firefox 4.0.1 - alert with [object Object]
(2) Google Chrome 12.0 - alert with [object Object]
(3) MSIE 7.0 - threw an error (Object doesn't support this property or method), of course
(4) Opera 11.11 - didn't do anything (probably threw an erro, but I don't have Dragonfly installed there yet)
(5) Safari 5.0.5 - alert with [object Object]
So we're almost there.
P.S.: I can't upgrade my MSIE because version 7 is the standard for [non-public-facing] intranet pages, but I'd be very curious about getPrototypeOf support in versions 8 and 9.
The standard where I work, that is.
@WebManWalking,
Very cool. I've never even heard of the Object.getPrototypeOf() method. We're definitely getting closer. Unfortunately, I'm not sure how easy it would be to fill in the gaps in the older browsers.
In the end, though, it's probably a good thing that delete() doesn't crawl up the prototype chain. If it did, it would probably lead to more problems than if it didn't :) I'll leave that kind of philosophy up to the smarter people who come up with standards.
@Ben,
Yes, steroidsDelete(myArray, "push") might have repercussions elsewhere in your code. :-)
It's fun to wander around and find new stuff in the ECMA documentation. (Aside: I said version 5. It's technically edition 5.) One of the related things I saw was that, although getting a value walks the prototype chain, setting a value does not. That's exactly analogous to delete not walking the prototype chain.
Whenever I find something useful in the ECMA PDF, I'm immediately off to do location bar experiments in the Big Five.
@WebManWalking,
Yeah, setting a value always sets into the lowest object in the prototype chain. However, you have to take special caution with things like Objects and Arrays. If you have Object, "someObject", higher up in the prototype chain:
myInstance.someObject.key = value;
... puts "key" into the prototype-level someObject. Meaning, that change will be replicated in all objects that extend that level of prototype. Since a the "set" is not on the instance itself, but rather on a property of that instance, the prototype structure doesn't dictate behavior.
That's messed me up a few times :) That's when I learned that calling super-constructors is absolutely critical:
www.bennadel.com/blog/1566-Using-Super-Constructors-Is-Critical-In-Prototypal-Inheritance-In-Javascript.htm
JavaScript is some fun stuff! Always a good mental exercise.