I won't know this either, go home and farm.
The code copy is as follows:
delete thisIsObject[key]
or
delete thisIsObject.key
By the way, let's talk about the usage of delete
A few weeks ago, I had a chance to read Stoyan Stefanov's Object-Oriented Javascript book. This book is highly rated on Amazon (12 reviews, 5 stars), so I was curious to see if it was such a recommended book, so I started reading the chapter of functions. I really appreciate the way this book explains things, and the examples are organized in a very beautiful, gradual way, and it seems that even beginners can easily master this knowledge. However, almost immediately, I discovered an interesting misunderstanding throughout the chapter - deleting functional functions. There are also some other errors (such as the difference between function declarations and function expressions), but we will not discuss them at this time.
The book claims:
"The function is treated like a normal variable - it can be copied into different variables, or even deleted". An example is attached to this explanation:
The code copy is as follows:
var sum = function(a, b) {return a + b;}
var add = sum;
delete sum
true
typeof sum;
"undefined"
Ignore some missing semicolons, can you see where the errors in these codes are? Obviously, the error is that the operation of deleting the sum variable will not succeed. The delete expression should not return true, and the typeof sum should not return "undefined". All this is because it is impossible to delete variables in JavaScript. At least, it is impossible in this way of declaration.
So, what exactly happened in this example? Is it a bug? Or a special usage? Probably not. This code is actually the real output in the Firebug console, and Stoyan must have used it as a tool for quick testing. It's almost like Firebug follows some other delete rules. It's Firebug that caused Stoyan to go astray! So, what exactly happened here?
Before answering this question, we first need to understand how the delete operator works in JavaScript: what exactly can be deleted and what can't be deleted? Today, I'll try to explain this in detail. We'll look at the "strange" behavior of Firebug and realize that it's not that weird. We'll dig deeper into what's hidden behind the scenes where variables, functions, assign values to attributes and delete them. We'll look at browser compatibility and some of the most notorious bugs. We'll also discuss the strict pattern of ES5, and how it changes the behavior of delete operators.
I'm going to swap JavaScript and ECMAScript, both of which mean ECMAScript (unless it's obvious that Mozilla's JavaScript implementation)
As expected, on the Internet, explanations of delete are quite scarce. The MDC article is probably the best resource to understand, but, unfortunately, it lacks some interesting details of the topic. Strangely, one of the forgotten things is the reason for the strange manifestation of Firebug. And the MSDN reference is almost useless in these aspects.
Theory
So, why are we able to delete the properties of an object:
The code copy is as follows:
var o = { x: 1 };
delete ox; // true
ox; // undefined
But the object declared like this cannot be deleted:
The code copy is as follows:
var x = 1;
delete x; // false
x; // 1
Or the function:
The code copy is as follows:
function x(){}
delete x; // false
typeof x; // "function"
Note: When a property cannot be deleted, the delete operator will only return false.
To understand this, we first need to master these concepts about variable instances and attribute properties - these concepts are unfortunately rarely mentioned in JavaScript books. I will try to briefly review these concepts in the next few paragraphs. These concepts are difficult to understand! If you don't care "why these things work this way", just skip this chapter.
Type of code:
In ECMAScript, there are 3 different types of executable code: Global code, Function code and Eval code. These types are more or less self-explanatory in terms of name, here is a brief overview:
When a piece of source code is regarded as a program, it will be executed in a global environment and is considered global code. In a browser environment, the contents of script elements are usually interpreted as programs and are therefore executed as global code.
Any code that is executed directly in a function is obviously considered Function code. In a browser, the content of event properties (such as <p onclick="...">) is usually interpreted as function code.
Finally, the code text applied to the built-in function eval is interpreted as Eval code. Soon we will find out why this type is special.
Execution context:
When ECMAScript code is executed, it usually occurs in a specific execution context. The execution context is a somewhat abstract entity concept, which can help understand how the scope and variable instances work. For each of the three executable codes, there is an execution context corresponding to it. When a function is executed, we say "program control enters the execution context of the function code"; when a piece of global code is executed, program control enters the execution context of the global code, etc.
As you can see, the execution context can logically form a stack. First, there may be a piece of global code and its own execution context, and then this piece of code may call a function with its (function) execution context. This piece of function can call another function, etc. Even if the function is called recursively, it will be entered into a new execution context each time it is called.
Active object (Activation object) / Variable object:
Each execution context has a so-called Variable Object associated with it. Similar to the execution context, a variable object is an abstract entity, a mechanism used to describe variable instances. Interestingly, variables and functions declared in the source code are usually added to this variable object as properties.
When program control enters the execution context of global code, a global object is used as a variable object. This is exactly why function variables declared global become global object properties.
The code copy is as follows:
/* remember that `this` refers to global object when in global scope */
var GLOBAL_OBJECT = this;
var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true
function bar(){}
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true
OK, so global variables will become properties of global objects, but what happens to local variables (those defined in function code)? In fact, they behave very similarly: they will become properties of variable objects (Variable objects). The only difference is that when in function code, a variable object is not a global object, but a so-called Activation object. The active object is created every time it enters the execution context of the function code.
Not only variables and functions declared in function code will become properties of the active object; this will also occur on each function parameter (the name corresponding to the corresponding formal parameter) and a special Arguments object (the name of arguments). Note that the active object is an internal description mechanism and cannot be accessed in program code.
The code copy is as follows:
(function(foo){
var bar = 2;
function baz(){}
/*
In abstract terms,
Special `arguments` object becomes a property of containing function's Activation object:
ACTIVATION_OBJECT.arguments; // Arguments object
...as well as argument `foo`:
ACTIVATION_OBJECT.foo; // 1
...as well as variable `bar`:
ACTIVATION_OBJECT.bar; // 2
...as well as function declared locally:
typeof ACTIVATION_OBJECT.baz; // "function"
*/
})(1);
Finally, variables declared in Eval code become properties of variable objects in the caller context. Eval code simply uses variable objects in the execution context of the code that calls it.
The code copy is as follows:
var GLOBAL_OBJECT = this;
/* `foo` is created as a property of calling context Variable object,
which in this case is a Global object */
eval('var foo = 1;');
GLOBAL_OBJECT.foo; // 1
(function(){
/* `bar` is created as a property of calling context Variable object,
which in this case is an Activation object of containing function */
eval('var bar = 1;');
/*
In abstract terms,
ACTIVATION_OBJECT.bar; // 1
*/
})();
Property attributes
We are almost here. Now that we are well aware of what is happening with variables (they become properties), the only remaining concept to be understood is property attributes. Each attribute can have 0 or more properties, which are selected from the following sets: ReadOnly, DontEnum, DontDelete and Internal. You can think of them as flags - a feature that can exist or not exists in properties. For our discussion today, we are only interested in DontDelete.
When declared variables and functions become attributes of variable objects (or active objects of function code, or global objects of global code), these attributes are created with the DontDelete attribute attribute. However, any explicit (or implicit) attributes will not be included with the DontDelete attribute. This is why we can delete some attributes but cannot delete others.
The code copy is as follows:
var GLOBAL_OBJECT = this;
/* `foo` is a property of a Global object.
It is created via variable declaration and so has DontDelete attribute.
This is why it can not be deleted. */
var foo = 1;
delete foo; // false
typeof foo; // "number"
/* `bar` is a property of a Global object.
It is created via function declaration and so has DontDelete attribute.
This is why it can not be deleted either. */
function bar(){}
delete bar; // false
typeof bar; // "function"
/* `baz` is also a property of a Global object.
However, it is created via property assignment and so has no DontDelete attribute.
This is why it can be deleted. */
GLOBAL_OBJECT.baz = 'blah';
delete GLOBAL_OBJECT.baz; // true
typeof GLOBAL_OBJECT.baz; // "undefined"
Built-in objects and DontDelete
So, this is all about it (DontDelete): a special property of the property that controls whether this property can be deleted. Note that some built-in objects are specified to contain DontDelete, so they cannot be deleted. For example, a special argument variable (or, as we know now, the property of an active object) has DontDelete. The length property of a function instance also has DontDelete property.
The code copy is as follows:
(function(){
/* can't delete `arguments`, since it has DontDelete */
delete arguments; // false
typeof arguments; // "object"
/* can't delete function's `length`; it also has DontDelete */
function f(){}
delete f.length; // false
typeof f.length; // "number"
})();
The attribute corresponding to the function parameter also has the DontDelete feature since its establishment, so we cannot delete it.
The code copy is as follows:
(function(foo, bar){
delete foo; // false
foo; // 1
delete bar; // false
bar; // 'blah'
})(1, 'blah');
Undeclared assignment:
You may also remember that an undeclared assignment creates a property on the global object unless the property has been found elsewhere in this scope chain before the global object. And now we know the difference between property assignment and variable declaration - the latter sets the DontDelete property, but the former does not. We must be clear why an undeclared assignment creates a deletionable property.
The code copy is as follows:
var GLOBAL_OBJECT = this;
/* create global property via variable declaration; property has DontDelete */
var foo = 1;
/* create global property via undeclared assignment; property has no DontDelete */
bar = 2;
delete foo; // false
typeof foo; // "number"
delete bar; // true
typeof bar; // "undefined"
Please note: Properties are determined when the attribute is created, and subsequent assignments do not modify the properties of existing attributes. It is very important to understand this difference.
The code copy is as follows:
/* `foo` is created as a property with DontDelete */
function foo(){}
/* Later assignments do not modify attributes. DontDelete is still there! */
foo = 1;
delete foo; // false
typeof foo; // "number"
/* But assigning to a property that doesn't exist,
creates that property with empty attributes (and so without DontDelete) */
this.bar = 1;
delete bar; // true
typeof bar; // "undefined"
Firebug confusion:
What happens in Firebug? Why variables declared in console can be deleted? Isn't this contrary to what we've learned before? Well, as I said before, the Eval code has special performance when facing variable declarations. Variables declared in Eval are actually created as attributes without the DontDelete attribute.
The code copy is as follows:
eval('var foo = 1;');
foo; // 1
delete foo; // true
typeof foo; // "undefined"
Similarly, similarly, when called in function code:
The code copy is as follows:
(function(){
eval('var foo = 1;');
foo; // 1
delete foo; // true
typeof foo; // "undefined"
})();
This is the basis for Firebug abnormal behavior. All text in the console will be parsed and executed as Eval code, rather than global or function code. Obviously, all variables declared here will eventually become properties without the DontDelete attribute, so they can all be easily deleted. We need to understand the difference between the global code and the Firebug console.
Delete variables through Eval:
This interesting eval behavior, coupled with another aspect of ECMAScript, can technically allow us to delete the properties of "non-deletable". One thing about function declarations is that they are able to override variables with the same name in the same execution context.
The code copy is as follows:
function x(){ }
var x;
typeof x; // "function"
Note how function declarations obtain priority and overwrite variables with the same name (or, in other words, the same properties in the variable object). This is because function declarations are instantiated after the variable declaration and are allowed to overwrite them (variable declarations). Function declarations not only replace the value of a property, but also replace the properties of that property. If we declare a function through eval, that function should replace the properties of the original (replaced) property with its own properties. And, since variables declared through eval create properties without the DontDelete attribute, instantiating this new function will actually remove the existing DontDelete attribute from the property, so that a property can be deleted (and, obviously, points its value to the newly created function).
The code copy is as follows:
var x = 1;
/* Can't delete, `x` has DontDelete */
delete x; // false
typeof x; // "number"
eval('function x(){}');
/* `x` property now references function, and should have no DontDelete */
typeof x; // "function"
delete x; // should be `true`
typeof x; // should be "undefined"
Unfortunately, this "deception" doesn't work in any implementation at present. Maybe I'm missing something here, or maybe the behavior is just too obscure that the implementer doesn't notice it.
Browser Compatibility:
It is useful in theory to understand how things work, but practice is the most important thing. Does the browser follow the standards when it comes to creating/deleting variables/properties? The answer is: In most cases, yes.
I wrote a simple test set to test browser compatibility with delete operators, including tests under global code, function code and Eval code. The test set checks whether the return value and attribute values of the delete operator (as they should behave) are really deleted. The return value of delete is not as important as its real result. If delete returns true instead of false, this is not important, and what is important is that attributes with the DontDelete attribute are not deleted, and vice versa.
Modern browsers are generally quite compatible. Apart from the eval features I mentioned earlier, the following browsers have passed all test sets: Opera 7.54+, Firefox 1.0+, Safari 3.1.2+, Chrome 4+.
Safari 2.x and 3.0.4 have problems when deleting function parameters; these properties seem to be created without DontDelete, so they can be deleted. Safari 2.x has more problems - deleting non-referenced variables (such as delete 1) will throw exceptions; function declarations create deleteable properties (but, strangely, variable declarations do not); variable declarations in eval will become non-delete (but function declarations are deleteable).
Similar to Safari, Konqueror (3.5, not 4.3) throws an exception when deleting a non-referenced type (such as: delete 1), and incorrectly makes the function variables deleteable.
Translator's note:
I tested the latest versions of Chrome, Firefox and IE, and basically retained the situation where all other passes except 23 and 24 will fail. At the same time, I tested UC and some mobile browsers. Except for the Nokia E72's built-in browser, which also has Fail 15 and 16, the other built-in browsers are mostly the same as desktop browsers. But it is worth mentioning that the built-in browser of Blackberry Curve 8310/8900 can pass 23, which surprised me.
Gecko DontDelete bug:
Gecko 1.8.x browser - Firefox 2.x, Camino 1.x, Seamonkey 1.x, etc. - shows a very interesting bug, explicit assignment of a property will remove its DontDelete property, even if this property is created through variable declarations or function declarations.
The code copy is as follows:
function foo(){}
delete foo; // false (as expected)
typeof foo; // "function" (as expected)
/* now assign to a property explicitly */
this.foo = 1; // erroneously clears DontDelete attribute
delete foo; // true
typeof foo; // "undefined"
/* note that this doesn't happen when assigning property implicitly */
function bar(){}
bar = 1;
delete bar; // false
typeof bar; // "number" (although assignment replaced property)
Surprisingly, Internet Explorer 5.5 - 8 passed the full test set, except that deleting non-referenced types (such as delete 1) will throw exceptions (just like the old Safari). However, there are more serious bugs under IE, which is not so obvious. These bugs are related to Global object.
IE bugs:
This whole chapter talks about Internet Explorer bugs? Wow! It's amazing!
In IE (at least IE 6-8), the following expression throws an exception (when executed in global code):
this.x = 1;
delete x; // TypeError: Object doesn't support this action
This one will also, but will throw different exceptions, which makes things even more interesting:
var x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'
This looks like in IE, variable declarations in the global code do not create attributes on the global object. Creating attributes by assignment (this.x = 1) and then deleting it by delete x afterward throws an error. Creating attributes by declaration (var x = 1) and deleting it afterward throws another error.
But that's not all. Creating properties by explicit assignments will actually always cause throwing exceptions when deleted. Not only are there errors here, but the created properties seem to have the DontDelete attribute, which of course should not be.
this.x = 1;
delete this.x; // TypeError: Object doesn't support this action
typeof x; // "number" (still exists, wasn't deleted as it should have been!)
delete x; // TypeError: Object doesn't support this action
typeof x; // "number" (wasn't deleted again)
Now, we would think that under IE, undeclared assignments (properties should be created on global objects) do create deleteable properties.
x = 1;
delete x; // true
typeof x; // "undefined"
However, if you delete this property through this reference in the global code (delete this.x), a similar error will pop up.
x = 1;
delete this.x; // TypeError: Cannot delete 'this.x'
If we want to summarize this behavior, it seems that using delete this.x to delete variables from global code will never be successful. When the property in the question is created by explicit assignment (this.x = 1), delete throws an error; when the property is created by an undeclared assignment (x = 1) or by a declaration (var x = 1), delete throws another error.
delete x, on the other hand, an error should be thrown only when the property is created by explicit assignment - this.x = 1. If a property is created by declaration (var x = 1), the delete operation never occurs, and the delete operation correctly returns false. If a property is created by undeclared assignment (x = 1), the delete operation works as expected.
I thought about this issue again in September. Garrett Smith suggested that under IE,
"The global variable object is implemented as a JScript object, and the global object is implemented by host."
Garrett used Eric Lippert's blog entry as a reference.
We can more or less confirm this theory by implementing some tests. Note that this and window seem to point to the same object (if we can trust the === operator), but the variable object (the object where the function declaration is located) is different from what this points to.
The code copy is as follows:
/* in Global code */
function getBase(){ return this; }
getBase() === this.getBase(); // false
this.getBase() === this.getBase(); // true
window.getBase() === this.getBase(); // true
window.getBase() === getBase(); // false
misunderstanding:
The beauty of understanding why things work that way is not to be underestimated. I have seen some misunderstandings about the delete operator on the Internet. For example, the answer on Stackoverflow (with surprisingly high rating), explains confidently
"When the target operand is not an object property, delete should be operational."
Now that we have understood the core of the delete operation behavior, the error in this answer becomes obvious. Delete does not distinguish between variables and attributes (in fact, for delete, they are both reference types) and in fact only cares about DontDelete attributes (and whether the attributes themselves exist).
It is also very interesting to see various misunderstandings refuting each other. In a same topic, one person first suggested that only delete variables (this will not work unless it is declared in eval), while another person provided a bug correction to how delete is used to delete variables in global code, but not in function code.
Be extra careful about the explanation of JavaScript on the Internet. The ideal method is to always understand the essence of the problem. ;)
delete and host object (Host Object):
The delete algorithm is roughly like this:
Return true if the operand is not a reference type
If the object does not have a direct attribute of this name, return true (as we know, the object can be an active object or a global object)
If the property exists but has the DontDelete attribute, return false
In other cases, delete the attribute and return true
However, the behavior of the delete operator on the host object is unpredictable. And this behavior is not actually wrong: (by the standard), the host object is allowed to implement any behavior for several operators like read (internal [[Get]] method), write (internal [[Put]] method) and delete (internal [[Delete]] method). This grace for custom [[Delete]] behavior is what makes the host object so confusing.
We have seen some IE quirks, where deleting specific objects (which are obviously implemented as host objects) will throw errors. Some versions of Firefox will throw when deleting window.location. When operands are host objects, you cannot trust the return value of delete. Let's see what happens in Firefox:
The code copy is as follows:
/* "alert" is a direct property of `window` (if we were to believe `hasOwnProperty`) */
window.hasOwnProperty('alert'); // true
delete window.alert; // true
typeof window.alert; // "function"
Delete window.alert returns true, even if there is no reason for this property to cause such a result at all. It will resolve to a reference (so it won't return true in the first step). This is a direct property of a window object (so it won't return true in the second step). So the only case where delete can return true is to reach the fourth step and actually delete that property. However, this property is never deleted.
The moral of this story is: Never trust the host object.
ES5 Strict Mode:
So, what does strict mode ECMAScript5 bring to us? It introduces few limitations. When the expression of the delete operator is a direct reference to a variable, a function parameter or a function identifier, a syntax error will be thrown. In addition, if the property has an internal property [[Configurable]] == false, a type error will be thrown.
The code copy is as follows:
(function(foo){
"use strict"; // enable strict mode within this function
var bar;
function baz(){}
delete foo; // SyntaxError (when deleting argument)
delete bar; // SyntaxError (when deleting variable)
delete baz; // SyntaxError (when deleting variable created with function declaration)
/* `length` of function instances has { [[Configurable]] : false } */
delete (function(){}).length; // TypeError
})();
In addition, deleting undeclared variables (or unresolved references) will also throw syntax errors:
"use strict";
delete i_dont_exist; // SyntaxError
Undeclared assignment behaves similarly to undeclared variables in strict mode (except this time it raises a quote error instead of a syntax error):
"use strict";
i_dont_exist = 1; // ReferenceError
As you understand now, all the restrictions make more or less sense, because deleting variables, function declarations, and parameters can cause so much confusion. Instead of silently ignoring the deletion operation, the strict pattern takes a more radical and descriptive measure.
Summarize:
This blog post ended up being quite long, so I'm not going to talk about something like using delete to delete an array object or what it means. You can refer to the MDC article's special explanation (or read the standards and do your own experiments).
Here is a brief summary of how deletion operations work in JavaScript:
Variables and function declarations are properties of active objects or global objects
Attributes have some characteristics, and the DontDelete is the attribute that determines whether this attribute can be deleted.
Variables and function declarations in global or function code always create attributes with DontDelete attributes.
Function parameters are always attributes of the active object and are accompanied by DontDelete.
Variables and functions declared in Eval code always create properties without DontDelete.
New properties have no attributes when they are created (of course there is no DontDelete either).
The host object is allowed to decide on its own how to react to the delete operation.
If you want to be more familiar with what is described here, see ECMA-262 3rd edition specification.
I hope you can enjoy this article and learn something new. Any questions, suggestions or corrections are welcome.