I understand about how eval() works in non-strict contexts, however the case of using eval() in strict mode has completely befuddled me. When eval() is called directly in the global scope, variables are kept inside the new eval() scope:
'use strict';
eval('var a = 1;');
console.log(a); // ReferenceError: a is not defined
However, if I perform an indirect call to eval() in the global scope (should be the same thing, right?), it acts as though it is not in strict mode (if you don't believe me, see this JSFiddle):
'use strict';
(0, eval)('var a = 1;'); // indirect call to eval
console.log(a); // 1???
For an explanation of what (0, eval) does: see Why does google main page use (0, obj.func)(args) syntax?.
At least according to my understanding of how eval() is supposed to work in strict mode, it is meant to (no matter whether eval() is called directly or indirectly) create a new scope for variables defined in the eval() call, however this doesn't seem to be the case here. (ECMA-262 spec 5th ed 10.4.2)
This is the case in all major browsers (including Internet Explorer 10, Chrome 30 and Firefox 24) so I don't think it's a bug. Aren't they both meant to do the same thing, and if not, why is this the case?
Note: yes, I know the "dangers" of using eval() - I simply want to understand the logic behind this :)
tl;dr
The second (0, eval)('var a = 1;'); case is in fact not a direct call.
You can see this more prevalently in:
(function(){ "use strict"
var x = eval;
x("var y = 10"); // look at me all indirect
window.y;// 10
eval("var y = 11");
window.y;// still 10, direct call in strict mode gets a new context
})();
The issue can be seen in:
If the eval code is strict code, then (me: fix context)
But strict eval code is defined as:
Eval code is strict eval code if it begins with a Directive Prologue that contains a Use Strict Directive or if the call to eval is a direct call.
Since the call is not direct, the eval code is not strict eval code - and the execution is on global scope.
First of all great question.
"Eval Code" is more general than direct or indirect call to eval.
Let's check the exact specification for the eval function
15.1.2.1 eval (x)
When the eval function is called with one argument x, the following steps are taken:
If Type(x) is not String, return x.
Let prog be the ECMAScript code that is the result of parsing x as a Program. If the parse fails, throw a SyntaxError exception (but see also clause 16).
Let evalCtx be the result of establishing a new execution context (10.4.2) for the eval code prog.
Let result be the result of evaluating the program prog.
Exit the running execution context evalCtx, restoring the previous execution context.
...
So, let's explore what 10.4.2 tells us, you cited that - in specific let's look at the first clause:
If there is no calling context or if the eval code is not being evaluated by a direct call (15.1.2.1.1) to the eval function then ... Initialise the execution context as if it was a global execution context
So what is a direct call?
A direct call to the eval function is one that is expressed as a CallExpression that meets the following two conditions:
The Reference that is the result of evaluating the MemberExpression in the CallExpression has an environment record as its base value and its reference name is "eval".
The result of calling the abstract operation GetValue with that Reference as the argument is the standard built-in function defined in 15.1.2.1.
So, what's the MemberExpression in both cases?
In eval('var a = 1;'); indeed the result of evaluating it has a reference name eval and calling GetValue resolution on it returns the built in function.
In (0, eval)('var a = 1;'); the result of evaluating the member expression does not have a reference name eval. (It does resolve to the built in function on GetValue though).
What are reference names anyway?
Section 8.7 in the spec tells us:
A Reference is a resolved name binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1). A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.
This requires us to look into the GetReferencedName:
GetReferencedName(V). Returns the referenced name component of the reference V.
So, while the expression (0,eval) === eval is true, when evaluating the function, this is actually an indirect call because of naming.
May I offer the Function constructor instead :)?
Related
I start to read JavaScript Patterns, some codes confused me.
var global = (function () {
return this || (1, eval)('this');
}());
Here are my questions:
Q1:
(1, eval) === eval?
Why and how does it work?
Q2: Why not just
var global = (function () {
return this || eval('this');
}());
or
var global = (function () {
return this;
}());
The difference between (1,eval) and plain old eval is that the former is a value and the latter is an lvalue. It would be more obvious if it were some other identifier:
var x;
x = 1;
(1, x) = 1; // syntax error, of course!
That is (1,eval) is an expression that yields eval (just as say, (true && eval) or (0 ? 0 : eval) would), but it's not a reference to eval.
Why do you care?
Well, the Ecma spec considers a reference to eval to be a "direct eval call", but an expression that merely yields eval to be an indirect one -- and indirect eval calls are guaranteed to execute in global scope.
Things I still don't know:
Under what circumstance does a direct eval call not execute in global scope?
Under what circumstance can the this of a function at global scope not yield the global object?
Some more information can be gleaned here.
EDIT
Apparently, the answer to my first question is, "almost always". A direct eval executes from the current scope. Consider the following code:
var x = 'outer';
(function() {
var x = 'inner';
eval('console.log("direct call: " + x)');
(1,eval)('console.log("indirect call: " + x)');
})();
Not surprisingly (heh-heh), this prints out:
direct call: inner
indirect call: outer
EDIT
After more experimentation, I'm going to provisionally say that this cannot be set to null or undefined. It can be set to other falsy values (0, '', NaN, false), but only very deliberately.
I'm going to say your source is suffering from a mild and reversible cranio-rectal inversion and might want to consider spending a week programming in Haskell.
The fragment
var global = (function () {
return this || (1, eval)('this');
}());
will correctly evaluate to the global object even in strict mode. In non-strict mode the value of this is the global object but in strict mode it is undefined. The expression (1, eval)('this') will always be the global object.
The reason for this involves the rules around indirect versus direct eval. Direct calls to eval has the scope of the caller and the string this would evaluate to the value of this in the closure. Indirect evals evaluate in global scope as if they were executed inside a function in the global scope.
Since that function is not itself a strict-mode function the global object is passed in as this and then the expression 'this' evaluates to the global object. The expression (1, eval) is just a fancy way to force the eval to be indirect and return the global object.
A1: (1, eval)('this') is not the same as eval('this') because of the special rules around indirect versus direct calls to eval.
A2: The original works in strict mode, the modified versions do not.
To Q1:
I think this is a good example of comma operator in JS. I like the explanation for comma operator in this article: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/
The comma operator evaluates both of its operands (from left to right) and returns the value of the second operand.
To Q2:
(1, eval)('this') is considered as indirect eval call, which in ES5 does execute code globally. So the result will be the global the context.
See http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope
Q1: Multiple consecutive javascript statements separated by a comma take the value of the last statement. So:
(1, eval) takes the value of the last one which is a function reference to the eval() function. It apparently does it this way to make the eval() call into an indirect eval call that will be evaluated in the global scope in ES5. Details explained here.
Q2: There must be some environment that doesn't define a global this, but does define eval('this'). That's the only reason I can think of for that.
I start to read JavaScript Patterns, some codes confused me.
var global = (function () {
return this || (1, eval)('this');
}());
Here are my questions:
Q1:
(1, eval) === eval?
Why and how does it work?
Q2: Why not just
var global = (function () {
return this || eval('this');
}());
or
var global = (function () {
return this;
}());
The difference between (1,eval) and plain old eval is that the former is a value and the latter is an lvalue. It would be more obvious if it were some other identifier:
var x;
x = 1;
(1, x) = 1; // syntax error, of course!
That is (1,eval) is an expression that yields eval (just as say, (true && eval) or (0 ? 0 : eval) would), but it's not a reference to eval.
Why do you care?
Well, the Ecma spec considers a reference to eval to be a "direct eval call", but an expression that merely yields eval to be an indirect one -- and indirect eval calls are guaranteed to execute in global scope.
Things I still don't know:
Under what circumstance does a direct eval call not execute in global scope?
Under what circumstance can the this of a function at global scope not yield the global object?
Some more information can be gleaned here.
EDIT
Apparently, the answer to my first question is, "almost always". A direct eval executes from the current scope. Consider the following code:
var x = 'outer';
(function() {
var x = 'inner';
eval('console.log("direct call: " + x)');
(1,eval)('console.log("indirect call: " + x)');
})();
Not surprisingly (heh-heh), this prints out:
direct call: inner
indirect call: outer
EDIT
After more experimentation, I'm going to provisionally say that this cannot be set to null or undefined. It can be set to other falsy values (0, '', NaN, false), but only very deliberately.
I'm going to say your source is suffering from a mild and reversible cranio-rectal inversion and might want to consider spending a week programming in Haskell.
The fragment
var global = (function () {
return this || (1, eval)('this');
}());
will correctly evaluate to the global object even in strict mode. In non-strict mode the value of this is the global object but in strict mode it is undefined. The expression (1, eval)('this') will always be the global object.
The reason for this involves the rules around indirect versus direct eval. Direct calls to eval has the scope of the caller and the string this would evaluate to the value of this in the closure. Indirect evals evaluate in global scope as if they were executed inside a function in the global scope.
Since that function is not itself a strict-mode function the global object is passed in as this and then the expression 'this' evaluates to the global object. The expression (1, eval) is just a fancy way to force the eval to be indirect and return the global object.
A1: (1, eval)('this') is not the same as eval('this') because of the special rules around indirect versus direct calls to eval.
A2: The original works in strict mode, the modified versions do not.
To Q1:
I think this is a good example of comma operator in JS. I like the explanation for comma operator in this article: http://javascriptweblog.wordpress.com/2011/04/04/the-javascript-comma-operator/
The comma operator evaluates both of its operands (from left to right) and returns the value of the second operand.
To Q2:
(1, eval)('this') is considered as indirect eval call, which in ES5 does execute code globally. So the result will be the global the context.
See http://perfectionkills.com/global-eval-what-are-the-options/#evaling_in_global_scope
Q1: Multiple consecutive javascript statements separated by a comma take the value of the last statement. So:
(1, eval) takes the value of the last one which is a function reference to the eval() function. It apparently does it this way to make the eval() call into an indirect eval call that will be evaluated in the global scope in ES5. Details explained here.
Q2: There must be some environment that doesn't define a global this, but does define eval('this'). That's the only reason I can think of for that.
I was planning on using the delete operator in JavaScript for something, when I decided to remind myself how it worked by playing with it some. I understand that "delete" is supposed to be used on object properties, but I wanted to see how it behaves on variables. However, some weird results occurred when I put the following snippet of code in different browsers:
var one = 1;
delete one;
console.log(one); // prints out 1 in Chrome, but not in Safari or Firefox
In Safari, the JavaScript console printed out the error "ReferenceError: Can't find variable: one". Firefox gave a similar response: "ReferenceError: one is not defined". However, Chrome printed out the value of the variable, 1. Can anyone explain why Chrome behaves differently than Safari and Firefox in this regard?
Don't trust the console. Some codes behave different there.
The following will alert 1 if you run it on a real page:
var one = 1;
delete one;
alert(one);
Demo
(Tested on Firefox, Chrome, IE, Safari and Opera.)
Understanding delete explains why the console (or firebug) shows a different behavior:
Every execution context has a so-called Variable Object associated
with it. Similarly to execution context, Variable object is an
abstract entity, a mechanism to describe variable instantiation. Now,
the interesing part is that variables and functions declared in a
source text are actually added as properties of this Variable object.
Finally, variables declared within Eval code are created as properties
of calling context’s Variable object.
When declared variables and functions become properties of a Variable
object — either Activation object (for Function code), or Global
object (for Global code), these properties are created with DontDelete
attribute. However, any explicit (or implicit) property assignment
creates property without DontDelete attribute. And this is essentialy
why we can delete some properties, but not others.
So what happens in Firebug? Why is it that variables declared in
console can be deleted, contrary to what we have just learned? Well,
as I said before, Eval code has a special behavior when it comes to
variable declaration. Variables declared within Eval code are actually
created as properties without DontDelete.
Let's checkout the docs on delete from MDN.
The delete operator removes a property from an object.
You are using it a way that doesn't make much sense, that is to remove a local variable.
Those docs also say:
Throws in strict mode if the property is an own non-configurable property (returns false in non-strict). Returns true in all other cases.
Which means your usage here would throw an exception in strict mode, since you are using delete in an unsupported way. This is proven when you do:
var one = 1;
delete one; // returns false
And as the docs mention, a return value of false means the delete operation did not succeed.
If you use it properly, it should behave like you expect:
var obj = {one: 1};
delete obj.one; // returns true
alert(obj.one); // alerts "undefined"
I think the answer he's looking for, and someone essentially said earlier, but muddied up the clarity by adding more explanation through an edit should be restated thus:
When you use a method in a way that was not original dictated in the standard, then the result is undefined and therefore varies from browser to browser (because each essentially has a different js engine) in the way that it's handled.
Revised Answer:
Based on feedback from #Oriol concerning property attributes. I have found that the real issue here is concerning Property Attributes (See ECMA-262 edition 5.1 section 8.6.1) and the Variable Environment's execution context (See ECMA-262 edition 5.1 section 10.3)
Can anyone explain why Chrome behaves differently than Safari and
Firefox in this regard?
var one = 1;
delete one;
console.log(one); // Returns 1.. but why?
Two things are happening here:
The var declaration binds the declared object into an "Execution context" that is distinctly different from the Global (window) one.
JavaScript evaluates the [[Configurable]] property to determine if its "OK" to delete
Concerning #1
The var declaration in the code establishes a VariableEnvironment whereby the value is bound to the object in a distinctly different execution context (scope) than the global one. So naturally, when var is not used the VariableEnvironment is interpreted in a global execution binding process, making statements like one = 1; or delete one; possible.
var one = 1; // Execution context #1 has a unique VariableEnvironment
delete one; // Execution context #2 has a global VariableEnvironment
console.log(one); // Return the value from 'var one'
This complies with the language spec:
10.4 Establishing an Execution Context
Evaluation of global code or code using the eval function (15.1.2.1)
establishes and enters a new execution context...
10.4.2 Entering Eval Code
The following steps are performed when control enters the execution
context for eval code:
If there is no calling context or if the eval code is not being
evaluated by a direct call (15.1.2.1.1) to the eval function then,
Initialise the execution context as if it was a global execution
context using the eval code...
Concerning #2
Chrome and JSFiddle are both doing the right thing here. The the reason has to do with the [[Configurable]] property attribute, which is assigned to both native and user-created properties behind the scenes. When a user-created property is established, this attribute is set to true. This allows the developer to execute assign and delete commands on the property.
var test = {};
test.me = "OK" // [[Configurable]] is true so No Problem!
delete test.me // Good here too!
To prevent certain situations where object properties should not ever be deleted or modified [[Configurable]] is set to false by default. Which respects the language spec:
If the value of an attribute is not explicitly specified by this
specification for a named property, the default value defined in Table
7 is used...
[[Configurable]] false
var test2 = [1,2,3];
console.log(test2.length); // length property is '3'
console.log(delete test2.length); // NOPE [[Configurable]] is false
Same is true in function arguments in a function scope:
(function foo(one) {
console.log(delete one);
})(); // NOPE (false)
What can we draw from both findings?
From this we can understand that Firefox and Safari do not does not play by the rules. When var one=1; is declared in either of these browser's consoles, properties in this scope are incorrectly deemed [[Configurable]] by default and thus deletes var one and not the implied window.one.
In Firefox/Safari:
var one = 1; // var 'one'?
delete one; // NUKE var 'one'!
console.log(one); // ReferenceError: 'one' is not defined :'(
"OK Wait! So why then is delete one true by itself?
It plays out as determined by the language spec (10.4.2):
var one = 1; // VariableEnvironment not global or [[Configurable]]
delete one; // FALSE
...
delete one; // TRUE VariableEnvironment global and [[Configurable]]
...
var one = 1; // VariableEnvironment not global or [[Configurable]]
delete this.one; // TRUE VariableEnvironment is global and [[Configurable]]
One of the strict mode rules (Annex C) states:
When a delete operator occurs within strict mode code, a SyntaxError is thrown if its UnaryExpression is a direct reference to a variable, function argument, or function name.
So in this code:
delete x
x is a reference. (I know this because "the result of evaluating an Identifier is always a value of type Reference"). But is it a direct reference?
And, are there other kinds of references? Indirect references? (If not, what's the point of using the word "direct" at all?)
Yes, there are different kinds of References (EcmaScript §8.7). The member operators (EcmaScript §11.2.1) for example do result in references whose base value is the value of the baseReference, which I'd call "not direct". A "direct reference" would be an identifier reference (EcmaScript §10.2.2.1, where the base value is an Environment Record.
Anything that's not defined as a property if I understand it correctly.
These should throw errors or fail in a console:
(function(){ 'use strict'; var x = '2'; delete x; })();
(function(){ 'use strict'; delete arguments[0]; })('2');
Why does this code throw an error?
// global non-strict code
(function eval () { 'use strict'; });
Live demo: http://jsfiddle.net/SE3eX/1/
So, what we have here is a named function expression. I'd like to explicitly point out that this function expression appears in non-strict code. As you can see, its function body is strict code.
The strict mode rules are here: http://ecma-international.org/ecma-262/5.1/#sec-C
The relevant bullet is this one (it's the last one on the list):
It is a SyntaxError to use within strict mode code the identifiers eval or arguments as the Identifier of a FunctionDeclaration or FunctionExpression or as a formal parameter name (13.1). Attempting to dynamically define such a strict mode function using the Function constructor (15.3.2) will throw a SyntaxError exception.
Notice how this rule only applies if the function declaration/expression itself appears in strict code, which it does not in my example above.
But it still throws an error? Why?
§13.1 outlines what ought to happen in cases such as yours:
It is a SyntaxError if any Identifier value occurs more than once within a FormalParameterList of a strict mode FunctionDeclaration or
FunctionExpression.
It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs within a - FormalParameterList of a strict mode
FunctionDeclaration or FunctionExpression.
It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the Identifier of a strict mode
FunctionDeclaration or FunctionExpression.
Emphasis mine. Your strict-mode function's identifier is eval, thus, it's a SyntaxError. Game over.
To see why the above is a "strict mode function expression," look at the semantic definitions in §13 (Function Definition):
The production
FunctionExpression : function Identifieropt ( FormalParameterListopt ) { FunctionBody }
is evaluated as follows:
Return the result of creating a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt and body
specified by FunctionBody. Pass in the LexicalEnvironment of the
running execution context as the Scope. Pass in true as the Strict
flag if the FunctionExpression is contained in strict code or if its
FunctionBody is strict code.
Emphasis mine. The above shows how a function expression (or declaration) becomes strict. What it says (in plain English) is that a FunctionExpression is strict in two scenarios:
It's called from a use strict context.
Its function body begins with use strict.
Your confusion arises from thinking that only the function body is strict, when in fact, the entire function expression is strict. Your logic, while intuitive, is not how JS works.
If you're wondering why ECMAscript works this way, it's pretty simple. Suppose we have this:
// look ma, I'm not strict
(function eval() {
"use strict";
// evil stuff
eval(); // this is a perfectly legal recursive call, and oh look...
// ... I implicitly redefined eval() in a strict block
// evil stuff
})();
Thankfully, the above code will throw because the entire function expression is flagged as strict.
Great question!
So to find the answer to your problem you actually need to look at the process for declaring a function (specifically, steps 3-5 -- emphasis added):
...
...
Call the CreateImmutableBinding concrete method of envRec passing the String value of Identifier as the argument.
Let closure be the result of creating a new Function object as specified in 13.2 with parameters specified by FormalParameterListopt and body specified by FunctionBody. Pass in funcEnv as the Scope. Pass in true as the Strict flag if the FunctionExpression is contained in strict code or if its FunctionBody is strict code.
Call the InitializeImmutableBinding concrete method of envRec passing the String value of Identifier and closure as the arguments.
So, what happens is that your use of eval isn't a problem when the binding gets created in step 3, but once it hits step 5, it's attempting to initialize the binding of eval within a strict lexical environment (i.e. assigning something to eval), which is not allowed because we're in a strict context following step 4.
Remember that the restriction is not on initializing a new eval variable. It's on using it as the LeftHandSideExpression of an Assignment operator, which is what happens in step 5 of the function declaration process.
UPDATE:
As #DavidTitarenco pointed out, this is explicitly covered in section 13.1 (in addition to the implicit restriction in section 13).
I'm guessing that it throws an error because inside the function eval would point to the function itself which now is in violation of the strict mode.