js 的严格模式与松散(正常)模式,史上最全

649 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

早期的 JavaScript 语言有很多设计不合理的地方,但是为了兼容以前的代码,又不能改变老的语法,只能不断添加新的语法,引导程序员使用新语法。

严格模式对正常的 JavaScript 语义做了一些更改。

  1. 严格模式通过抛出错误来消除了一些原有静默错误。保证代码运行的安全。
  2. 严格模式修复了一些导致 JavaScript 引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快
  3. 严格模式禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 JavaScript 语法做好铺垫。

用法:

(1) 整个脚本文件

use strict放在脚本文件的第一行,整个脚本都将以严格模式运行。如果这行语句不在第一行就无效,整个脚本会以正常模式运行。(严格地说,只要前面不是产生实际运行结果的语句,use strict可以不在第一行,比如直接跟在一个空的分号后面,或者跟在注释后面。)

<script>
  'use strict';
  console.log('这是严格模式');
</script>

<script>
  console.log('这是正常模式');
  'use strict' // 写在这里无效
</script>

(2)单个函数

use strict放在函数体的第一行,则整个函数以严格模式运行。

function strict() {
  'use strict';
  return '这是严格模式';
}

function strict2() {
  'use strict';
  function f() {
    return '这也是严格模式';
  }
  return f();
}

function notStrict() {
  return '这是正常模式';
}

In strict mode, starting with ES2015, functions inside blocks are scoped to that block. Prior to ES2015, block-level functions were forbidden in strict mode.
在严格模式下,从ES2015开始,块内的函数作用于该块。在ES2015之前,块级函数在严格模式下是被禁止的。

ES6的模块化默认开启模块化

function strict() {
    // because this is a module, I'm strict by default
}
export default strict;

所有class都默认开启严格模式

All parts of ECMAScript classes are strict mode code, including both class declarations and class expressions — and so also including all parts of class bodies.

严格模式与正常模式的不同

1、变量名直接赋值报错,正常模式是添加到window

'use strict';
let mistypeVariable;
                      
mistypeVarible = 17;  // Assuming no global variable mistypeVarible exists
                      // this line throws a ReferenceError due to the
                      // misspelling of "mistypeVariable" (lack of an "a")

2、只读属性不可写

'use strict';
'abc'.length = 5;
// TypeError: Cannot assign to read only property 'length' of string 'abc'

上面代码报错,因为length是只读属性,严格模式下不可写。正常模式下,改变length属性是无效的,但不会报错。

3、严格模式下,对只读属性赋值,或者删除不可配置(non-configurable)属性都会报错。对一个只有取值器(getter)、没有存值器(setter)的属性赋值,会报错。对禁止扩展的对象添加新属性,会报错。

// Assignment to a non-writable property
var obj1 = {};
Object.defineProperty(obj1, 'x', { value: 42, writable: false });
obj1.x = 9; // throws a TypeError

// 删除不可配置的属性会报错
'use strict';
var obj = Object.defineProperty({}, 'p', {
  value: 1,
  configurable: false
});
delete obj.p
// TypeError: Cannot delete property 'p' of #<Object>

// Assignment to a getter-only property
var obj2 = { get x() { return 17; } };
obj2.x = 5; // throws a TypeError

// Assignment to a new property on a non-extensible object
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = 'ohai'; // throws a TypeError

4、严格模式下,形参名重复将报错。正常模式下,如果函数有多个重名的参数,前面重名的会被覆盖,但是可以用arguments[i]读取。严格模式下,这属于语法错误。

function sum(a, a, c) { // !!! syntax error
  'use strict';
  return a + a + c; // wrong if this code ran
}

You cannot write a "use strict"; directive in the body of a function definition that accepts rest, default, or destructured parameters. Doing so will throw a syntax error.

严格模式下还不允许使用默认参数,rest参数,解构参数。假如使用会报语法错误。

5、ECMAScript 5的严格模式禁止以0开头的八进制文字或八进制转义序列。在严格模式之外,以0开头的数字,如果所有数字都小于8,如0644,被解释为八进制数字(0644==420)。八进制转义序列,如"\45",等于"%",可以用来用八进制的扩展ASCII字符编码数字表示字符。在严格模式下,这是一个语法错误。在ECMAScript 2015中,通过在数字前加 "0o "来支持八进制字数;例如:

var a = 0o10; // ES2015: Octal

(function () {
    'use strict'
    console.log('\45')
    console.log(045)
    // Uncaught SyntaxError: Octal escape sequences are not allowed in strict mode.
}())

初学者可能不知道数字以0开头代表8进制,为了避免使用错误,严格模式不允许0开头的数字代表8进制。

'use strict';
var sum = 015 + // !!! syntax error
          197 +
          142;

var sumWithOctal = 0o10 + 8;
console.log(sumWithOctal); // 16

6、 strict mode in ECMAScript 2015 forbids setting properties on primitive values. Without strict mode, setting properties is ignored (no-op), with strict mode, however, a TypeError is thrown.

(function() {
'use strict';
false.true = '';         // TypeError
(14).sailing = 'home';   // TypeError
'with'.you = 'far away'; // TypeError
})();

In ECMAScript 5 strict-mode code, duplicate property names were considered a SyntaxError. With the introduction of computed property names, making duplication possible at runtime, ECMAScript 2015 removed that restriction.

以下这段代码在ES6不报错,ES5及以前会报错

'use strict';
var o = { p: 1, p: 2 }; // syntax error prior to ECMAScript 2015

7、禁止使用with语句

'use strict';
var x = 17;
with (obj) { // !!! syntax error
  // If this weren't strict mode, would this be var x, or
  // would it instead be obj.x?  It's impossible in general
  // to say without running the code, so the name can't be
  // optimized.
  x;
}

8、正常模式下,JavaScript 语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。

正常模式下,eval语句的作用域,取决于它处于全局作用域,还是函数作用域,有点类似花括号 {},使用let申明则是块级作用域,var则是全局。严格模式下,eval语句本身就是一个作用域,不再能够在其所运行的作用域创设新的变量了,也就是说,eval所生成的变量只能用于eval内部。

(function () {
  'use strict';
  var x = 2;
  console.log(eval('var x = 5; x')) // 5
  console.log(x) // 2
})()

var x = 17;
var evalX = eval("'use strict'; var x = 42; x;");
console.assert(x === 17);  // true
console.assert(evalX === 42);  // true

下面的例子说明了哪些eval开启了严格模式

function strict1(str) {
  'use strict';
  return eval(str); // str will be treated as strict mode code
}
function strict2(f, str) {
  'use strict';
  return f(str); // not eval(...): str is strict if and only
                 // if it invokes strict mode
}
function nonstrict(str) {
  return eval(str); // str is strict if and only
                    // if it invokes strict mode
}

strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");

9、严格模式删除(delete)变量或者删除对象的不可配置的属性(即:configurable: false)时报错,正常模式不报错但是删除不成功。

'use strict';

var x;
delete x; // !!! syntax error

eval('var y; delete y;'); // !!! syntax error

var obj = Object.create(null, {
  x: {
    value: 1,
    configurable: false // true则可以删除和重新配置(writable,configurable,enumerable...)
  }
});
delete obj.x; // TypeError: Cannot delete property 'x' of [object Object]

10、严格模式简化了eval和arguments

eval和arguments不能作为变量名和参数名,下面这些都是语法错误

'use strict';
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function('arguments', "'use strict'; return 17;");

变量arguments代表函数的参数。严格模式下,函数内部改变参数与arguments的联系被切断了,两者不再存在联动关系。

function f(a) {
  a = 2;
  return [a, arguments[0]];
}
f(1); // 正常模式为[2, 2]

function f(a) {
  'use strict';
  a = 2;
  return [a, arguments[0]];
}
f(1); // 严格模式为[2, 1]

上面代码中,改变函数的参数,不会反应到arguments对象上来。反之亦然

禁止使用arguments.callee,正常模式下调用指向的的是执行的函数,不会报错。严格模式明确规定,函数内部使用arguments.callee将会报错。并且arguments.callee是一个不可删除属性。

'use strict';
var f = function () {
  return arguments.callee;
};

f(); // 报错

11、传递给非严格模式下函数中this的值会被强制转换为Object类型,严格模式下this不会转换为对象,假如是null就是null,undefined就是undefined,不会自动指向window。建议直接看英文。英文不好看例子更简单。

The value passed as this to a function in strict mode is not forced into being an object (a.k.a. "boxed"). For a normal function, this is always an object: either the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this; or the global object if called with an undefined or null this. (Use callapply, or bind to specify a particular this.) Not only is automatic boxing a performance cost, but exposing the global object in browsers is a security hazard because the global object provides access to functionality that "secure" JavaScript environments must restrict. Thus for a strict mode function, the specified this is not boxed into an object, and if unspecified, this will be undefined:

'use strict';
function fun() { return this; }
console.assert(fun() === undefined); // true
console.assert(fun.call(2) === 2);   // true
console.assert(fun.apply(null) === null);   // true
console.assert(fun.call(undefined) === undefined); // true
console.assert(fun.bind(true)() === true);  // true

12、函数内部不得使用fn.callerfn.arguments,否则会报错。这意味着不能在函数内部得到调用栈了。

function restricted() {
  'use strict';
  restricted.caller;    // throws a TypeError
  restricted.arguments; // throws a TypeError
}
function privilegedInvoker() {
  return restricted();
}
privilegedInvoker();

13、ES5严格模式下的函数申明不在作用域顶级会报错,ES6则不会

这一点要深究的话看这 es6.ruanyifeng.com/#docs/let,在…

'use strict';
if (true) {
  function f() { } // !!! ES5 syntax error,ES6正常
  f();
}

for (var i = 0; i < 5; i++) {
  function f2() { } // !!! ES5 syntax error,ES6正常
  f2();
}

function baz() { // kosher(合法的)
  function eit() { } // also kosher
}