缺少灵活性
1 | var o = { foo: 17 }; |
在 ES5 里头你可以决定对象的一个属性是否是可枚举的,可变的,可删除的。
1 | var o = {}; |
还可以定义类似其他面向对象语言的 getter 和 setter 。
1 | var p = { get y() { return "y"; }, |
对象允许锁定属性,避免增加属性、删除属性和修改属性值。
1 | var o = {}; |
ECMAScript 5 的严格模式是JavaScript中的一种限制性更强的变种方式。它的设计源自 Perl 语言的严格模式。
严格模式在语义上与正常的JavaScript有一些不同。
严格模式可以应用到整个script标签或个别函数中。不要在封闭大括弧( {} )内这样做;在这样的上下文中这么做是没有效果的。在eval 代码,Function 代码,事件处理属性,传入 setTimeout方法的字符串和包含整个脚本的块中开启严格模式会如预期一样工作。
为整个script标签开启严格模式,需要在所有语句之前放一个特定语句 "use strict";
(或 'use strict';
)
1 | // 整个语句都开启严格模式的语法 |
但这么做要格外小心代码合并:合并均为严格模式的脚本或均为非严格模式的都没问题,只有在合并严格模式与非严格模式有可能有问题。建议按一个个函数去开启严格模式(至少在学习的过渡期要这样做)。
同样的,要给某个函数开启严格模式,得把 "use strict"
; (或 'use strict';
)声明一字不漏地放在函数体所有语句之前。
1 | function strict() |
严格模式同时改变了语法及运行时行为。变化通常分为这几类:
首先,严格模式下无法再意外创建全局变量。在普通的JavaScript里面给一个拼写错误的变量名赋值会使全局对象新增一个属性并继续“工作”(尽管后面可能出错:在现在的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:
1 | ; |
其次,严格模式会使引起静默失败 (silently fail,注:不报错也没有任何效果) 的赋值操作抛出异常。例如,NaN 是一个不可写的全局变量。在正常模式下,给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈。但在严格模式下,给 NaN 赋值会抛出一个异常。任何在正常模式下引起静默失败的赋值操作 (给不可写属性赋值,给只读属性(getter-only)赋值赋值,给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常:
1 | ; |
第三,在严格模式下,试图删除不可删除的属性时会抛出异常(之前这种操作不会产生任何效果):
1 | ; |
第四,严格模式要求函数的参数名唯一。在正常模式下,最后一个重名参数名会掩盖之前的重名参数。之前的参数仍然可以通过 arguments[i] 来访问,还不是完全无法访问。然而,这种隐藏毫无意义而且可能是意料之外的 (比如它可能本来是打错了),所以在严格模式下重名参数被认为是语法错误:
1 | function sum(a, a, c){ // !!! 语法错误 |
第五,严格模式禁止八进制数字语法。ECMAScript并不包含八进制语法,但所有的浏览器都支持这种以零(0)开头的八进制语法: 0644 === 420
还有 "\045" === "%"
。 有些新手开发者认为数字的前导零没有语法意义,所以他们会用作对齐措施 — 但其实这会改变数字的意义! 八进制语法很少有用并且可能会错误使用,所以严格模式下八进制语法会引起语法错误:
1 | ; |
严格模式简化了代码中变量名字映射到变量定义的方式。很多编译器的优化是依赖存储变量X位置的能力:这对全面优化JavaScript代码至关重要。JavaScript有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生。严格模式移除了大多数这种情况的发生,所以编译器可以更好的优化严格模式的代码。
首先,严格模式禁用 with。 with 所引起的问题是块内的任何名称可以映射(map)到with传进来的对象的属性,也可以映射到包围这个块的作用域内的变量(甚至是全局变量),这一切都是在运行时决定的: 在代码运行之前是无法得知的。严格模式下,使用 with 会引起语法错误,所以就不会存在 with 块内的变量在运行时才决定引用到哪里的情况了:
1 | ; |
一种取代 with 的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性。
第二,严格模式下的 eval 不再为上层范围(surrounding scope,注:包围eval代码块的范围)引入新变量。在正常模式下, 代码 eval("var x;")
会给上层函数(surrounding function)或者全局引入一个新的变量 x 。这意味着,一般情况下, 在一个包含 eval 调用的函数内所有没有引用到参数或者局部变量的名称都必须在运行时才能被映射到特定的定义 (因为 eval 可能引入的新变量会覆盖它的外层变量)。在严格模式下 eval 仅仅为被运行的代码创建变量,所以 eval 不会影响到名称映射到外部变量或者其他局部变量:
1 | var x = 17; |
相应的,如果函数 eval 被在被严格模式下的eval(…)以表达式的形式调用时,其代码会被当做严格模式下的代码执行。当然也可以在代码中显式开启严格模式,但这样做并不是必须的.
1 | function strict1(str) |
因此在严格模式下eval执行的内容跟在非严格模式下eval执行的内容的结果是等同的。
第三,严格模式禁止删除声明变量。delete name 在严格模式下会引起语法错误:
1 | ; |
严格模式让arguments和eval少了一些奇怪的行为。两者在通常的代码中都包含了很多奇怪的行为: eval会添加删除绑定,改变绑定好的值,还会通过用它索引过的属性给形参取别名的方式修改形参。虽然在未来的ECMAScript版本解决这个问题之前,是不会有补丁来完全修复这个问题,但严格模式下将eval和arguments作为关键字对于此问题的解决是很有帮助的。
首先,名称 eval 和 arguments 不能通过程序语法被绑定(be bound)或赋值。以下的所有尝试将引起语法错误:
1 | ; |
第二,严格模式下,参数的值不会随 arguments 对象的值的改变而变化。在正常模式下,对于第一个参数是 arg 的函数,对 arg 赋值时会同时赋值给 arguments[0],反之亦然(除非没有参数,或者 arguments[0] 被删除)。严格模式下,函数的 arguments 对象会保存函数被调用时的原始参数。arguments[i] 的值不会随与之相应的参数的值的改变而变化,同名参数的值也不会随与之相应的 arguments[i] 的值的改变而变化。
1 | function f(a) |
第三,不再支持 arguments.callee。正常模式下,arguments.callee 指向当前正在执行的函数。这个作用很小:直接给执行函数命名就可以了!此外,arguments.callee 十分不利于优化,例如内联函数,因为 arguments.callee 会依赖对非内联函数的引用。在严格模式下,arguments.callee 是一个不可删除属性,而且赋值和读取时都会抛出异常:
1 | ; |
严格模式下更容易写出“安全”的JavaScript。现在有些网站提供了方式给用户编写能够被网站其他用户执行的JavaScript代码。在浏览器环境下,JavaScript能够获取用户的隐私信息,因此这类Javascript必须在运行前部分被转换成需要申请访问禁用功能的权限。没有很多的执行时检查的情况,Javascript的灵活性让它无法有效率地做这件事。一些语言中的函数普遍出现,以至于执行时检查他们会引起严重的性能损耗。做一些在严格模式下发生的小改动,要求用户提交的JavaScript开启严格模式并且用特定的方式调用,就会大大减少在执行时进行检查的必要。
第一,在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象。对一个普通的函数来说,this总会是一个对象:不管调用时this它本来就是一个对象;还是用布尔值,字符串或者数字调用函数时函数里面被封装成对象的this;还是使用undefined或者null调用函数式this代表的全局对象(使用call,apply或者bind方法来指定一个确定的this)。这种自动转化为对象的过程不仅是一种性能上的损耗,同时在浏览器中暴露出全局对象也会成为安全隐患,因为全局对象提供了访问那些所谓安全的JavaScript环境必须限制的功能的途径。所以对于一个开启严格模式的函数,指定的this不再被封装为对象,而且如果没有指定this的话它值是undefined:
1 | ; |
第二,在严格模式中再也不能通过广泛实现的ECMAScript扩展“游走于”JavaScript的栈中。在普通模式下用这些扩展的话,当一个叫fun的函数正在被调用的时候,fun.caller是最后一个调用fun的函数,而且fun.arguments包含调用fun时用的形参。这两个扩展接口对于“安全”JavaScript而言都是有问题的,因为他们允许“安全的”代码访问"专有"函数和他们的(通常是没有经过保护的)形参。如果fun在严格模式下,那么fun.caller和fun.arguments都是不可删除的属性而且在存值、取值时都会报错:
1 | function restricted() |
第三,严格模式下的arguments不会再提供访问与调用这个函数相关的变量的途径。在一些旧时的ECMAScript实现中arguments.caller曾经是一个对象,里面存储的属性指向那个函数的变量。这是一个安全隐患,因为它通过函数抽象打破了本来被隐藏起来的保留值;它同时也是引起大量优化工作的原因。出于这些原因,现在的浏览器没有实现它。但是因为它这种历史遗留的功能,arguments.caller在严格模式下同样是一个不可被删除的属性,在赋值或者取值时会报错:
1 | ; |
未来版本的ECMAScript很有可能会引入新语法,ECMAScript5中的严格模式就提早设置了一些限制来减轻之后版本改变产生的影响。如果提早使用了严格模式中的保护机制,那么做出改变就会变得更容易。
首先,在严格模式中一部分字符变成了保留的关键字。这些字符包括implements, interface, let, package, private, protected, public, static和yield。在严格模式下,你不能再用这些名字作为变量名或者形参名。
其次,严格模式禁止了不在脚本或者函数层面上的函数声明。在浏览器的普通代码中,在“所有地方”的函数声明都是合法的。这并不在ES5规范中(甚至是ES3)!这是一种针对不同浏览器中不同语义的一种延伸。未来的ECMAScript版本很有希望制定一个新的,针对不在脚本或者函数层面进行函数声明的语法。在严格模式下禁止这样的函数声明对于将来ECMAScript版本的推出扫清了障碍:
1 | ; |
这种禁止放到严格模式中并不是很合适,因为这样的函数声明方式从ES5中延伸出来的。但这是ECMAScript委员会推荐的做法,浏览器就实现了这一点。
严格模式主要有以下限制:
可以参考 Transitioning to strict mode
ES5 将一些工程中常用的扩展功能放进了标准库。
Date.now()
numeric timestampbound
方法:当被调用时,this
的值保持不变。