该标准以标题 ECMA-262,第 5 版(以下简称 ES5)进行发布,是 ECMAScript 规范的最新版本。ECMAScript 是 JavaScript — 当今最重要的 web 语言 — 的基础标准。由于 JavaScript 语言还是 Adobe ActionScript(以及其他风格)的基础,因此可以说,ECMAScript 标准是当今和未来 web 交互的核心。
经过一个漫长的、偶尔颇具争议的过程,ES5 最终于 2009 年 12 月发布,代表 JavaScript 语言的一个渐进改进。不管怎样,无论此规范开发过程中出现了什么摩擦,最终结果都包含了一些对 JavaScript 语言的优秀增强。本文首先简要回顾此规范漫长的开发史,然后逐一介绍它的众多重要新特性和新概念。
毫不奇怪,考虑到参与这个日益重要的规范的开发的众多公司和个人,通往 ES5 的道路不仅漫长,而且充满变数。此前称为 European Computer Manufacturers Association (ECMA) — 现在简称为 Ecma — 的组织是一个国际计算机和信息系统标准组织。大多数 web 标准(包括密切相关的 DOM 规范)都由 W3C 管理。但 JavaScript/ECMAScript 是个例外。在其史诗般的 JavaScript 演示系列中,Yahoo! 的 JavaScript 架构师 Douglas Crockford 对这一情况进行了精彩的解释。
1996 年,JavaScript 是来自 Netscape 的一个广泛流行的浏览器创新,正在 web 浏览器领域迎头赶上的 Microsoft? 对其进行了反向工程并将其呈现为 Windows? Internet Explorer? 浏览器中的一个特性。Crockford 解释:
“Netscape 觉得(JScript)面临危险:噢,不,我们(的技术)就要被吞并和扩展了,因此我们需要根据这种技术制定出一种标准。因此,他们找到 W3C,对他们说,好吧,我们发明了一种语言,请你们标准化。W3C 一直在等待一个机会以便对 Netscape 说 ‘下地狱去吧’,于是他们对 Netscape 说,‘下地狱去吧!’ ”
“Netscape 前往 ISO 和其他地方,想找到一个可以购买标准的地方,最终他们找到了 European Computer Manufacturers Association,这对于一个加利福尼亚软件公司来说是一个漫长旅程,但那就是他们的终点。”
好消息是,即使拥有这样一个四处流浪的起源故事,ECMAScript 还是为整个 web 开发社区提供了不错的服务 — 尽管 Netscape 自身发展并不顺利。
在 ES5 之前,此规范最近的主要版本是第 3 版,于 1999 年 12 月发布。第 3 版发布之后,人们就开始着手开发最初称为 ES4 的版本。2003 年发布了一个临时报告,这导致了 Adobe 和 Microsoft 的实现工作,但主标准的进展一直停滞不前,直到 2005 年秋,Task Group 1 of Ecma Technical Committee 39 (TG1) 才开始定期召开会议。
人们后来才发现,那是重新启动开发流程的好时机,因为由 Asynchronous JavaScript + XML (Ajax) 引领的 JavaScript 复兴从那时才真正开始。Jesse James Garrett 的开创性文章 “Ajax: A New Approach to Web Applications”(参见 参考资料)于同年 2 月发表,您现在以为理所当然的一些技术设计范式那时正在快速形成。
根据他们在 ES3 上的工作(报告于 2003 年发布)以及从 Adobe 和 Microsoft 的实现获取的经验教训,TG1 积极致力于对 JavaScript 语言的大幅改进。尽管详细介绍规范的细节超出了本文范围,但规范雄心勃勃的目标在工作组自身中间导致了矛盾。
2007 年,这种内部争论 — 一种触及规范根本目标的矛盾 — 公诸于众。公众可见的分歧发生在 Microsoft 的 Chris Wilson 和此语言的初始设计者、Mozilla 的 Brendan Eich 之间。这两个人代表了在规范目标上的两种相反观点。
Eich 是激进派代表人物,支持那时为 ES4 提出的 JavaScript 语言大幅增强。而 Wilson 是温和派代表,认为大幅增强最终会 “破坏 web”,原因是那些变化对于现有规范来说过于剧烈。那时,ES4 还不能向后兼容 — 这个问题在 WG 在 HTML5 规范工作中所做的决策中得到考虑。
这种分歧最终导致标准组织分裂为两大阵营:ES4 反对者(由 Microsoft 和 Yahoo! 领导)分裂出去并开始开发 ECMAScript 3.1。同时,Eich 和其他 ES4 支持者继续开发 ES4 规范。认识到这种分裂的负面效果,且可能是由于认识到 ECMAScript 对 web 未来的重要性日益增加,这个标准组织于 2008 年 7 月重新联合起来(在所谓的 “Oslo Meeting” 会议中),以便弥补他们的分歧。值得庆幸的是,TG1 在那次会议中达成了新的共识。
Eich 在所在的小组的邮件列表上宣布的计划原来是要关注在 3.1 规范上的当前工作,后来尽管计划目标有所缩减(至少与 ES4 相比),但仍然是雄心勃勃 JavaScript 改造计划,该计划称为 ES-harmony。那个 3.1 分支最终变成了 ES5。
现在您已经了解了这个规范的历史,下面我们来看看所有的艰苦工作和偶发争论所产生的结果。
在这个最新版标准中,有几个领域受到了关注。这个版本包含两个重要领域,还有几个较小、但仍然有用的更改和增加。我已根据对新的 ES5 特性的支持提供了一些代码样例。要亲自试用这些样例,必须运行 Internet Explorer 版本 9、Mozilla Firefox 版本 4 或 Google Chrome 版本 6 或更高版本。您还需要一个打开的控制台。
注意:在本文撰写之时,Firefox 4 是惟一支持严格模式的浏览器。要了解最新兼容性信息,请查看 ES5 Compatibility Table(参见 参考资料 获取链接)。
新规范中的一个有趣部分是严格模式的引入。严格模式是一种许可式(opt-in)机制,允许开发人员使用 JavaScript 语言的一个有限但更整洁的子集。根据 Doug Crockford 的精神,严格模式旨在修复或限制该语言的一些劣迹斑斑的 “坏部分”。
严格模式可以在脚本或函数级别实现。这很不错,因为它允许模块和库作者选择编写严格代码,而不会影响环境的其余部分。有一点需要牢记:由于性能因素连接严格和非严格脚本可能会导致意想不到的结果。请参见 参考资料 部分中 Amazon 的 bug 了解一个示例。
要在脚本级别激活严格模式,可以在文件中所有其他语句之前包含语句 use strict
。清单 1 展示了一个示例。
"use strict";var myNamespace = { myMethod : function(){ //strict code }} |
在函数级别激活严格模式的方法类似:在函数主体中的其他任何语句之前包含语句 use strict
,如清单 2 所示。
function getStrict(){ "use strict"; //strict code} |
有一点很有趣,应引起注意:用于激活严格模式的语法只是一个字符串表达式语句,这意味着,现在在您的代码中包含该语句本身并不会带来什么坏处。理解它的浏览器会打开该子集。不理解它的浏览器将会将其视为一个表达式语句,忽略它,然后继续处理下一行代码。当然,如果只有少数几个浏览器支持严格模式,激活严格模式可能会导致其他问题,但 Use Strict 指令本身不是其中之一。
下面几个小节简单展示严格模式在您的代码上的效果。尽管这不是对严格模式的每个方面的全面检查,但这些示例展示了这个新模式的主要特征。理解严格模式的最好方法是在一个支持严格模式的浏览器中试用这些示例。
如果足够大胆,您可以将这条指令放到您的一个现有脚本的顶端,看看会导致多少错误(或者没有错误,如果您已经小心翼翼地避免了这些陷阱的话)。
严格模式中我特别喜欢的一个部分,看起来就像代码审查中的一些挑剔部分。严格模式将标准脚本中通常会忽略或静默失败的几种常见负面编码模式转变为实际错误。最常见的一种模式是向一个没有使用 var
语句声明的未声明变量分配一个值,意外创建一个全局变量。在标准代码中,这只是一种不良实践。在严格模式中,将抛出一个引用错误,如清单 3 所示。
function nopullution(){ "use strict"; myAccidentalGlobal = "polluting the global namespace is for losers";}nopullution();>>>Error: assignment to undeclared variable myAccidentalGlobal |
在某些情况下,可以通过更改 this
关键字的绑定方式来防止意外全局变量。例如,如果一个构造器函数被直接调用,而不是使用新前缀,this
将被绑定到全局对象。在这种情况下,严格模式将 this
绑定到 undefined
,防止意外破坏全局名称空间。
另外,原来只会静默失败的动作(比如删除一个不可删除的属性或重复参数或属性名称)现在会抛出错误。清单 4 展示了一些示例。
"use strict"delete Array.prototype;>>>Error: property "use strict";Array.prototype is non-configurable andcan't be deleted"use strict"var myObj ={ dupe: 1, dupe :2}>>>Error: property name dupe appears more than once in object literal |
严格模式还通过消除依赖动态绑定的 JavaScript 语言特性来实施变量名称静态绑定。为此,严格模式通过限制 eval
在调用上下文中创建变量的能力来禁用 with
语句。在严格模式中,eval
在一个沙盒中执行,该沙盒在 eval
结束后被销毁。这个特性的一个好处是有利于 JavaScript 压缩工具提高压缩比率,原因是它们能优化静态绑定。
第二个受到关注的领域是针对核心 Object
的一组急需增强。Object
受到了几个新特性的增强,这些新特性向 ECMAScript 继承性工具包添加了一些必须的强大技巧。
您通过 Object.create()
方法、使用一个指定的原型对象和一个额外的属性对象创建一个新对象。这是一个用于对象创建、继承和重用的强大的新接口。使用 create()
,可以设置继承性并增强对象 — 这一切都在一个整洁定义的接口中完成。借助可用的 Object
属性提供的新特性,JavaScript 继承性已经变成一项更加简单的工作。清单 5 提供了一个简单示例。
//create a simple, traditional object that has some propertiesvar marvelProto = { publisher : "Marvel Comics", founded : "1939", founder : "Martin Goodman", headquarters : "417 5th Avenue, New York, NY, U.S."}//create a new object that inherits properties from the simple object//create() takes two arguments. the object that will serve//as the new object's prototype and an optional set of //new properties to populate the new object var FantasticFour = Object.create( marvelProto, { title : { value : "Fantastic Four", }, year : { value : "1961", } });console.log( FantasticFour.title +" was first published by "+ FantasticFour.publisher +" in "+ FantasticFour.year); |
一组新特性属性(property attributes)增强了 create()
的威力,支持更加精细地控制继承性。上一个示例展示了一个例子:显式标记的 value
。其他三个布尔标志极大地增强了灵活性和您对您的 Object
s 的控制能力:
enumerable
表明此特性在 for...in
循环和新的 Object.keys()
方法中是否可见。 上一个示例可以使用这些新特性属性进行改进,锁定几个定义后禁止更改的属性。新版本如清单 6 所示。
var marvelProto = Object.create( //pass in the default object as a template {}, { //and then create more finely defined properties publisher : { value : "Marvel Comics", //yes, we want it exposed enumerable: true, //Theoretically, the value might change writable: true, //but it can't be deleted/edited configurable: false }, founded : { value : "1939", enumerable: true, //this won't change, so we stop it from being overwritten writable: false, configurable: false }, founder : { value : "Martin Goodman", enumerable: true, writable: false, configurable: false }, headquarters : { value : "417 5th Avenue, New York, NY, U.S.", enumerable: true, //we might need to edit at some point writable: true, configurable: false } });var FantasticFour = Object.create( marvelProto, { title : { value : "Fantastic Four", enumerable: true, writable: false, configurable: false }, year : { value : "1961", enumerable: true, writable: false, configurable: false } });console.log( FantasticFour.title +" was first published by "+ FantasticFour.publisher +" in "+ FantasticFour.year); |
ES5 继续了可以调优和控制 Object
s 的传统,创建了三个新方法,允许在对象上应用不同的 “锁定” 级别。这些强大、急需的工具极大地提高了 ECMAScript 程序的可预测性。
注意: 这些示例将始终使用严格模式,在代码试图执行锁定模式阻止的动作时强制抛出错误。
Object.preventExtensions() 和 Object.isExtensible()
第一个锁定方法是 Object.preventExtensions()
,该方法阻止将任何新命名的特性添加到对象。Object.isExtensible()
测试命名特性是否可以添加到对象。清单 7 展示了这些方法。
"use strict" var marvelProto = Object.create( //pass in the default object as a template {}, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false } /*...OTHER PROPERTIES*/ });//Let's see if we can add some more info to the objectif (Object.isExtensible(marvelProto)){ //true marvelProto.flagshipCharater = "Spider-Man";};//Stop extensionsObject.preventExtensions(marvelProto);//Adding an Editor in Chief field throws an errormarvelProto.editorInChief = "Axel Alonso";>>>TypeError: Can't add property editorInChief, object is not extensible |
Object.seal() 和 Object.isSealed()
Object.seal
首先调用 Object.preventExtensions()
,阻止添加新特性,然后将所有对象特性的 configurable
标志设置为 false。这允许锁定对象的所有特性(但不锁定对象的值),使其可预测,以用作数据存储。循环一个密封的数据对象的值现在成为一个更加可预测的任务,特别是配合使用 enumerable
标志时。Object.isSealed()
测试对象是否可配置。清单 8 展示了这些方法的应用情况。
"use strict" var marvelProto = Object.create( //pass in the default object as a template {}, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false }, /*...OTHER PROPERTIES*/ //optional properties flagshipCharacter : { value : "Spider-Man", enumerable: true, writable: false, configurable: true }, editorInChief : { value : "Axel Alonso", enumerable: true, writable: false, configurable: true } });//Can we delete configurable properties?if (!Object.isSealed(marvelProto)){ //we can delete marvelProto.flagshipCharacter;};//Seal the objectObject.seal(marvelProto);//deleting the Editor in Chief field throws an errordelete marvelProto.editorInChief;>>>Error: property "use strict";marvelProto.editorInChief is non-configurable and can't be deleted |
Object.freeze() 和 Object.isFrozen()
Object.freeze
调用 Object.seal()
来停止对象的配置,然后将所有对象特性的 writeable
标志设置为 false,提供一个完美静态的对象。在有多个源与数据交互的环境中,完全冻结对象的能力能创建一种此前无法实现的可预测性和安全性级别。
Object.isFrozen()
测试对象是否冻结。清单 9 展示了这种方法的一个应用示例。
"use strict" var marvelProto = Object.create( //pass in the default object as a template {}, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false }, //optional properties flagshipCharacter : { value : "Spider-Man", enumerable: true, writable: false, configurable: true } });//Can we write writeable properties?if (!Object.isFrozen(marvelProto)){ //we can marvelProto.flagshipCharacter = "Iron Man";};//Seal the objectObject.freeze(marvelProto);//Changing the Flagship Character throws an errormarvelProto.flagshipCharacter = "Wolverine";>>>Error: "use strict";marvelProto.flagshipCharacter is read-only |
新方法 Object.getPrototypeOf()
返回对象的原型。该方法的值等同于非标准的 Object.__proto__
特性。
Object.keys() 和 Object.getOwnPropertyNames()
Object.keys()
方法返回一个字符串数组,表示一个对象自己的可数(enumerable)特性的名称,这些特性是在 enumerable
标志设置为 true 的正在接受调查的对象上直接定义的特性。Object.getOwnPropertyNames()
与上述方法类似,但还包含 enumerable
标志设置为 false 的特性。清单 10 展示了这两个方法。
var USComicPublishers = { countryOfOrigin : { value : "USA", enumerable: true, writable: false, configurable: false }, medium : { value : "comic books", enumerable: true, writable: false, configurable: false }}var marvelProto = Object.create(//inherits properties from another Object USComicPublishers, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false }, founded : { value : "1939", enumerable: true, writable: false, configurable: false }, founder : { value : "Martin Goodman", enumerable: true, writable: false, configurable: false }, headquarters : { value : "417 5th Avenue, New York, NY, U.S.", enumerable: true, writable: true, configurable: false }, launchComic : { value : function(){ /*fancy code to create a new comic series this isn't data we want to expose when we inspect inherited objects */ return true; }, enumerable: false, writable: true, configurable: false } });//inherits medium from parent, as expectedconsole.log(marvelProto.medium.value);>>>comic books//Our function is availableconsole.log(marvelProto.launchComic());>>>true//BUT... keys returns ONLY properties on object itself//inherited properties are ignored//launchComic (enumerable:false) is also skippedconsole.log(Object.keys(marvelProto));>>>["publisher", "founded", "founder", "headquarters"]//getOwnPropertyNames also operates only on //properties of the object itself, but it//also includes properties that have the//enumerable flag set to false console.log(Object.getOwnPropertyNames(marvelProto);>>>["launchComic", "founder", "founded", "headquarters", "publisher"] |
Get
和 set
将一个对象特性绑定到一个函数,该函数将在试图访问或写入一个属性的值时被调用。Get
不接受参数;而 set
接受一个参数(要设置的值)。清单 11 展示了这两个方法。
var marvelProto = Object.create( //pass in the default object as a template {}, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false }, /*OTHER PROPERTIES*/ }); var FantasticFour = Object.create( marvelProto, { title : { value : "Fantastic Four", }, year : { value : "1961", } });//Use Object.defineProperty to set the getters and setters//Alternatively, this could be set in the Object.create aboveObject.defineProperty(FantasticFour, "bestIssue", { get: function () { return fave; }, set: function (num) { fave = "The best single issue of Fantastic Four is issue #" + num; } });FantasticFour.bestIssue = 51;console.log(FantasticFour.bestIssue);>>>The best single issue of Fantastic Four is #51 |
新规范中受到大量关注的另一个领域是数组。与对 Object
的增强相比,对数组的更改不那么彻底或强大,但有很多新方法值得一提。
Array.forEach
函数接受一个参数:将对数组中的每个元素执行一次的函数。这个被执行的函数接受三个参数:正在被访问的特定元素、该元素的索引、以及数组本身。清单 12 展示这个方便的方法的一个简单用例。
注意:由于每次运行一个独立函数的开销,这个方法比对等的 for
循环慢得多。对于每个步骤,都将创建和销毁一个新的执行上下文,然后另一个级别被添加到作用域链(scope chain)。这些开销将累积起来。
var arr = [8, 10, 13, 10, 8, 1, 5];function logger(element, index, array) { console.log("The value of the element at index " + index + " is " + element); }arr.forEach(logger);>>>The value of the element at index 0 is 8>>>The value of the element at index 1 is 10>>>The value of the element at index 2 is 13>>>The value of the element at index 3 is 10>>>The value of the element at index 4 is 8>>>The value of the element at index 5 is 1>>>The value of the element at index 6 is 5 |
Array.map()
返回一个新数组,新数组通过在原始数组中的每个元素上调用单个函数参数生成。提供的函数将数组中的元素接收为单个参数。清单 13 展示了一个简单示例。
var arr = [8, 10, 13, 10, 8, 1, 5];function square(num){ return num * num; } console.log(arr.map(square)); >>>[64, 100, 169, 100, 64, 1, 25] |
Array.reduce() 和 Array.reduceRight()
Reduce
和 reduceRight
都针对一个数组的两个元素执行一个提供的函数,遍历整个数组,生成单个值。Array.reduce
从左到右执行;Array.reduceRight()
从右到左处理数组。清单 14 展示了这两个方法示例,还展示了顺序的重要性。
var arr = [8, 10, 13, 10, 8, 1, 5];console.log(arr.reduce(function(a, b){ return a + b; })); >>>55console.log(arr.reduce(function(a, b){ return a +" "+ b; })); >>>8 10 13 10 8 1 5console.log(arr.reduceRight(function(a, b){ return a + b; })); >>>55console.log(arr.reduceRight(function(a, b){ return a +" "+ b; }));>>>5 1 8 10 13 10 8 |
Array.filter()
函数返回一个新数组,新数组包含所有通过由单个函数参数实现的测试的元素。清单 15 展示了使用简单的平均/基数测试的示例。
var arr = [8, 10, 13, 10, 8, 1, 5];function odd(element, index, array) { return (element%2);}console.log(arr.filter(odd));>>>[13, 1, 5] |
Array.every()
和 Array.some()
针对单个函数参数测试一个数组元素。如果数组中的所有元素都通过由提供的函数实现的测试,Array.every()
将返回 true。如果数组中的任一元素都通过由提供的函数实现的测试,Array.some()
将返回 true。清单 16 展示了这两个函数使用一个简单的平均/基数测试的示例。
var arr = [8, 10, 13, 10, 8, 1, 5];function odd(element, index, array) { return (element%2);}console.log(arr.every(odd));>>>falseconsole.log([1,3,5].every(odd))>>>trueconsole.log(arr.some(odd));>>>trueconsole.log([2,4,6].some(odd))>>>false |
Array.indexOf() 和 Array.lastIndexOf()
Array.indexOf()
和 Array.lastIndexOf()
函数都返回一个指定元素在数组中的索引,如果不存在,则返回 -1。Array.indexOf
从数组的第一个元素(或一个可选的位置参数)开始向后搜索匹配值,直到数组末尾。Array.lastIndexOf
从数组的最后一个元素(或一个可选位置参数)向前搜索匹配值,直到数组开始。清单 17 显示了这两个方法的示例。
var arr = [8, 10, 13, 10, 8, 1, 5]; console.log("lastIndexOF is " + arr.lastIndexOf(10));>>>lastIndexOF is 3console.log("indexOF is " + arr.indexOf(10));>>>indexOF is 1 |
JSON 是一个流行的数据交换格式,基于 JavaScript 语法的一个子集。ES5 以一种兼容参考 json2.js 实现的方式标准化 JSON。它提供两个方法:JSON.parse()
和 JSON.stringify()
。
JSON.parse()
方法接收一个 JSON 文本字符串,如果有效,将其转换为一个对象或数组。清单 18 展示了 JSON.parse()
的一个简单用例,将一个字符串转换回一个对象。
var marvel = JSON.parse('{"publisher":"Marvel Comics", /*other properties*/}');console.log(marvel.founder);>>>Martin Goodman |
JSON.stringiy()
方法将一个对象转换为一个 JSON 字符串。清单 19 将示例对象转换为一个字符串。
var marvelProto = Object.create( {}, { publisher : { value : "Marvel Comics", enumerable: true, writable: false, configurable: false } /*OTHER PROPERTIES*/ });console.log(JSON.stringify(marvelProto));{"publisher":"Marvel Comics","founded":"1939","founder":"Martin Goodman"} |
在本文结束之前,有另外两个新特性值得一提。
The Date.now()
方法是一个方便的新方法,用于获取当前时间。这个方法替代常见的 (new Date()).getTime()
模式。清单 20 展示了一个 Date.now()
示例,测试发送到 setTimeout
的 500 毫秒参数和函数两次执行之间的实际时钟值之间的差异。
var datePrev = Date.now(), dateNow, count=0;function later(){ if (count < 5 ){ dateNow = Date.now(); console.log(datePrev - dateNow); datePrev=dateNow; setTimeout(later,500); count++; }}later();>>>0>>>-487>>>-500>>>-512>>>-500 |
简言之,Function.prototype.bind()
是来自 Prototype.js 的一个流行调用,使用一个指定的 this
值定义在未来某个日期需要执行的函数。另外,参数可以被指定并绑定到函数,允许对绑定函数进行定制。清单 21 展示了这个强大特性的一个基本示例。
//global values;var favoriteIssue = 27, title = "Detective Comics";var myFavoriteComic = { returnFavorite: function() { if (arguments.length && arguments[0].newComic) { this.favoriteIssue =arguments[0].newComic.favoriteIssue this.title =arguments[0].newComic.title }; //what's this? console.log("this is "+this); //display the valuesconsole.log("My favorite issue of "+ this.title +" is #" + this.favoriteIssue); }, favoriteIssue: 168, title : "Daredevil"};//this is myFavoriteComic//values are internal to the modulemyFavoriteComic.returnFavorite();>>>this is [object Object]>>>My favorite issue of Daredevil is #168//now we let this slip to the global objectvar makeGlobal = myFavoriteComic.returnFavorite;makeGlobal();>>>this is [object Window]>>>My favorite issue of Detective Comics is #27//use bind to bind it to the correct this valuevar boundToModule = makeGlobal.bind(myFavoriteComic);boundToModule();>>>this is [object Object]>>>My favorite issue of Daredevil is #168//pass in options to change the module valuesvar updatedFavoriteIssue = makeGlobal.bind(myFavoriteComic,{ newComic : { favoriteIssue:51, title:"Fantastic Four" }});updatedFavoriteIssue()>>>this is [object Object]>>>My favorite issue of Fantastic Four is #51 |
以一个库的工作所激发的方法结束是结束本文的一种恰当方式,因为最终将由库作者和其他核心 JavaScript 开发人员来揭秘这个规范中的新特性和功能的真正威力。随着越来越多的浏览器提供 ES5 支持,更多开发人员深入其中并开始使用这个最新规范版本,您将真正开始感受到这些工具所提供的可能性。考虑到浏览器供应商和开发人员在这个拥有 10 年历史的规范上所做的工作,看起来这个现代版本将成为未来几年激动人心的 web 发展的基础。
联系客服