JS代码编写灵活,但语法纠错功能较弱,咱们常常会由于一些小Bug而调试许久。所以ES5引入了Strict模式,强制开发者使用更为严谨的编码规范,下降脚本出错的几率。浏览器
在函数的第一行加上"use strict"
字符串后,即代表在这个函数中使用Strict模式:性能优化
function abc(){ "use strict" // ... }
若是在整个JS脚本的第一行写上"use strict"
,则整个脚本都会处于Strict模式中。函数
一旦开启Strict模式,就应该遵循如下编码规范:性能
变量必须被显式声明测试
禁止修改只读属性优化
禁止删除变量、函数、函数参数、原型链ui
禁用with
语句this
禁用八进制数值编码
同一个函数不能有同名形参es5
禁止改写arguments
的值
直接调用函数时,this
置空
eval
不能污染外层做用域
禁止用保留字、eval
、arguments
做为变量或参数名
禁用caller
与callee
不能在非函数的块级做用域定义函数
若是你违反以上原则,那么脚本会抛出异常,并中止后续的执行(除非你用了try-catch
处理异常)。我以为有必要思考一下,为何这些限制是有意义的。
若是你声明了一个变量,但没用var
关键字,那么这个变量就是全局变量。我知道尽可能避免全局污染已是个共识了,但就怕手一抖:
var name = 'kid' nane = 'wumeng'
变量名打错了,该变的没变,还建立了一个新的全局变量。更麻烦的是,在正常模式下,编译器不会给出任何提示。而Strict模式下,你根本不能缺乏var
(或ES6的let
、const
):
"use strict" abc = 1 // 抛出异常
只读属性也是ES5的新特性,以后会详细说明。总而言之,若是我误认为某个只读属性是可写的,又没有任何提示的话,那么当脚本执行后不是我预期的结果,我就要查上半天了。而Strict模式会明确阻止你修改一个只读属性:
"use strict" var author = {} // 声明author.name为只读属性 Object.defineProperty(author, 'name', { value: 'kid', writable: false }) author.name = 'dik' // 抛出异常,delete author.name时也会
delete
用于删除对象属性,而不应删除其它东西:
"use strict" var a = 1 delete a // 删变量,抛出异常
"use strict" function fn(){} delete fn // 删函数,抛出异常
"use strict" function fn(a){ delete a // 删函数参数,抛出异常 }
"use strict" delete Array.prototype // 删原型链,抛出异常,原型链是个特别的属性
with
虽然很省事,但只有到运行期才能肯定调用哪一个对象,因此没法享受到某些编译期优化,是不推荐的写法,在Strict模式中明确禁用:
"use strict" with(console){ // 抛出异常 log('kid') }
标准中就没支持过八进制,但几乎全部浏览器都实现了。为何这不是个好特性?借用MDN上一个经典例子:
"use strict" var sum = 010 + // 抛出异常 100 + 200 // 就算你不开Strict模式,结果也不是310呐
听说有人误觉得010
与10
等价,加个0
只为对齐……结果固然是错的。
谨防手误:
"use strict" function fn(a, a){ // 抛出异常 console.log(a) }
arguments
的值函数实参与arguments
是互通的,修改其中任何一方,另外一方也会跟着变:
fn(1) function fn(a){ a = 2 console.log(arguments[0]) // -> 2 }
fn(1) function fn(a){ arguments[0] = 2 console.log(a) // -> 2 }
Strict模式会阻止互通,让其各自独立。arguments
将一直保持原始参数值,以便随时取用:
"use strict" fn(1) function fn(a){ a = 2 console.log(arguments[0]) // -> 1 }
"use strict" fn(1) function fn(a){ arguments[0] = 2 console.log(a) // -> 1 }
直接调用某函数时,其this
默认指向window
对象:
fn() function fn(a){ console.log(this) // -> Window{} }
若在Strict模式下遇到这种状况,this
将置为undefined
,以构造更严实的封装,下降全局污染的可能性:
"use strict" fn() function fn(a){ console.log(this) // -> undefined }
固然,不会影响new
出来的实例对象:
"use strict" new Animal() function Animal(a){ console.log(this) // -> Animal{} }
正常模式下,若是在eval
中声明一个变量,则变量的做用域为eval
所在做用域。当你的网页容许用户(或第三方应用)写入自定义脚本时,则可能埋下隐患:
eval('var a = 1') console.log(a) // -> 1
而在Strict模式下,eval
中声明的变量不会流出到外层域:
"use strict" eval('var a = 1') console.log(a) // 异常,a未定义
其实在Strict模式下,除了全局、函数做用域外,还有一个eval
做用域的概念,所以,你仍然能够在eval
中使用声明的变量:
"use strict" eval('var a = 1; console.log(a)') // -> 1
保留字(包括:class
, enum
, export
, extends
, import
和super
)在将来可能成为关键字,而eval
与arguments
有其特殊性,因此Strict模式禁止声明它们:
"use strict" var eval = 1 // 抛出异常 function eval(){} // 抛出异常 function fn(eval){} // 抛出异常 // arguments与保留字状况相同
不过却是能够做为属性名:
"use strict" var obj = { eval: 1 } // 保留字、arguments状况相同
caller
与callee
Strict模式下,caller
与callee
属性不能使用:
"use strict"; function fn(){ console.log(fn.caller) // 抛出异常 } fn()
"use strict"; function fn(){ console.log(arguments.callee) // 抛出异常 } fn()
caller
能知道是谁调用的函数,代价是破坏了封装性,并且性能优化上也有影响;
callee
便是函数自己,这在匿名函数的递归时颇有用,但没法享受尾递归优化,更推荐的作法是给函数取个名字。关于性能方面的详细解释,能够参考这篇文章:为何 arguments.callee 从 ES5 严格模式中移除掉?
听起来很绕,实际上是相似下面的状况:
"use strict"; if(true){ function fn() // 抛出异常 } while(true){ function fn() // 抛出异常 }
以上只是部分举例,块级做用域是指用{}
括起的范围。至于为何禁止这种写法,请参考这篇说明。简单来讲,这么写容易致使浏览器解析的不一致。
我测试了下,打开Strict模式,在Chrome、Opera中这么写并不会抛异常,而Safari、Firefox与IE10+则会遵循标准。
原创,自由转载,请署名,本人博客 kid-wumeng.me