Strict模式下的编码规范
JS代码编写灵活,但语法纠错功能较弱,我们经常会因为一些小Bug而调试许久。因此ES5引入了Strict模式,强制开发者使用更为严谨的编码规范,降低脚本出错的概率。
在函数的第一行加上"use strict"
字符串后,即表明在这个函数中使用Strict模式:
function abc(){ "use strict" // ... }
如果在整个JS脚本的第一行写上"use strict"
,则整个脚本都会处于Strict模式中。
一旦开启Strict模式,就应该遵循以下编码规范:
变量必须被显式声明
禁止修改只读属性
禁止删除变量、函数、函数参数、原型链
禁用
with
语句禁用八进制数值
同一个函数不能有同名形参
禁止改写
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语句
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置空
直接调用某函数时,其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
所在作用域。当你的网页允许用户(或第三方应用)写入自定义脚本时,则可能埋下隐患:
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
禁止用保留字、eval、arguments作为变量或参数名
保留字(包括: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
即是函数本身,这在匿名函数的递归时很有用,但无法享受尾递归优化,更推荐的做法是给函数取个名字。关于性能方面的详细解释,可以参考这篇文章:
不能在非函数的块级作用域定义函数
听起来很绕,其实是类似下面的情况:
"use strict";if(true){ function fn() // 抛出异常}while(true){ function fn() // 抛出异常}
以上只是部分举例,块级作用域是指用{}
括起的范围。至于为什么禁止这种写法,请参考。简单来说,这么写容易导致浏览器解析的不一致。
我测试了下,打开Strict模式,在Chrome、Opera中这么写并不会抛异常,而Safari、Firefox与IE10+则会遵循标准。
原创,自由转载,请署名,