【译】严格模式(Strict mode)

严格模式(Strict mode)

前言

ECMAScript 5中推出的JavaScript严格模式(strict mode),可以让你使用JavaScript的一种受限”变体“,从而悄悄地退出了”正常模式(sloppy mode)“。严格模式并非只是正常模式的子集:它专门拥有与正常模式下的代码不一样的语义。如果浏览器A不支持严格模式,浏览器B支持,它们俩运行同样的严格模式代码结果也会不同,所以如果没有对严格模式下的代码进行相应的功能测试,请不要依赖它。严格模式和正常模式的代码可以共存,所以正常模式的代码可以逐渐改造,最终替换为严格模式代码

有时候你会看到正常的,非严格的模式也被称作”马虎模式(sloppy mode)“,这并不是术语,但知道总归是好的。

相比正常模式,严格模式在JavaScript语义上做了如下改动:

  1. 消除了一些JavaScript隐蔽的错误,并将它们抛出来。
  2. 修复了一些JavaScript错误(这些错误使得JavaScript引擎难以优化代码):有时候严格模式下的代码比正常模式执行得更快。
  3. 对于在未来版本的ECMAScript中可能出现的功能,在语法上进行禁止(例如禁止使用某些关键字)。

如果你想更改代码以让其在JavaScript的这个受限”变体“中运行,可以查看”迁移到严格模式“

启用严格模式

严格模式可以在全局作用域函数作用域启用,但是无法在块级作用域 {} 中单独启用,如果尝试在其中启用不会有任何效果。eval代码、Function代码、事件处理程序属性(译者注:例如The time is?)、WindowTimers.setTimeout()的字符串参数(译者注:例如var timeoutID = scope.setTimeout(code[, delay]);)等等类似的代码均为全局作用域,在它们中也可以启用严格模式。

全局作用域下的严格模式

想要在全局作用域下启用严格模式,只需要在任意其他代码之前写上"use strict";(或者’use strict’;)。

// 全局作用域下的严格模式语法
'use strict';
var v = "Hi! I'm a strict mode script!";

这个语法存在一个陷阱,某个主要网站受此影响:对于冲突的两个script无法进行合并。例如:如果需要将一个正常模式的script合并到一个严格模式的script,那么整个script都会变成严格模式!反过来则会成为正常模式的script。显然,合并脚本从来不是明智之举,如果非要这样做,请在每个函数作用域下单独启用。

你也可以将脚本的所有内容放在一个函数中,并在该函数中决定是否启用严格模式。这消除了合并的问题,也意味着你必须从函数中人工导出需要共享的变量。

函数作用域下的严格模式

同样的,为了在函数作用域下启用严格模式,在函数体中任意代码前写上"use strict";(或者’use strict’;)。

function strict() {
  // 函数作用域下的严格模式语法
  'use strict';
  function nested() { return 'And so am I!'; }
  return "Hi!  I'm a strict mode function!  " + nested();
}
function notStrict() { return "I'm not strict."; }

模块(modules)下的严格模式

ECMAScript 2015推出了JavaScript模块(JavaScript modules),所以现在有第三种方法启用严格模式。JavaScript模块中的代码自动开启了严格模式,无需进行任何声明。

function strict() {
    // 由于该函数位于模块内,代码自动启用严格模式
}
export default strict;

严格模式下的一些改变

严格模式既改变了句法(syntax),也改变了运行时表现(runtime behavior)。改变可以分为以下几类:

  1. 将一些误解(mistake)转变为错误(error)(句法错误(syntax error)或运行时错误(runtime error))
  2. 简化了变量名到特定变量的映射关系
  3. 简化了eval和arguments的使用
  4. 更容易书写”安全“的JavaScript(secureJavaScript)
  5. 对未来的ECMAScript的进化做铺垫(译者注:可以理解为限制一些未来规范中可能定义的一些关键字的使用)
将误解转变为错误

严格模式下,之前被广泛接受的误解被转换成错误。JavaScript是一个对新手友好的开发语言,有时候一些本该是错误操作却被赋予了非错误的语义。这样做能够临时解决一些问题,但是有时也为将来埋下了更糟糕的问题。严格模式可以让这些错误的操作变成运行时错误,从而及时得到发现和解决。

第一,严格模式禁止了意外创建全局变量,在常规JavaScript中,如果赋值时误输入了一个变量,它会成为全局对象的属性并且程序继续”正常工作“(不过在JavaScript的未来版本中可能会运行失败)。严格模式下,如果为一个未定义的变量进行赋值操作,不会创建一个全局变量,而是抛出错误:

'use strict';
                       // 假设全局变量 mistypeVariable 不存在
mistypeVariable = 17;  // 由于误拼写了变量名,这一行会抛出引用错误(ReferenceError)

第二,严格模式下,之前一些赋值操作只是无效,现在则会抛出异常。例如,NaN是一个不可写的全局变量。常规模式下赋值给NaN不会发生任何事,开发者也不会收到任何错误的反馈。严格模式下这样做则会抛出异常。在严格模式下,任何在常规模式下无效的赋值操作(例如赋值给一个不可写的全局变量或对象属性、赋值给只读的对象属性、在不可扩展的对象上添加一个新属性)在严格模式下都将会抛出异常:

'use strict';

// 赋值给一个不可写的全局变量
var undefined = 5; // 抛出 TypeError
var Infinity = 5; // 抛出 TypeError

// 赋值给一个不可写的对象属性
var obj1 = {};
Object.defineProperty(obj1, 'x', { value: 42, writable: false });
obj1.x = 9; // 抛出 TypeError

// 赋值给一个只读的对象属性
var obj2 = { get x() { return 17; } };
obj2.x = 5; // 抛出 TypeError

// 给不可扩展的对象添加一个新属性
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = 'ohai'; // 抛出 TypeError

第三,严格模式下,如果试图删除一个不可删除的属性也会抛出异常(常规模式下只是无效):

'use strict';
delete Object.prototype; // 抛出 TypeError

第四,在 Gecko 34 版本前的严格模式中,要求所有对象字面量上定义的属性唯一。而常规模式下只是复制一份属性,最后一个属性决定最终的取值。由于同名属性只有最后一个起作用,如果更改属性值时改的不是最后一个属性本身,这样的复制可能会导致一连串bug。在严格模式下重复定义属性是语法错误:

注:ECMAScript 2015后允许重复定义属性(bug 1041128)

'use strict';
var o = { p: 1, p: 2 }; // !!! syntax error

第五,严格模式要求函数参数名唯一。而常规模式下最后一个参数会覆盖之前同名的,不过他们并非完全不可访问,通过arguments[i]仍然可以获取这些参数值。尽管如此,这种覆盖没有意义,甚至有可能是无意为之(例如可能只是拼写错误),所以严格模式下这样做是语法错误:

function sum(a, a, c) { // !!! syntax error
  'use strict';
  return a + a + c; // 代码运行结果是错误的
}

第六,严格模式禁止八进制的语法。八进制语法并非ECMAScript标准,但是所有浏览器都支持该语法(只需在八进制数前面加上0:0644 === 420,或者"\045" === “%”)。在ECMAScript 2015中可以通过加上0o成为八进制数,例如:

var a = 0o10; // ES2015: 八进制

新手开发者可能认为0作为前缀并没有什么语义,所以他们使用0来进行代码对齐,但是这样改变了数字真正的值!以0作为前缀几乎没用,反而有误导作用,因此严格模式下这样做会导致语法错误:

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

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

第七,严格模式禁止在基础类型值上添加属性,而常规模式这样操作只是无效。严格模式会抛出TypeError:

(function() {
'use strict';

false.true = '';         // TypeError
(14).sailing = 'home';   // TypeError
'with'.you = 'far away'; // TypeError

})();

简化变量的使用

严格模式简化了变量名和代码中具体的变量定义间的映射关系(译者注:就是给定一个变量名,可以更简单地找到其定义(也就是内存地址),从而获取值)。编译器的一些优化依赖于快速找到某个变量存储在哪,这对于全面优化JavaScript代码至关重要。有时候这种这种映射关系直到运行时才能建立。严格模式消除了影响映射关系建立的大多数情况,因此编译器能更好地优化严格模式代码。

第一,严格模式禁止使用with,with的问题在于其代码块中的任何变量名要么映射到传给它的对象属性上,要么是他的外部作用域(可能是全局作用域)变量上,而且映射关系只能在运行时建立,无法提前知道。严格模式使with成为语法错误,从而避免其代码块中的变量名在代码运行前无法确定具体位置的问题:

'use strict';
var x = 17;
with (obj) { // !!! syntax error
  // 如果不是在严格模式下,x 指的是外部定义的 `var x` ?
  // 又或者是 `obj.x` ? 在代码真正运行前无法准确得知,
  // 所以这段代码无法优化
  x;
}

一个简单的替代方案是将对象临时存储到一个变量,然后访问这个变量的对应属性以替代使用with。

为将来的ECMAScript铺平道路

未来的ECMAScript可能会推出新语法,ECMAScript 5的严格模式使用了一些限制,以使得过渡更加平滑。如果未来的一些变更基础已经在严格模式中被禁止,那这些变更在新版本中的推进就会更加容易。

第一,在严格模式中,少部分标识符成为了保留关键字。这些包括:implementsinterfaceletpackageprivateprotectedpublicstaticyield。在严格模式中,你不能使用这些来进行变量或参数命名。

function package(protected) { // !!!
  'use strict';
  var implements; // !!!

  interface: // !!!
  while (true) {
    break interface; // !!!
  }

  function private() { } // !!!
}
function fun(static) { 'use strict'; } // !!!

有两条针对Mozilla浏览器的警告:首先,如果你的JavaScript版本大于或等于1.7(例如你的chrome代码或正确使用了加载的,则无法使用let/yield标识符。其次,尽管ES5无条件地保留了class、enum、extends、import以及super,在Firefox 5以前,Mozilla仅仅在严格模式中保留了它们。

第二,严格模式禁止函数声明在全局或函数顶层之外。在常规模式下的浏览器中,函数声明允许在”任何地方“。*这并不是ES5(甚至ES3)规范!*而是一个语法扩展,而且在不同浏览器中语义不兼容。注意ECMAScript 2015允许了顶层之外进行函数声明。

'use strict';
if (true) {
  function f() { } // !!! syntax error
  f();
}

for (var i = 0; i < 5; i++) {
  function f2() { } // !!! syntax error
  f2();
}

function baz() { // 合法
  function eit() { } // 也合法
}

这种禁止正确来说不是严格模式,因为允许函数声明在”任何地方“是ES5的一个语法扩展。但是ECMAScript委员会推荐了这个语法扩展,因此浏览器都实现了它。

浏览器中的严格模式

主流浏览器现在都实现了严格模式。然而,由于目前仍然有许多版本的浏览器仅仅部分支持或完全不支持严格模式(例如IE10以下浏览器)。严格模式改变了代码的语义。如果浏览器不支持严格模式,依赖这些改变会导致一些误解或错误。使用严格模式时务必格外小心,依赖严格模式之前,请确保进行了单元测试以验证相关部分严格模式的代码正常运行。最后,确保在支持和不支持严格模式的浏览器中均测试了代码。如果你仅仅在不支持严格模式的浏览器中测试代码,你可能会在支持严格模式的浏览器中遇到一些问题,反之亦然。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值