ECMA-262-5 in detail. Chapter 2. Strict Mode.

Tweet
  1. Introduction
  2. Strict mode features
    1. Reasons
    2. Definition
    3. Strictness scope
  3. Strict mode restrictions
    1. Future reserved keywords
    2. Octal extension for literals
    3. Assignment to an undeclared identifier
    4. Assignment to read-only properties
      1. Shadowing inherited read-only properties
    5. Creating a new property of non-extensible objects
    6. eval and arguments restrictions
    7. callee and caller restrictions
    8. Duplications
    9. delete operator restrictions
    10. with statement
    11. this value restrictions
      1. Indirect eval call
  4. Conclusion
  5. Additional literature

Introduction

In this chapter we will concentrate on another innovation of the ECMA-262-5. The topic of the discussion is a strict variant (or strict mode) of an ECMAScript program. We’ll discuss the reasons for providing this feature, how it affects on some already existing semantic aspects and what can it restrict.

Strict mode features

Below the general provisions regarding strict variant of the ECMAScript language are described. Let’s start from the reasons for which strictness was made and definition of this mode.

Reasons

The current version of the specification — ES5 doesn’t change the syntax much. As one of the incompatible syntactic innovations can be mentioned e.g. an accessor property (getter/setter) defined in the declarative view, with using object initialiser. Although, even this new syntactic feature isn’t new for some implementations (e.g. for SpiderMonkey), which had such constructions before standardizing the ES5. Some other new syntax constructions and ideological aspects which can be based on that new syntax are leaved for the future ECMAScript version — ES6 aka Harmony. Instead, all innovations of the ES5 are made in the context of already existed ES3 syntax constructs. From this viewpoint, ES5 and namely all new meta-methods of the Object constructor (such as e.g. Object.create and other) can be treated as just an additional library (a framework) which is “loaded” before any other library used in a project.

However, without changing the syntax, ES5 provides some important semantic innovations, as e.g. ability to control property attributes, objects extensibility, already mentioned accessor properties, etc. All these features are the good addition for the ES3.

At the same time some features of ES3 were recognized as error-prone, not enough secure or having poor errors checking at parsing stage. In addition, some ES3 features were marked as deprecated and possibly will be removed from the next version of the language. From this point of view, ES5 is a transitional version of the specification.

For that purpose, ECMA-262-5 defines a strict variant of the language. This variant excludes some specific syntactic and semantic features of the regular ECMAScript language and modifies the detailed semantics of some features. The strict variant also specifies additional error conditions that must be reported by throwing error exceptions in some situations that are not specified as errors by the non-strict form.

The strict variant of ECMAScript is commonly referred to as the strict mode of the language.

Definition

Strict mode syntax and semantics of ECMAScript is explicitly made at the level of individual ECMAScript code units. That means we can choose whether to process some code unit (e.g. a function) with the strict mode or not.

To define a strict mode we should use a Use Strict Directive of so-called a Directive Prologues.

A Directive Prologues is a sequence of directives represented entirely as string literals which followed by an optional semicolon. A Directive Prologue may be an empty sequence.

More generally (regardless ECMAScript), a Directive Prologues corresponds to a prologue code i.e. some automatically generated code, preprocessing options, or directives.

For example in ECMAScript:

// start of a directive prologue
"first directive";
'the second in the single quotes';
// end of a directive prologue

The meaning of some such directives is special. Currently, ES5 defines only one special directive — already mentioned a Use Strict Directive.

A Use Strict Directive is a directive in a Directive Prologue whose string literal is either the exact character sequences "use strict" or 'use strict'. A Use Strict Directive may not contain an escape sequence or line continuation. This directive is used for specifying a strict mode of a code unit.

So, to define a strict mode of some code unit we use simply:

"use strict";
// implementation of a module

Notice, that a Directive Prologue may contain more than one Use Strict Directive. However, an implementation may issue a warning if this occurs.

"use strict";
"use strict"; // may be a warning

In addition to the specification, an implementation can use any other directive with specific meaning for the particular implementation. If some directive is not recognized as a special, a warning can be issued. For example, if the implementation reserves additionally “check arguments” directives, then a warning is issued only for the third directive in the following example:

"use strict";
"check arguments";
"use paranoid"; // may be a warning

Strictness scope

As we mentioned, we can choose a strictness for a particular code unit, defining a scope of the strict variant. In the following example, we do not use the strict mode for the whole program, but use it only for the foo function:

var eval = 10; // OK

function foo() {
  "use strict";
  alert(eval); // 10
  eval = 20; // SyntaxError
}

foo();

We see the SyntaxError in the second case — that’s because eval identifier is not allowed for assignment in strict mode, although the first assignment, being evaluated in non-strict mode, occurred normally. But all such restrictions we will discuss shortly in detail.

Another thing to note, is that a Directive Prologue is required to be placed exactly at the top of a context code:

// OK, non-strict mode
(function foo() {
  arguments = 10;
  "use strict";
})();

// SyntaxError, strict-mode
(function foo() {
  "use strict";
  arguments = 10;
})();

It though doesn’t require the "use strict"; directive to be placed at the first position (again — it’s only the whole Directive Prologue should be placed as initial statement):

// also strict mode

"use restrict";
"use strict";

a = 10; // ReferenceError

Besides, the affecting of a strict mode prolongs to all inner contexts. I.e. the context code is a strict code if either its context or any surrounding context contains a Use Strict Directive:

// define strict mode in the global context,
// i.e. for the whole program
"use strict";

(function foo() {
  // strictness is "inherited" from
  // the global context
  eval = 10; // SyntaxError
  (function bar() {
    // the same - from the global context
    arguments = 10; // SyntaxError
  })();
})();

At the same time a strict mode is specified lexically (statically) for a context — just as a closure — where this context is created, but not executed. I.e. the strictness of the context being called is not dependent on the strictness of a caller:

// globally use a non-strict mode

var foo = (function () {
  "use strict";
  return function () {
    alert(this);
  };
})();

function bar() {
  alert(this);
}

// for both functions, a caller is the global context

// but "foo" is evaluated in the strict mode
foo(); // undefined

// meanwhile "bar" is not
bar(); // object

Another important point to notice, that functions created via the Function constructor do not inherit the strictness from the surrounding context. The following example does not throw an exception:

"use strict";

var f = Function("eval", "arguments", " \
  eval = 10; arguments = 20; \
  with ({a: 30}) { \
    alert(a + eval + arguments); \
  }"
);

f(); // OK, 60

But if a strict mode directive is inside such a function, then all restrictions are available:

// non-strict globally

var f = Function("eval", "'use strict'; alert(eval);"); // SyntaxError

And one more important feature related with the strictness scope is an indirect eval call, which in detail will be discussed below. As we’ll see shortly, direct eval cannot create a variable in the environment of the calling context. However, indirect eval call, may create a variable in the global scope, even if the global code is strict. In other words, global strictness scope does not prolong and does not affect indirect evals:

"use strict";

eval("var x = 10"); // direct "eval"

("indirect", eval)("var y = 20"); // indirect "eval"

console.log(typeof x, typeof y); // "undefined", "number"

Repeat, the indirect eval itself is discussed in detail below, so don’t worry if for now the code above with this ("indirect", eval)(...) looks a bit unfamiliar; we’ll clarify it.

Now we are ready to discuss all the cases applied to the strict mode.

Strict mode restrictions

Here you can find all restrictions provided by the strict mode of the ECMA-262-5. Some of them, having similar semantics (e.g. eval and arguments identifiers are not allowed for assignment or function argument names) are combined in one subsection.

At the moment of writing this article (2010-06-01), the only available implementation for testing the strict mode (and the complete implementation of the ES5) — is the BESEN engine written by Benjamin Rosseaux. It of course still has some bugs (some of them I’ve reported while was writing this article and glad to see that several bugs are already fixed), but it is a good candidate for ES5 programming and testing. Implementation is open source, written in Object Pascal. You can download the sources and the IDE from which you can run your tests. Hope, some major implementation will follow soon also.

So, let’s see which restrictions we have in the strict mode of the ES5.

Future reserved keywords

The following set of identifiers is classified as future reserved keywords and cannot be used as variable or function names: implements, interface, let, package, private, protected, public, static, and yield.

// non-strict mode

var lеt = 10; // OK
console.log(lеt); // 10
"use strict";

var let = 10; // SyntaxError

Notice though, that some of the identifiers are already were used as special keywords in several implementations. For example, yield and let are the keywords of the SpiderMonkey since 1.7 version:

// SpiderMonkey 1.7

let x = 10;
console.log(x); // 10

Octal extension for literals

A conforming implementation, when processing strict mode code, may not extend the syntax of numeric literals and escape sequence with octal semantics.

In ES3, if an implementation extended the numeric literal syntax, we had the following case:

// ES3 with octal extension

var x = 010; // octal number
print(x); // 8 - in octal radix

Notice, that even in ES3 octal extension for numeric and string literals was only for backward compatibilities and was defined only in the Annex B (see B.1 Additional Syntax) but not in the main section of numeric or string literals. The same in the ES5 specification — octal extension may be used only for backward compatibilities and only in non-strict mode. It’s also defined in the Annex B of ES5. In strict mode, as we said, octal extension is forbidden:

"use strict";

var x = 010; // SyntaxError

The same with escape sequence, e.g. “\0<octal number>”.

This restriction mostly relates to well-known confusion with often used parseInt function, when programmers get “unexpected” result for e.g. “08″ value:

// ES3

parseInt("07"); // 7
parseInt("08"); // 0 ?
parseInt("09"); // 0 ?
parseInt("010"); // 8 ?

That exactly related with the octal extension — 08 isn’t a valid octal number, so it is treated as 0 in parseInt (notice, however, that parseInt had in ES3 its own, independent from numeric literals, handling of octal numbers). To solve the issue a needed radix should be specified in ES3:

parseInt("08", 10); // 8

In ES5, handling of octal case in parseInt algorithm has been removed, so now we can use it without specifying a radix in this case. Notice again, that new parseInt algorithm is applied regardless strict mode and regardless numeric grammar extension:

// ES5, no matter, strict
// or non-strict mode

parseInt("08"); // 8

But, I want to remind, that parseInt is for parsing (that’s why it’s named so) a number from a string. That means, this method does something more than just a type conversion:

parseInt("08Gb", 10); // 8

If you need just a type conversion, an alternative way can be used, e.g. applying the Number constructor as a function — because it exactly performs ToNumber conversion in this case. If the value of an argument is known and can be converted to number, it can be (and could in ES3) safely used without specifying any radix:

Number("08"); // 8

// alternative way
+"08"; // 8

Of course, it won’t parse an argument as parseInt does:

Number("08Gb"); // NaN

Assignment to an undeclared identifier

As we know, in ES3 assignment to an undeclared identifier creates a new property of the global object (which are often confused with global variables). This causes also well-known issue related with unintentional polluting of the global scope:

// non-strict mode

(function foo() {
  // local vars
  var x = y = 20;
})();

// unfortunately, "y" wasn't local
// for "foo" function
alert(y); // 20
alert(x); // "x" is not defined

In strict mode this feature has been removed:

"use strict";
a = 10; // ReferenceError

var x = y = 20; // also a ReferenceError

There is a pair of subtle cases related with this restriction. According to 11.13.1 Simple Assignment algorithm, first — the left part of the expression is evaluated. This means, that even the grouping operator cannot help to manage such an assignment to an undeclared identifier, which actually can be already declared after the work of the grouping operator:

"use strict";

// The left part is evaluated first and
// returns an unresolved reference, i.e.
// at the moment - "undeclared" does not exist.
//
// However, in the grouping operator, which
// logically should form a priority of an evaluation,
// we create "undeclared" property (and even see
// that alert correctly shows 10).
//
// But even it's created, the assignment cannot
// work because we already evaluated an unresolved
// reference according to the 11.13.1 algorithm

// ReferenceError
undeclared = (this.undeclared = 10, alert(undeclared), 10);

On the other hand, the similar case with delete operator works logically in the correct precedence order, i.e. first — a property is deleted inside the grouping operator, and then we already try to assign a value to the already undeclared identifier:

"use strict";

// if use "prefix" (base), we may
// easily assign to a new property, because
// now the reference is resolved

this.declared = 10; // OK

// since it's delacred, we may even
// change it without base

declared = 20; // OK

// However, with the next evaluation
// we also have an error, but now
// according to 8.7.2.

declared = (delete this.declared, 30); // ReferenceError

In the example above, again, first — the left part is evaluated (and in this case it’s already resolved, because “declared” property exists). Then we delete the property and at the last step — try to assign a value 30 to the already unresolved reference. This case is caught by the step 3-a of the 8.7.2 PutValue algorithm.

However, regarding the delete operator, there is one more subtle case related with removing properties of the environment records. This case we’ll consider shortly below in the section devoted to delete operator restrictions.

Assignment to read-only properties

The same situation is with read-only properties, i.e. if a property being either a data property with [[Writable]] attribute set to false or an accessor property without a [[Set]] method. In the following example a TypeError should be thrown:

"use strict";

var foo = Object.defineProperties({}, {
  bar: {
    value: 10,
    writable: false // by default
  },
  baz: {
    get: function () {
      return "baz is read-only";
    }
  }
});

foo.bar = 20; // TypeError
foo.baz = 30; // TypeError

However, if a property is configurable, then we can change it via Object.defineProperty:

"use strict";

var foo = Object.defineProperty({}, "bar", {
  value: 10,
  writable: false, // read-only
  configurable: true // but still configurable
});

// change the value
Object.defineProperty(foo, "bar", {
  value: 20
});

console.log(foo.bar); // OK, 20

// but still can't via assignment

foo.bar = 30; // TypeError

Shadowing inherited read-only properties

Notice, that it relates to inherited properties as well. If we try to shadow some read-only inherited property via assignment we also get TypeError. And again, if we shadow the property via Object.defineProperty it’s made normally — thus, a configurable attribute of the inherited property doesn’t matter in this case:

"use strict";
 
var foo = Object.defineProperty({}, "x", {
  value: 10,
  writable: false
});
 
// "bar" inherits from "foo"
 
var bar = Object.create(foo);

console.log(bar.x); // 10, inherited
 
// try to shadow "x" via assignment
 
bar.x = 20; // TypeError

console.log(bar.x); // still 10, if non-strict mode
 
// however shadowing works
// if we use "Object.defineProperty"
 
Object.defineProperty(bar, "x", { // OK
  value: 20
});

console.log(bar.x); // 20

In ES3 and non-strict ES5 such assignments to read-only properties failure silently.

Creating a new property of non-extensible objects

Restricted assignment also relates to augmenting non-extensible objects, i.e. objects having [[Extensible]] property as false:

"use strict";

var foo = {};
foo.bar = 20; // OK

Object.preventExtensions(foo);
foo.baz = 30; // TypeError

eval and arguments restrictions

In strict mode these names — eval and arguments are treated as kind of “keywords” (while they are not) and not allowed in several cases.

They cannot appear as a variable declaration or as a function name:

"use strict";

// SyntaxError in both cases
var arguments;
var eval;

// also SyntaxError
function eval() {}
var foo = function arguments() {};

They are not allowed as function argument names:

"use strict";

// SyntaxError
function foo(eval, arguments) {}

It is impossible to assign new values to them (thus, arguments is restricted in the global scope also, but not only in functions):

"use strict";

// SyntaxError
eval = 10;
arguments = 20;

(function (x) {
  alert(arguments[0]); // 30
  arguments = 40; // TypeError
})(30);

They cannot be used with prefix and postfix increment/decrement expressions:

"use strict";

// SyntaxError
++eval;
arguments--;

Regarding objects and their properties, eval and arguments are allowed in assignment expressions and may be used as property names of objects:

"use strict";

// OK
var foo = {
  eval: 10,
  arguments: 20
};

// OK
foo.eval = 10;
foo.arguments = 20;

However, eval and arguments are not allowed as parameter names of declarative view of a setter:

"use strict";

// SyntaxError
var foo = {
  set bar (eval, arguments) {
    ...
  }
};

An implementation may not associate special meanings within strict mode functions to properties named caller or arguments of function instances. It is impossible to create or modify properties with these names on function objects in strict mode (caller and callee restrictions will be described in the following section):

"use strict";

function foo() {
  alert(foo.arguments); // SyntaxError
  alert(foo.caller); // SyntaxError
}

foo();

Also, neither eval, nor arguments cannot be used as an identifier of a catch clause:

"use strict";

try {
  throw Error("...");
} catch (arguments) {} // SyntaxError, the same for "eval" name

Besides restrictions for these names, arguments has additional restriction in semantics. In the strict mode it does not dynamically share its array indexed property values with the corresponding formal parameter bindings of a function. So, array indexed property values of the arguments object are just static copies:

"use strict";

(function foo(x) {

  alert(arguments[0]); // 10
  arguments[0] = 20;

  alert(x); // 10, but not 20

  x = 30;
  alert(arguments[0]); // 20, but not 30

})(10);

Implementation of eval also has restriction in semantics in this case — it cannot instantiate a new binding (a variable or function) in the calling context (as it does in non-strict mode). Instead, strict code for eval is executed in its own “sandbox environment”, which is being destroyed after the eval ends:

"use strict";

eval("var x = 10; alert(x);"); // 10
alert(x); // "x" is not defined

Notice, there is an important exception from this rule — an indirect eval (will be discussed below), which allows to create a global variable regardless even that the global code is strict:

"use strict";

("indirect", eval)("var x = 10; alert(x);"); // 10
alert(x); // 10

callee and caller restrictions

Just like eval and arguments names, callee and caller identifiers because of some security reasons have been restricted. For example:

// non-strict mode

function foo(x, y) {
  alert(x); // 10
  bar();
  alert(x); // 100
}

function bar() {
  console.dir(bar.caller.arguments); // 10, 20
  bar.caller.arguments[0] = 100;
}

foo(10, 20);

In non-strict mode, arguments.callee still can be used to reference an anonymous function from inside, e.g. for a recursive call. Unfortunately, in strict mode if we want to call a function expression recursively (or just to reference it) we should define a NFE (name function expression):

"use strict";

(function foo(bar) {
  if (!bar) {
    arguments.callee(true); // SyntaxError
    foo(true); // OK
  }
})();

There is one unpleasant consequence related with removing arguments.callee from the strict mode — now it’s not possible to reference a function created via the Function constructor from the inside (e.g. at recursive call) if such function created not in the global context. As we know, the scope chain of Function functions consists only of the global object. Therefore, if such function created inside other function, no any binding name of surrounding function is available for the Function function. We could call such functions in ES3 only via the arguments.callee reference. In ES5 — there is no (at least normal) way to call such function recursively:

(function () {
  
  // outer name is not available,
  // regardless strictness
  var foo = Function("alert(foo);'");
  foo(); // "foo" is not defined (no such name in the global context)
  
  // error in strict mode for arguments.callee
  Function("'use strict; alert(arguments.callee);'")(); // TypeError
  
  // OK in non-strict for arguments.callee
  Function("alert(arguments.callee);'")(); // OK, function

})();

However, if such a strict function is created in the global context, we can use outer variable name to reference it:

var foo = Function("'use strict; alert(foo);'");
foo(); // function

As a variant of solving the issue, a function can be passed as an argument to itself:

(function () {
  var foo = Function("foo", "'use strict'; alert(foo);");
  foo(foo); // OK, function
})();



gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.