JavaScript Delete() Only Affects The Referenced Object Regardless Of Prototype Chain

Posted June 2, 2011 at 10:45 AM by Ben Nadel

Tags: Javascript / DHTML

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.




Reader Comments

Jun 2, 2011 at 12:54 PM // reply »
260 Comments

@Ben,

How about:

  • function steroidsDelete(obj, prop)
  • {
  • if (prop in obj)
  • if (obj.hasOwnProperty(prop))
  • {
  • delete obj[prop];
  • // delete even shadowed props:
  • if (prop in obj)
  • steroidsDelete(obj, prop);
  • }
  • else
  • steroidsDelete(obj, prop);
  • }

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. :-)


Jun 2, 2011 at 1:01 PM // reply »
10,743 Comments

@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):

  • function deleteProp( object , prop ){
  •  
  • delete( object[ prop ] );
  •  
  • // Check to see if property still detected.
  • if (prop in object){
  •  
  • // Move up the prototype chain and try again.
  • deleteProp( object.getPrototype(), prop );
  •  
  • }
  •  
  • }

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.


Jun 2, 2011 at 1:14 PM // reply »
260 Comments

@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.


Jun 2, 2011 at 1:23 PM // reply »
10,743 Comments

@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.


Jun 2, 2011 at 3:02 PM // reply »
260 Comments

@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.


Jun 2, 2011 at 3:10 PM // reply »
260 Comments

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.


Jun 2, 2011 at 3:12 PM // reply »
260 Comments

The standard where I work, that is.


Jun 2, 2011 at 5:16 PM // reply »
10,743 Comments

@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.


Jun 2, 2011 at 7:56 PM // reply »
260 Comments

@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.


Jun 3, 2011 at 3:04 PM // reply »
10,743 Comments

@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:

http://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.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 21, 2012 at 1:58 AM
Updated: Converting A ColdFusion Query To CSV Using QueryToCSV()
Hi Ben, why do you need to have so many double quotes when adding the field and field name to the row data? ----------------------------------------- <cfset LOCAL.RowData[ LOCAL.ColumnIndex ] = ... read »
AXL
May 21, 2012 at 1:24 AM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Mounir, Open your lower case URL Rewrite rule and add the following condition. Condition input: {REQUEST_URI} Check if input string: Does Not Match the Pattern Pattern: ^/CFFileServlet/_cf_ca ... read »
May 20, 2012 at 4:28 AM
Understanding The Complex And Circular Relationships Between Objects In JavaScript
@Will Vaughn I tried your javascript example but got this error:- foo.print is not a function ... read »
May 19, 2012 at 5:37 AM
A Graphical Explanation Of Javascript Closures In A jQuery Context
Thanks for this article, but I fear you missed an important point. If variables in the outer context change, these changes affect the inner anonymous functions as well. That means: if you change the ... read »
May 18, 2012 at 3:39 PM
Parsing CSV Data With An Input Stream And A Finite State Machine
Can you use file upload button with this? and read live? or does the file have to already be on the server saved? ... read »
May 18, 2012 at 1:06 AM
VIRGO (Aug. 23-Sept. 22): Dead On The Money!
A friend of mine and I were arguing about astrology and she told me that he believes in astrology. She hasn't provided me with any evidence that the belief makes any sense to me. She she been telling ... read »
May 17, 2012 at 11:32 PM
Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server
Very easy the configuration. I read a lot pages and I can't find the solution. I open the administrator and change this Administrator/server settings/Error Handlers/Missing Template Handler and p ... read »
May 17, 2012 at 3:13 PM
LOCAL Variables Scope Conflicts With ColdFusion Query of Queries
I never cease to be amazed that almost EVERY random CF issue I come across lands me on your site. Thank you for documenting your findings for the world. ... read »