正文
一、概念
除了正常运行模式,ECMAScript 5 添加了第二种运行模式:严格模式(strict mode)。顾名思义,这种模式使得 JavaScript 在更严格的条件下运行。
与之相反的非严格模式,被称为“sloppy mode”,也称之为“正常模式”。因为翻译原因,正常模式也被翻译为 —— 马虎模式/稀松模式/懒散模式。但这并不是一个官方术语,但是你会经常见到如上的一些说法,其意义就是指代非严格模式,即正常模式。
设立严格模式的目的,主要有以下几个:
- 消除 JavaScript 语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 JavaScript 做好铺垫。
严格模式体现了 JavaScript 更合理、更安全、更严谨的发展方向,包括 IE10 在内的主流浏览器都已经支持它,许多大项目已经开始全面拥抱它。
另一方面,同样的代码,在严格模式中,可能会有不一样的运行结果;一些在正常模式下可以运行的语句,在严格模式下将不能运行。
二、启用严格模式
启用严格模式很简单,就一行语句。(分号可以显式显示,也可以通过自动分号插入。)
"use strict"; // 或者 'use strict'; // 或者(由于 ASI 机制,编写代码时可省略分号) 'use strict'
不支持该模式的浏览器,会把它当作一行普通字符串,加以忽略。
三、调用严格模式
严格模式可以应用到整个脚本或个别函数中。不要在封闭大括弧 {}
内这样做,在这样的上下文中这么做是没有效果的。
严格模式有两种调用方法,适用于不同的场合。
1. 针对整个脚本文件
将 "use strict";
放在脚本文件的第一行,则整个脚本都将以严格模式运行。如果这行语句不在第一行,则无效,整个脚本以正常模式运行。
如果不同模式的代码文件合并成一个文件,这一点需要特别注意。
<script> "use strict"; console.log("这是严格模式!"); </script> <script> console.log("这是正常模式!"); </script>
上述代码表示,一个网页中依次有两段 JavaScript 代码。前一个 <script>
标签是严格模式,后一个是非严格模式。
2. 针对单个函数
将 "use strict";
放在函数体的第一行,则整个函数以严格模式运行。
function strict() { "use strict"; return "这是严格模式!"; } function notStrict() { return "这是正常模式!"; }
3. 脚本文件的变通写法
因为第一种调用方法不利于文件合并,所以更好的做法是借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。
(function () { "use strict"; // ... })();
4. 关于 "use strict" 放在 Program 或 FunctionBody 的第一行问题
严格地说,只要前面不是产生实际运行结果的语句,
"use strict";
可以不在第一行,比如前面包括一些注释、或者是一些 JS 引擎无法识别的指令序言等。
例如:
function fn() { "use bar"; "abc"; "use strict"; // 因为这完全符合指令序言 — 多指令共存的语法. 所被应用的代码仍然会进入严格模式。 }
ES5 会把 "use bar"
和 "abc"
也作为指令序言的某个指令处理,由于 JS 引擎不认识该指令,只认识 "use strict"
指令,则同样会进入严格模式.
四、严格模式对于语法和行为的改变
严格模式对 JavaScript 的语法和行为,都做了一些改变。
1. 全局变量显式声明
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。
"use strict"; name = "Frankie"; // Uncaught ReferenceError: name is not defined for (i = 0; i < 2; i++) { // Uncaught ReferenceError: i is not defined // ... } // 上述代码在正常模式下,是可以正常运行的,而在严格模式下就会报错(引用类型错误)
因此,严格模式下变量都必须先声明再使用。抛开 JavaScript 设计的不合理、缺陷、甚至是 Bug,或者是其他看起来很反人类的东西,在了解历史原因和其中原理之后,为了代码可读性都理应如此。
2. 静态绑定
JavaScript 语言的一个特点,就是允许“动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。
严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。
具体来说,涉及以下几个方面。
(1)禁止使用 with 语句
因为 with
语句无法在编译时就确定,属性到底归属哪个对象。
"use strict"; var obj = { name: "Frankie" } // 语法错误,Uncaught SyntaxError: Strict mode code may not include a with statement with (obj) { name = "Mandy"; }
(2)创设 eval 作用域
正常模式下,JavaScript 语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval 作用域。
正常模式下,eval
语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval
语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于 eval 内部。
"use strict"; var name = "Frankie"; console.log(eval("var name = 'Mandy'; name")); // "Mandy" console.log(name); // "Frankie"
3. 增强的安全措施
(1)禁止 this 关键字指向全局对象
function fn1() { // 返回 false,因为 "this" 指向全局对象 return !this; } function fn2() { "use strict"; // 返回 true,因为严格模式下,this 的值为 undefined。 return !this; }
因此,使用构造函数时,如果忘加 new
关键字时,this
不再指向全局对象,而是报错。
// 构造函数 function Fn() { "use strict"; this.name = "Frankie"; // Uncaught TypeError: Cannot set property 'name' of undefined }; // 直接当作普通函数调用就会报错,因为此时 this 为 undefined。 Fn();
(2)禁止在函数内部遍历调用栈
function fn() { "use strict"; fn.arguments; // 报错 fn.caller; // 报错 // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them } fn();
4. 禁止删除变量
严格模式下无法删除变量。
"use strict"; var name; delete name; // 语法错误,Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
只有 configurable
设置为 true
的对象属性,才能被删除。
"use strict"; var obj = Object.create(null, { "name": { value: "Frankie", configurable: true } }); delete obj.name; // 删除成功
5. 显式报错
正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。
"use strict"; var obj = {}; Object.defineProperty(obj, "name", { value: "Frankie", writable: false }); obj.name = "Mandy"; // 报错,Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
严格模式下,对一个使用 getter
方法读取的属性进行赋值,会报错。
"use strict"; var obj = { get name() { return "Frankie"; } }; obj.name = "Mandy"; // 报错,Uncaught TypeError: Cannot set property name of #<Object> which has only a getter
严格模式下,对禁止扩展的对象添加新属性,会报错。
"use strict"; var obj = {}; Object.preventExtensions(obj); obj.name = "Frankie"; // 报错,Uncaught TypeError: Cannot add property name, object is not extensible
严格模式下,删除一个不可删除的属性,会报错。
"use strict"; // 报错,Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] } delete Object.prototype;
6. 重名错误
严格模式新增了一些语法错误。
(1)对象不能有重名的属性
在 Gecko 版本 34 之前,严格模式要求一个对象内的所有属性名在对象内必须唯一。正常模式下重名属性是允许的,最后一个重名的属性决定其属性值。因为只有最后一个属性起作用,当代码要去改变属性值而不是修改最后一个重名属性的时候,复制这个对象就产生一连串的 bug。在严格模式下,重名属性被认为是语法错误:
这个问题在 ECMAScript 6 中已经不复存在(bug 1041128)。
"use strict"; // 语法错误:SyntaxError: property name age appears more than once in object literal var obj = { age: 18, age: 20 }
(2)函数不能有重名的参数
正常模式下,如果函数有多个重名的参数,最后一个重名参数名会掩盖之前的重名参数,之前的参数仍然可以通过 arguments[i]
来访问。然而,这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了),所以在严格模式下重名参数被认为是语法错误。
"use strict"; // 语法错误:Uncaught SyntaxError: Duplicate parameter name not allowed in this context function fn(x, x, y) { return; }
7. 禁止八进制表示法
ECMAScript 并不包含八进制语法,但所有的浏览器都支持这种以零(0
)开头的八进制语法:0100 === 64
,还有 "\045" === "%"
。在 ECMAScript 6 中支持为一个数字加 "0o"
的前缀来表示八进制数.
"use strict"; var n = 0100; // 语法错误:Uncaught SyntaxError: Octal literals are not allowed in strict mode.
var n = 0o100; // ES6 八进制数表示法
8. arguments 对象的限制
arguments 是函数的参数对象,严格模式对它的使用做了限制。
(1)不允许对arguments赋值
"use strict"; arguments++; // 语法错误:Uncaught SyntaxError: Unexpected eval or arguments in strict mode var obj = { set p(arguments) { } }; // 语法错误,同上 try { } catch (arguments) { } // 语法错误,同上 function arguments() { } // 语法错误,同上 var fn = new Function("arguments", "'use strict'; return 17;"); // 语法错误,同上
(2)arguments不再追踪参数的变化
function fn1(x) { x = 2; return [x, arguments[0]]; } fn1(1); // 正常模式为 [2, 2] function fn2(x) { "use strict"; x = 2; return [x, arguments[0]]; } fn2(1); // 严格模式为 [2, 1]
(3)禁止使用 arguments.callee
这意味着,你无法在匿名函数内部调用自身了。
"use strict"; var fn = function () { return arguments.callee; }; fn(); // 报错:Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
9. 函数必须声明在顶层
将来 JavaScript 的新版本会引入"块级作用域"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。
严格模式禁止了不在脚本或者函数层面上的函数声明。在浏览器的普通代码中,在“所有地方”的函数声明都是合法的。这并不在 ES5 规范中(甚至是 ES3)!这是一种针对不同浏览器中不同语义的一种延伸。未来的 ECMAScript 版本很有希望制定一个新的,针对不在脚本或者函数层面进行函数声明的语法。在严格模式下禁止这样的函数声明对于将来 ECMAScript 版本的推出扫清了障碍:
"use strict"; if (true) { function f() { } // !!! 语法错误,SyntaxError: in strict mode code, functions may be declared only at top level or immediately within another function f(); } for (var i = 0; i < 5; i++) { function f2() { } // !!! 语法错误,SyntaxError: in strict mode code, functions may be declared only at top level or immediately within another function f2(); } function baz() { // 合法 function eit() { } // 同样合法 }
关于这块内容,可以看下这两篇文章或讨论:
10. 保留字
为了向将来 JavaScript 的新版本过渡,严格模式新增了一些保留字:implements
、interface
、let
、package
、private
、protected
、public
、static
、yield
。使用这些词作为变量名将会报错。
function package(protected) { // 语法错误,Uncaught SyntaxError: Unexpected strict mode reserved word "use strict"; var implements; // 语法错误 }
此外,ECMAScript 5 本身还规定了另一些保留字(class
、enum
、export
、extends
、import
、super
),以及各大浏览器自行增加的 const
保留字,也是不能作为变量名的。
未完待续...