ECMAScript5 中的严格模式 ('use strict') 是该语言的一个受限子集, 修正了 JavaScript 这门语言的一些缺陷, 并提供了健壮的查错功能和安全机制. JavaScript 中的严格模式分为两个级别, 全局和函数级, 取决于'use strict'指令的位置.
在 JavaScript 中存在一些'不好的东西'(在浏览器环境中), 比如全局变量自动成为全局对象 Windows 的属性, 给未声明的变量赋值全局作用域中自动声明一个同名变量, 以及函数级作用域中 undefined 重写的问题等等. 而严格模式修正了一些问题, 也引发了一些奇怪的问题.
先说一下严格模式与非严格模式的区别:
严格模式下禁止使用 with 语句
with 语句一般是下面这种形式:
with( obj )
statement
一开始设计 with 的目的可能就是为了简化代码编写. 如下:
- // 初始化对象
- var obj = {
- a = 1,
- b = 2,
- c = 3
- }
- // 修改对象属性
- obj.a = 2;
- obj.b = 3;
- obj.c = 4;
- // 等同于下面的写法
- with( obj ) {
- a = 2;
- b = 3;
- c = 4;
}
with(obj)可以在代码块中的变量解析时将 obj 对象添加到作用域链的最前端, 当代码块中的变量进行解析, 根据变量的解析规则会首先查找 obj 对象中是否存在同名属性. 但是这种破坏词法作用域的手段不仅影响代码的执行速度, 还会产生意想不到的结果.
- var obj = {};
- with( obj ) {
- a = 2;
}
这段代码中的 with 语句并不会为 obj 创建一个 a 属性, 在代码快中的 a 变量进行解析时并未在 obj 对象中找到同名属性. 这时候变量解析会继续沿着作用域链向上查找, 若是找到了同名变量 a, 就修改它的值(但如果这个变量是 const 声明的, 你懂的). 如果未找到同名变量 a, 在非严格模式下就会创建一个全局变量 a 并赋值为 2. 这是多么糟糕的事!!! 该禁!
严格模式下限制 eval( )的超能力
eval( )一种可以改变 JavaScript 词法作用域的神奇函数
- // 超能力一 访问并拥有更改当前作用域内变量或函数的能力
- var a = 2;
- eval( 'a = 3' );
- console.log( a ); // 3
- // 超能力二 拥有在当前作用域创建变量或函数的能力.
- eval( 'var b = 6' );
- console.log( b ); // 6
- // 别名 eval;
- var otherEval = eval;
这里说一下如果将 eval 赋值给一个变量在 ES3 中规定的是会报错, ES5 中不会报错但是限制了别名 eval 的能力, ES5 规定别名 eval 不具有读, 写, 定义函数作用域内变量和函数的能力, 它只能 读, 写, 定义全局作用域中的变量和函数.
在严格模式下 eval()扩号中的内容会创建专属于 eval 的词法作用域 (即使被限制了还是这么牛逼) 这个作用域和函数级作用域一样, 可以通过作用域链访问并修改外部作用域的变量和函数, 但是 eval 内部创建的变量和函数外部是访问不了的. 所以 eval 内部创建的变量和函数就不会暴露到 eval 所处的作用域中了.
严格模式下所有的变量都需要先声明再赋值, 如果为一个未声明的变量赋值会报错
在非严格模式下会创建一个全局变量并自动成为 Windows 的属性. 这带来的后果就是屏蔽了 Windows 对象上的同名属性. 在这里说一下, 不管是否是严格模式使用 var 申明的变量都会成为 Windows 对象的属性(而且是不可配置的, 不能通过 delete 删除).
如下图:
严格模式下修改对象只读属性和为不可拓展的对象创建属性都会报错.
在非严格模式下会失败并不会报错. 由于这个原因我说一下关于 JavaScript 的另一个'坑'-- undefined.undefinedJavaScript 的基本数据类型之一, 而且只有 undefined 这一个值. 用来作为声明但为赋值的变量的值. 下面细数一下它的'坑'.
- var a;
- console.log( typeof a ) // undefined
- console.log( typeof b ) // undefined
这个也不算坑, 有人认为这是 typeof 的一种安全防范机制. 在《你不知道的 JavaScript 中》一书中作者认为使用 typeof 操作符检测未声明变量应返回 undeclared. 之所以提这个是提醒自己下面这个知识点.
console.log( typeof b ); // 报错
let b; or const b = someValue;
这时候 typeof 的安全防范机制就敌不过 temporal dead zone (let,const 声明块级绑定带来的暂时性死区)了. 这个暂时不说, 今天写笔记也不是为了说这个. 下面说一下 undefined 真正的'坑'
首先明确一件事, 作为和 null 这个基友一样只有唯一值的数据类型, undefined 竟然不是关键字, 只是一个全局变量(导致了它面临被屏蔽的危险), 人家 null 就是关键字. 幸好 ES5 给了一些补救措施, 在 ES5 中 undefined 作为 Windows 对象的一个只读属性, 唉, 这就有点变化了, 最起码在全局环境中声明同名变量屏蔽不了它了.
- // 非严格模式
- var undefined = 2;
- console.log( undefinded ); // undefinded
- // 注意如果是 let 或 const 声明, 会报重复声明的错误. 因为同一作用域下 let const 不允许重复声明.
- let undefined = 2; or const undefined = 2;
- // 严格模式
- var undefined = 2;
- console.log( undefined ); // error
这个看起来是不是合理多了. 防止了 undefined 被同名变量覆盖. 但是还是有意外的... 看下面:
- // 不管是不是严格模式都会屏蔽
- function show() {
- var undefined = 2
- console.log( undefined );
- }
- show(); // 2
这还是被屏蔽了啊... 这是因为治标不治本啊! 在全局作用域中 (非严格模式下) 当我们使用 var 声明或直接给变量 undefined 赋值, 他会自动成为 Windows 的属性, 因为 undefined 是只读属性, 所以这个赋值就会失败, 但是默认不报错. 我们使用 let 或 const 声明时又由于重复声明, 会报重复声明的错误. 在严格模式下 var 声明不仅会失败, 还会因为修改只读属性报错. 这就是全局环境不能修改 undefined 值的原因.
因为在函数级作用域中以上两种情况都不存在, undefined 又不是关键字, 可以用做标识符. 所以当在函数内部 undefined 的值就可以被修改了. 这时候如果想要一个可靠地 undefined 的值, 一种方法是通过 Windows 对象访问, 另一种是使用 void 操作符.
严格模式下函数作为函数调用 this 指向 undefined
这句话读起来有点绕口, 其实就是函数 this 的默认绑定问题. 在非严格模式下, 函数直接作为函数调用时 this 默认绑定报 Windows 对象上. 这个特性可以用来检测当前环境是否是严格模式.
- function isStrict() {
- return this === undefined;
- }
- var strictMode = isStrict(); // false
严格模式下 call 和 apply 的调用的函数的 this 值就是其第一个参数
在非严格模式下如果传入 call 或 apply 的第一个参数是 null 或 undefined, 会默认其被 Windows 对象替代. 如果是基本类型, 则包装为对应的包装对象.
- var a = 2;
- function foo(b ) {
- console.log( this.a + b );
- }
- foo.call( null, 3 ); // 5
- // 这里介绍一种安全的机制, 即使不使用严格模式也很安全.
- // 如果仅仅使用 call 或 apply 调用一个函数并不涉及 this 绑定时,
可以给它传入一个完全为'空'的对象.
- var empty = Object.create( {
- } );
- Math.max.apply( empty, array );
严格模式函数的 arguments 对象仅包含传入实参的副本. 以及禁止通过其 caller 和 callee 检测函数调用栈的能力
arguments 这个对象谁用谁知道. 函数重载啊, 柯里化, 通过 caller 属性进行函数递归(这个没人用吧哈哈), 实现各种绑定的 polyfill, 简直是神器有木有(ES6 使用 REST 参数代替). 之所以对其进行限制还是因为他无法无天的能力. 看看下面的代码:
- function foo() {
- console.log( arguments[ 0 ] );
- }
- foo( 5 ); // 5
- function foo( a ) {
- console.log( a + arguments[ 1 ] );
- }
- foo( 2, 3 ); // 5
这种能力在即使没有形参或者传入实参数量多于形参时我们也可以获取实参.
arguments 对象不仅获取了实参还建立了与形参之间的关联(和形参指向同一个值得引用)
- function foo( a ) {
- arguments[ 0 ] = 5;
- console.log( a );
- }
- foo( 2 ); // 5
但是这种关联只在传入对应实参后才会建立. 所以当同时访问命名参数和与其对应的 arguments 类数组单元是就容易产生混乱
- function foo( a ) {
- a = 5;
- console.log( arguments[ 0 ] );
- console.log( a );
- }
- foo(); // undefined 5
在 ES6 中函数使用默认参数时也会引起混乱
- function foo( a = 2 ) {
- console.log( a );
- console.log( arguments[ 0 ] );
- console.log( arguments.length );
- }
- foo(); // 2 undefined 0
所以严格模式为了避免这种情况出现, 使 arguments 对象仅仅包含传入实参的副本, 并不会与形参之间建立起关联
- 'use strick'
- function foo( a ) {
- arguments[ 0 ] = 5;
- console.log( a );
- console.log( arguments[ 0 ] );
- }
- foo( 2 ); // 2 5
因此如果我们期望使用 arguments 对象这种神奇能力, 就不要同时访问形参何其对应的 arguments 类数组单元. 或者使用 ES6 的 REST 参数.
严格模式下 delete 运算符后面跟非法操作数时这会报错, 使用 delete 删除不可配置的属性时报错
在非严格模式下这两种行为仅仅是简单的返回 false 并不会报错. 这是为了健壮查错机制.,
严格模式下函数声明中存在两个或两个以上个同名参数会报错
在非严格模式下最后一个同名形参覆盖前面所有的同名形参
- function foo( a, a ) {
- console.log( a );
- console.log( arguments[ 0 ] );
- console.log( arguments[ 1 ] );
- }
- foo( 2 ); // undefined 2 undefined
在严格模式下不允许使用以 0 开头的八进制整数直接量
在非严格模式下不报错并将其转化为十进制. ES6 中的八进制以 0o 开头, 如: 0o32
在严格模式中 eval,arguments 当做关键字
严格模式下对象直接量中定义两个或两以上的同名属性会报错(ES6 中支持属性名重复定义)
由于 ES6 支持属性名重复定义说以这个没啥用, 但是我们要注意如下:
- var obj = { a: 1, a: 2, a: 3 };
- for ( var key in obj ) {
- console.log( obj[ key ] );
- }
- // 3
从上面的代码看一看出仅保留最后一个创建的同名属性.
这就是我所理解的严格模式以及 JavaScript 中可爱的'坑'. 还有很多我还没学到, 也没被'坑', 或者说还未发觉. 本人水平有限, 上面也只是自己在书上及实践中对 JavaScript 的理解. 有什么问题欢迎大家指出, 希望在大家们的批评下取得进步.
来源: https://juejin.im/post/5c8ddb50f265da2da835c13b