JavaScript 严格模式

379 阅读7分钟

相对于C、Java等编程语言来说,JavaScript的语法规则相对松散。对此,ECMAScript 5引入了严格模式(strict mode),那么是什么严格模式,与之前的非严格模式(sloppy mode)有什么区别呢?本文基于MDN文档,学习记录一下ECMAScript 5中的严格模式

一、什么是严格模式(strict mode)

ECMAScript 5的严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码隐式地脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。 —— MDN

严格模式对正常的JavaScript语义做了如下更改:

  • 严格模式通过抛出错误来消除一些原有的静默错误
  • 严格模式修复了一些导致JavaScript引擎难以执行优化的缺陷。(某些情况下,相同的代码,严格模式比非严格模式运行的更快
  • 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。 严格模式的产生是为了形成与正常代码不同的语义。所以,不支持严格模式与支持严格模式的浏览器在执行严格模式代码时会采用不同行为

二、如何开启严格模式

2.1 整个脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict"; (或 'use strict';
即:在脚本文件开头

// 开启全局严格模式
"use strict"

这种语法在合并非严格模式脚本和严格模式脚本的时候可能会出现问题。

2.2 函数开启严格模式

要给某个函数开启严格模式,得把 "use strict";  (或 'use strict'; )声明放在函数体所有语句之前。

function strict() {
  // 函数级别严格模式语法
  'use strict';

}

此外,ES6中引入的新特性类(class)的块中,默认使用严格模式。

三、严格模式下的限制

(1)不允许未声明的变量

非严格模式下,如果一个变量在声明之前就被使用,会默认创建一个全局变量。但是严格模式下不会意外创建全局变量。

"use strict"
a = 1  // ReferenceError: a is not defined

(2)静默失败的操作抛出异常

静默失败的赋值操作指的是:修改不可写(writable:false)或只读属性(只定义了set函数)的值、给不可扩展(non-extensible)对象添加属性等。在非严格模式下会默认忽略这些操作,但不会抛出异常。但是在严格模式下会抛出异常。

"use strict";

// 给不可写属性赋值
var obj = {};
Object.defineProperty(obj, "a", { writable: false });
// obj.a = 1; // TypeError: Cannot assign to read only property 'a' of object '#<Object>'

// 给只读属性赋值
Object.defineProperty(obj, "b", { get x() { return 17; }  });
// obj.b = 5; // TypeError: Cannot assign to read only property 'b' of object '#<Object>'

// 给不可扩展对象的新属性赋值
Object.preventExtensions(obj);
obj.newProp = "ohai"; // TypeError: Cannot add property newProp, object is not extensible

此外,删除不可配置(configurable: false)的对象属性也会抛出异常

// 删除不可配置属性
var obj = {};
Object.defineProperty(obj, "a", { configurable: false });
delete obj.a;  // TypeError: Cannot delete property 'a' of #<Object>

(3) 函数参数名必须唯一

非严格模式下,当函数参数重名时后面的参数会覆盖前面的参数值。函数体获取的参数值是同名参数中最后一个参数的值。但是非严格模式下,函数参数重名会报语法错误。

// SyntaxError: Duplicate parameter name not allowed in this context (at index.js:1:17)
function sum(a, a, c) { 
    "use strict";
    return a + a + c; // 代码运行到这里会出错
}

(4)禁止八进制数字语法

在ECMAScript 6中支持为一个数字加"0o"的前缀来表示八进制数.

"use strict";
var a = 0o15
console.log(a); // 13

var b = 015;  // SyntaxError: Octal literals are not allowed in strict mode.

(5)禁止设置原始值的属性

在非严格模式下设置原始值属性会被忽略,但是严格模式下会抛出异常。

"use strict";

  false.true = "";   //TypeError: Cannot create property 'true' on boolean 'false'
  (14).sailing = "home";  // TypeError: Cannot create property 'sailing' on number '14'
  "with".you = "far away";  // TypeError: Cannot create property 'you' on string 'with'

(6)禁用 with 关键字

严格模式禁用 with.  with所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性, 也可以映射到包围这个块的作用域内的变量(甚至是全局变量), 这一切都是在运行时决定的: 在代码运行之前是无法得知的. 严格模式下, 使用 with 会引起语法错误, 所以就不会存在 with 块内的变量在运行时才决定引用到哪里的情况了

"use strict";
var x = 17;
with (obj) { // !!! 语法错误
  // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
  // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
  x;
}

(7)eval 方法不能改变作用域

在严格模式下 eval 仅仅为被运行的代码创建变量, 所以 eval 不会使得名称映射到外部变量或者其他局部变量:

"use strict"; 
eval("var x = 5;"); 
console.log(x); // ReferenceError: x is not defined

如果函数 eval 被在严格模式下的eval(...)以表达式的形式调用时, 其代码会被当做严格模式下的代码执行.当然也可以在代码中显式开启严格模式, 但这样做并不是必须的.

(8)声明变量禁止删除

非严格模式下,删除声明变量会被忽略。但是严格模式下会抛出异常。

"use strict";

var a;
delete a;  //  SyntaxError: Delete of an unqualified identifier in strict mode.

(9)eval 和 arguments不能用作标识符

"use strict";

var eval;  // SyntaxError: Unexpected eval or arguments in strict mode 
arguments ++  // SyntaxError: Unexpected eval or arguments in strict mode 

(10)arguments的值不会随着参数值改变而改变

严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。

// 严格模式下
function foo(a, b) {
    "use strict"
    a = 10;
    arguments[1] = 20
    console.log(`a=${a}, arguments[0]=${arguments[0]}, b=${b}, arguments[1]=${arguments[1]}`)
}
foo(1, 2);  // a=10, arguments[0]=1, b=2, arguments[1]=20


// 非严格模式下
function foo(a, b) {
    "use strict"
    a = 10;
    arguments[1] = 20
    console.log(`a=${a}, arguments[0]=${arguments[0]}, b=${b}, arguments[1]=${arguments[1]}`)
}
foo(1, 2);  // a=10, arguments[0]=10, b=20, arguments[1]=20

(11)不再支持 arguments.calleearguments.callee.caller

在严格模式下,arguments.callee 和 arguments.callee.caller 是一个不可删除属性,赋值和读取时都会抛出异常:

"use strict";
var f = function() { return arguments.callee; };
f(); // 抛出类型错误

(12)this的指向

  • 在普通的函数调用f()中,this的值会指向全局对象.在严格模式中,this的值会指向undefined.
  • 当函数通过callapply调用时,如果传入的thisvalue参数是一个nullundefined除外的原始值(字符串,数字,布尔值),则this的值会成为那个原始值对应的包装对象,如果thisvalue参数的值是undefinednull,则this的值会指向全局对象.在严格模式中,this的值就是``thisvalue参数的值,没有任何类型转换.
function foo() {
    "use strict"
    console.log(this)
}
// 默认情况下this为undefined
foo();  // undefined

// 传递的thisValue为1 , this的值就是1
foo.call(1);  // 1

(13)不再支持Function.prototype.callerFunction.prototype.arguments

假设有一个函数fun, 在fun在严格模式下,fun.callerfun.arguments都是不可删除的属性而且在存值、取值时都会报错

function restricted() {
  "use strict";
  restricted.caller;    // 抛出类型错误
  restricted.arguments; // 抛出类型错误
}

(14)添加了保留的关键字

在严格模式中一部分字符变成了保留的关键字。这些字符包括implementsinterfaceletpackageprivateprotectedpublicstaticyield。在严格模式下,你不能再用这些名字作为变量名或者形参名。

"use strict"
var interface;  // SyntaxError: Unexpected strict mode reserved word

(15)禁止不在脚本或函数层面的函数声明

严格模式下禁止在一些语句的块中进行函数声明。

"use strict";
if (true) {
  function f() { } // !!! 语法错误
  f();
}

四、小结

严格模式下的限制主要有:

(1)语法错误(SyntaxError)

  • 八进制语法:var n = 023和var s = "\047"
  • with语句
  • 使用delete删除一个变量名(而不是属性名):delete myVariable
  • 使用evalarguments作为变量名或函数名
  • 使用未来保留字(也许会在ECMAScript 6中使用):implementsinterfaceletpackageprivateprotectedpublicstatic,和yield作为变量名或函数名
  • 在语句块中使用函数声明:if(a<b){ function f(){} }
  • 其他错误
    • 函数形参中使用两个相同的参数名:function f(a, b, b){}

(2)静默失败操作(TypeError)

  • 给未声明的变量赋值
  • 尝试删除不可配置的对象属性
  • 尝试修改不可写或只读的对象属性
  • 尝试向不可扩展的对象添加属性

(3)arguments对象和函数对象禁用的属性

  • 严格模式下,访问arguments.calleearguments.calleranyFunction.caller以及anyFunction.arguments都会抛出异常

(4)arguments对象保存参数原始值,不与对应的形参变量同步更新

(5)eval不会在当前的作用域内创建新的变量。传入eval的字符串参数也会按照严格模式来解析.

(6)严格模式中,this的值默认会指向undefined;函数通过callapply调用时,thisValue的值传入原始值时不会转换成包装对象。

主要参考:
[1]严格模式
[2]Transitioning to strict mode