文档章节

[译]ES6:JavaScript中将会有的几个新东西

sheilacat
 sheilacat
发布于 2015/03/21 15:51
字数 4885
阅读 17
收藏 0

原文:http://addyosmani.com/blog/a-few-new-things-coming-to-javascript/


我相信,在ECMAScript.next到来的时候,我们现在每天都在写的JavaScript代码将会发生巨大的变化.接下来的一年将会是令JavaScript开发者们兴奋的一年,越来越多的特性提案将被最终敲定,新一版本的JavaScript将会慢慢得到普及.

本文中,我将会讲几个我个人很期待的,希望能在2013年或者更晚一点使用上的新特性.

ES.next目前的实现情况

可以通过查看Juriy Zaytsev总结的ECMAScript 6兼容性表格,和Mozilla的ES6实现情况页面以及通过使用现代浏览器的最新版本(比如Chrome Canary, Firefox Aurora),来了解目前有哪些已经实现了的ES.next特性.

在Canary中,记得要进入chrome:flags打开'启用实验性JavaScript'选项以激活所有最新的的JavaScript特性.

另外,许多ES.next特性还可以通过使用Google的Traceur转换编译器(这里有一些单元测试的例子)来体验,以及一些shim项目比如ES6-ShimHarmony Collections,也实现了不少新特性.

在Node.js(V8)中使用--harmony命令行选项可以开启一些试验性质的ES.next特性,包括块级作用域,WeakMap等等.

模块

我们已经习惯了将我们的代码分割成为更加便于管理的功能块.在ES.next中,一个模块(module)是就是一个module声明,以及包含在该声明中的一组代码.模块可以用内联方式(inline)声明,也可以引入一个外部的模块文件.一个名为Car的内联模块的写法大致如下:

  1. module Car {  

  2.   // 导入 …  

  3.   // 导出 …  

  4. }  

一个模块实例就是一个被求过值的模块,它已经被链接到了其他的模块身上或者已经有了词法上的封装数据.下面是一个模块实例的例子:

  1. module myCar at "car.js";  

module声明可以使用在如下上下文中:

  1. module UniverseTest {};  

  2. module Universe { module MilkyWay {} };  

  3. module MilkyWay = 'Universe/MilkyWay';  

  4. module SolarSystem = Universe.MilkyWay.SolarSystem;  

  5. module MySystem = SolarSystem;  

一个export声明声明了一个可以被其他模块看到的局部函数或变量.

  1. module Car {  

  2.   // 内部变量

  3.   var licensePlateNo = '556-343';  

  4.   // 暴露到外部的变量和函数

  5.   export function drive(speed, direction) {  

  6.     console.log('details:', speed, direction);  

  7.   }  

  8.   export module engine{  

  9.     export function check() { }  

  10.   }  

  11.   export var miles = 5000;  

  12.   export var color = 'silver';  

  13. };  

一个模块可以使用import导入任何它所需要的其他模块.导入模块会读取被导入模块的所有可导出数据(比如上面的drive(), miles等),但不能修改它们.导出的变量或函数可以被重命名.

再次用到上面导出相关的例子,我们现在可以有选择性的导入一些模块中的功能.

比如我们可以导入drive():

  1. import drive from Car;  

还可以可以同时导入drive()miles:

  1. import {drive, miles} from Car;  

下面,我们要讲一下模块加载器API的概念.模块加载器能够让我们动态的加载所需要的脚本.类似于import, 我们可以使用被导入模块中的所有用export声明过的东西.

  1. // Signature: load(moduleURL, callback, errorCallback)  

  2. Loader.load('car.js'function(car) {  

  3.   console.log(car.drive(500, 'north'));  

  4. }, function(err) {  

  5.   console.log('Error:' + err);  

  6. });  

load()接受三个参数:

  • moduleURL: 表示一个模块URL的字符串 (比如 "car.js")

  • callback: 一个回调函数,接受模块加载,编译,以及执行后的输出结果

  • errorCallback: 一个回调函数,在加载或编译期间发生错误时调用

关于类(class)

我不打算在本文中过多的讲ES.next中的,如果你想知道类和模块将会有什么联系,Alex Russell曾经写过一个很好的例子来说明这件事.

JavaScript中有了类,并不意味着要把JavaScript变成Java.ES.next中的类只是我们已经熟悉的语义(比如函数,原型)的另外一种声明方式

下面是用来定义一个widget的ES.next代码:

  1. module widgets {  

  2.   // ...  

  3.   class DropDownButton extends Widget {  

  4.     constructor(attributes) {  

  5.       super(attributes);  

  6.       this.buildUI();  

  7.     }  

  8.     buildUI() {  

  9.       this.domNode.onclick = function(){  

  10.         // ...  

  11.       };  

  12.     }  

  13.   }  

  14. }  

下面是去糖(de-sugared)后的做法,也就是我们目前正在使用的方法:

  1. var widgets = (function(global) {  

  2.   // ...  

  3.   function DropDownButton(attributes) {  

  4.     Widget.call(this, attributes);  

  5.     this.buildUI();  

  6.   }  

  7.   DropDownButton.prototype = Object.create(Widget.prototype, {  

  8.     constructor: { value: DropDownButton },  

  9.     buildUI:     {  

  10.       value: function(e) {  

  11.         this.domNode.onclick = function(e) {  

  12.           // ...  

  13.         }  

  14.       }  

  15.     }  

  16.   });  

  17. })(this);  

ES.next的写法的确让代码变的更可读.这里的class也就相当于是function,至少是做了目前我们用function来做的一件事.如果你已经习惯并且也喜欢用JavaScript中的函数和原型,这种未来的语法糖也就不用在意了.

这些模块如何和AMD配合使用?

ES.next中的模块是朝着正确的方向走了一步吗?也许是吧.我自己的看法是:看相关的规范文档是一码事,实际上使用起来又是另一码事.在Harmonizr,Require HMTraceur中可以体验新的模块语法,你会非常容易的熟悉这些语法,该语法可能会觉得有点像Python的感觉(比如import语句).

我认为,如果一些功能有足够广泛的使用需求(比如模块),那么平台(也就是浏览器)就应该原生支持它们.而且,并不是只有我一个人这么觉得. James Burke,发明了AMD和RequireJS的人,也曾经说过:

我想,AMD和RequireJS应该被淘汰了.它们的确解决了一个实际存在的问题,但更理想的情况是,语言和运行环境应该内置类似的功能.模块的 原生支持应该能够覆盖RequireJS 80%的使用需求,从这一点上说,我们不再需要使用任何用户态(userland)的模块加载库了,至少在浏览器中是这样.

不过James的质疑是ES.next的模块是否是一个足够好的解决方案,他曾在六月份谈到过自己关于ES.next中模块的一些想法 ES6 Modules: Suggestions for improvement以及再后来的一篇文章Why not AMD?.

Isaac Schlueter前段时间也写过一些自己的想法,讲到了ES6的模块有哪些不足.尝试一下下面这些选项,看看你的想法如何.

兼容目前引擎的Module实现

Object.observe()

通过Object.observe,我们可以观察指定的对象,并且在该对象被修改时得到通知.这种修改操作包括属性的添加,更新,删除以及重新配置.

属性观察是我们经常会在MVC框架中看到的行为,它是数据绑定的一个重要组件,AngularJS和Ember.js都有自己的解决方案.

这是一个非常重要的新功能,它不仅比目前所有框架的同类实现性能要好,而且还能更容易的观察纯原生对象.

  1. // 一个简单的对象可以作为一个模块来使用 

  2. var todoModel = {  

  3.     label: 'Default',  

  4.     completed: false  

  5. };  

  6. // 我们观察这个对象

  7. Object.observe(todoModel, function(changes) {  

  8.     changes.forEach(function(change, i) {  

  9.         console.log(change);  

  10.         /* 

  11.             哪个属性被改变了? change.name 

  12.             改变类型是什么? change.type 

  13.             新的属性值是什么? change.object[change.name] 

  14.         */  

  15.     });  

  16. });  

  17. // 使用时:

  18. todoModel.label = 'Buy some more milk';  

  19. /* 

  20.     label属性被改变了

  21.     改变类型是属性值更新

  22.     当前属性值为'Buy some more milk' 

  23. */  

  24. todoModel.completeBy = '01/01/2013';  

  25. /* 

  26.     completeBy属性被改变了

  27.     改变类型是属性被添加

  28.     当前属性值为'01/01/2013' 

  29. */  

  30. delete todoModel.completed;  

  31. /* 

  32.     completed属性被改变了

  33.     改变类型是属性被删除

  34.     当前属性值为undefined 

  35. */  

Object.observe马上将会在Chrome Canary中实现(需要开启"启用实验性JavaScript"选项).

兼容目前引擎的Object.observe()实现

Rick Waldron的这篇文章有关于Object.observe更详细的介绍.

译者注:Firefox很早就有了一个类似的东西:Object.prototype.watch.

默认参数值

默认参数值(Default parameter values)的作用是:在一些形参没有被显式传值的情况下,使用默认的初始化值来进行初始化.这就意味着我们不再需要写类似options = options || {};这样的语句了.

该语法形式就是把一个初始值赋值给对应的形参名:

  1. function addTodo(caption = 'Do something') {  

  2.     console.log(caption);  

  3. }  

  4. addTodo(); // Do something  

拥有默认参数值的形参只能放在形参列表的最右边:

  1. function addTodo(caption, order = 4) {}  

  2. function addTodo(caption = 'Do something', order = 4) {}  

  3. function addTodo(caption, order = 10, other = this) {}  

已经实现该特性的浏览器: Firefox 18+

译者注:Firefox 15就已经实现了默认参数值,作者所说的18只是说18支持,并不是说18是第一个支持的版本.包括本文下面将要提到的chrome 24+等等,都有这个问题.

块级作用域

块级作用域引入了两种新的声明形式,可以用它们定义一个只存在于某个语句块中的变量或常量.这两种新的声明关键字为:

  • let: 语法上非常类似于var, 但定义的变量只存在于当前的语句块中

  • const: 和let类似,但声明的是一个只读的常量

使用let代替var可以更容易的定义一个只在某个语句块中存在的局部变量,而不用担心它和函数体中其他部分的同名变量有冲突.在let语句内部用var声明的变量和在let语句外部用var声明的变量没什么差别,它们都拥有函数作用域,而不是块级作用域.

译者注:以防读者看不懂,我用一个例子解释一下上面的这句话,是这样的:

let(var1 = 1
     var2 = 2 var3 = 3alert(var3);          alert(var1);
  1. var x = 8;  

  2. var y = 0;  

  3. let (x = x+10, y = 12) {  

  4.   console.log(x+y); // 30  

  5. }  

  6. console.log(x + y); // 8  

实现let的浏览器: Firefox 18+, Chrome 24+

实现const的浏览器: Firefox 18+, Chrome 24+,  Safari 6+, WebKit, Opera 12+

译者注:Firefox很久以前就支持了letconst,但这两个旧的实现都是依据了当年的ES4草 案.和目前的ES6草案有些区别,比如ES4中用const声明的常量并没有块级作用域(和var一样,只是值不可变),let也有一些细微差别,就不说 了.由于很少人使用旧版的Firefox(但我的主浏览器是FF3.6!),即使未来ES6和ES4中的一些东西有冲突,我们基本也可以忽略.

Map

我想大部分读者已经熟悉了映射的概念,因为我们过去一直都是用纯对象来实现映射的.Map允许我们将一个值映射到一个唯一的键上,然后我们就可以通过这个键获取到对应的值,而不需要担心用普通对象实现映射时因原型继承而带来的问题.

使用set()方法,可以在map中添加一个新的键值对,使用get()方法,可以获取到所存储的值.Map对象还有其他三个方法:

  • has(key) : 一个布尔值,表明某个键是否存在于map中

  • delete(key) : 删除掉map中指定的键

  • size() : 返回map中键值对的个数

  1. let m = new Map();  

  2. m.set('todo''todo'.length);  // "todo" → 4  

  3. m.get('todo');                 // 4  

  4. m.has('todo');                 // true  

  5. m.delete('todo');              // true  

  6. m.has('todo');                 // false  

已经实现Map的浏览器: Firefox 18+

Nicholas Zakas的这篇文章有关于Map更详细的介绍.

兼容目前引擎的Map实现

译者注:我翻译过尼古拉斯的这篇文章:[译]ECMAScript 6中的集合类型,第二部分:Map.

作者可能不知道,10月份的ES6草案中,Map.prototype.sizeSet.prototype.size都 从size()方法改成size访问器属性了.同时Map对象新添加的方法还有很多,clear()用来清空一个map,forEach()用来遍历一个 map,还有items(),keys(),values()等.Set对象也类似,有不少作者没提到的方法,下面的Set小节我就不指出了.

另外,在ES5中,在把对象当成映射来使用的时候,为了防止原型继承带来的问题(比如在twitter中,@__proto__能让浏览器卡死),可以用var hash = Object.create(null)代替var hash = {};

Set

正如Nicholas Zakas在他的文章中所说,对于那些接触过Ruby和Python等其他语言的程序员来说,Set并不是什么新东西,但它的确是在JavaScript中一直都缺少的特性.

任何类型的数据都可以存储在一个set中,但每个值只能存储一次(不能重复).利用Set可以很方便的创建一个不包含任何重复值的有序列表.

  • add(value) – 向set中添加一个值.

  • delete(value) – 从set中删除value这个值.

  • has(value) – 返回一个布尔值,表明value这个值是否存在于这个set中.

  1. let s = new Set([1, 2, 3]);  // s有1, 2, 3三个元素.  

  2. s.has(-Infinity);            // false  

  3. s.add(-Infinity);            // s有1, 2, 3, -Infinity四个元素.  

  4. s.has(-Infinity);            // true  

  5. s.delete(-Infinity);         // true  

  6. s.has(-Infinity);            // false  

Set对象的一个作用是用来降低过滤操作(filter方法)的复杂度.比如:

  1. function unique(array) {  

  2.     var seen = new Set;  

  3.     return array.filter(function (item) {  

  4.         if (!seen.has(item)) {  

  5.             seen.add(item);  

  6.             return true;  

  7.         }  

  8.     });  

  9. }  

这个利用Set来进行数组去重的函数的复杂度为O(n).而其他现有数组去重方法的复杂度几乎都为O(n^2).

已经实现Set的浏览器: Firefox 18, Chrome 24+.

Nicholas Zakas的这篇文章有关于Set更详细的介绍.

兼容目前引擎的Set实现

译者注:我翻译过尼古拉斯的这篇文章:[译]ECMAScript 6中的集合类型,第一部分:Set.

如果让我来实现一个ES6下的数组去重函数的话,我会这么写:

 [v

该函数使用到了ES6中的for-of遍历,以及数组推导式.不过效率比上面使用filter去重的方法稍微差点.Firefox最新版中已经可以执行这个函数.

另外,借助于下面将会提到的Array.from方法,还有更简单高效的写法:

>Array.from( Set([ 1, 1, 2, 2, 3, 41,2,3,4]

甚至,借助于ES6中的展开(spread)操作,还有可能这样实现:

>[ ...  Set([ 1, 1, 2, 2, 3, 41,2,3,4]

WeakMap

WeakMap的键只能是个对象值,而且该键持有了所引用对象的弱引用,以防止内存泄漏的问题.这就意味着,如果一个对象除了WeakMap的键以外没有任何其他的引用存在的话,垃圾回收器就会销毁这个对象.

WeakMap的另外一个特点是我们不能遍历它的键,而Map可以.

  1. let m = new WeakMap();  

  2. m.set('todo''todo'.length);  // 异常,键必须是个对象值!  

  3. // TypeError: Invalid value used as weak map key  

  4. m.has('todo');                 // 同样异常!  

  5. // TypeError: Invalid value used as weak map key  

  6. let wmk = {};  

  7. m.set(wmk, 'thinger');  // wmk → 'thinger'  

  8. m.get(wmk);             // 'thinger'  

  9. m.has(wmk);             // true  

  10. m.delete(wmk);          // true  

  11. m.has(wmk);             // false  

已经实现WeakMap的浏览器: Firefox 18+, Chrome 24+.

兼容目前引擎的WeakMap实现

Nicholas Zakas的这篇文章有关于WeakMap更详细的介绍.

译者注:我翻译过尼古拉斯的这篇文章:[译]ECMAScript 6中的集合类型,第三部分:WeakMap

代理

代理(Proxy)API允许你创建一个属性值在运行期间动态计算的对象.还可以利用代理API"钩入"其他的对象,实现例如打印记录和赋值审核的功能.

  1. var obj = {foo: "bar"};  

  2. var proxyObj = Proxy.create({  

  3.   get: function(obj, propertyName) {  

  4.     return 'Hey, '+ propertyName;  

  5.   }  

  6. });  

  7. console.log(proxyObj.Alex); // "Hey, Alex"  

可以看看Nicholas Zakas的这篇文章这个例子.

实现代理API的浏览器: Firefox 18+, Chrome 24+

译者注:作者不知道的是,一共有过两个代理API的提案,一个是旧的Catch-all Proxies,一个是新的直接代理(Direct Proxies).前者已被废弃.两者的区别在这里.V8(Chrome和Node.js)实现的是前者,Firefox18及之后版本实现的是后者(17及之前版本实现的是前者).尼古拉斯在2011年写的文章也应该是过时的.

一些新的API

Object.is

Object.is是一个用来比较两个值是否相等的函数.该函数和===最主要的区别是在对待特殊值NaN与自身以及正零与负零之间的比较上.Object.is的判断结果是:NaN与另外一个NaN是相等的,以及+0和-0是不等的.

  1. Object.is(0, -0); // false  

  2. Object.is(NaN, NaN); // true  

  3. 0 === -0; // true  

  4. NaN === NaN; // false  

实现了Object.is的浏览器: Chrome 24+

兼容目前引擎的Object.is实现

译者注:Object.is方法和严格相等===运算符的区别体现在ES标准内部就是SameValue算法严格相等比较算法的区别.

译者注:如果我没有理解错这条BE的推特的话,Object.is要改名成为Object.sameValue了.

Array.from

Array.from: 将参数中的类数组(array-like)对象(比如arguments, NodeList, DOMTokenList (classList属性就是这个类型), NamedNodeMap (attributes属性就是这个类型))转换成数组并返回,比如转换一个纯对象:

  1. Array.from({  

  2.     0: 'Buy some milk',  

  3.     1: 'Go running',  

  4.     2: 'Pick up birthday gifts',  

  5.     length: 3  

  6. });  

再比如转换一个DOM节点集合:

  1. var divs = document.querySelectorAll('div');  

  2. Array.from(divs);  

  3. // [<div class="some classes" data-info="12"></div>, <div data-info="10"></div>]  

  4. Array.from(divs).forEach(function(node) {  

  5.     console.log(node);  

  6. });  

兼容目前引擎的Array.from实现

译者注:从作者举的两个例子可以看出,Array.from基本相当于目前使用的[].prototype.slice.call.

目前的草案也的确是这样规定的,但从Rick Waldron(TC39成员)在原文评论中给出的代码可以看出,也许Array.from未来也能将Set对象(非类数组对象,但可迭代)转换成数组.

译者注:除了这两个API,还有很多个新添加的API,比如

Number.isFinite, isNaN, isInteger, toInteger

String.prototype.repeat, startsWith, endsWith, contains, toArray

下面给出两个很有用的链接:

Mozilla正计划实现的ES6特性https://wiki.mozilla.org/ES6_plans.

ES6目前的所有特性提案http://wiki.ecmascript.org/doku.php?id=harmony:proposals

总结

ES.next中添加了许多被认为是JavaScript中缺失已久的新特性.虽然ES6规范计划在2013年年底发布,不过浏览器们已经开始实现其中的一些特性了,这些特性被广泛使用也只是时间问题了.

在ES6完全实现之前,我们可以使用一些转换编译器(transpiler)或者shim来体验其中一些特性.

译者注:目前最强大的ES6实现应该是Brandon Benvie写的continuum, 这是一个JavaScript虚拟机,也就是用JavaScript(ES3)实现的JavaScript(ES6)引擎,它未来甚至可以工作在IE6 上.目前实现的ES6特性有:模块以及模块加载器API,直接代理,生成器,解构,@symbols(我翻译成标志,这是一个不可能通过shim方式实现 的语法)等等.

想要查看更多的例子和了解最新的信息,可以去TC39 Codex Wiki,该站点由Dave Herman和其他一些EC39成员维护(译者注:该新站仍在建设中,应该访问旧站).其中包含了下一代JavaScript中将要有的所有新特性.

激动人心时刻马上就要到来了!

译者注:文本中提到的知识点仅仅是ES6中新知识的一小部分,而且明显作者自己也有点赶不上草案的快速变化(本文中提到的所有知识点都有可能在明天就发生变化).所以本文的内容仅仅是个开始,原文中的外部连接加上我给出的外部链接才是最需要你关注的.


本文转载自:

上一篇: CSS简写指南
下一篇: ES6新特性概览
sheilacat
粉丝 35
博文 75
码字总数 35249
作品 0
长沙
程序员
私信 提问
【译】用一天入门 canvas 和 JavaScript

原文地址:Spend your Sunday (or any day) with the canvas element and JavaScript 原文作者:Ashish Nandan Singh 译者:liaozeen 在最近的 JavaScript 30天挑战中,我有机会了解 HTML 内......

liaozeen
03/01
0
0
(译) 函数式 JS #1:简介

“written equations on brown wooden board” byRoman Mager onUnsplash 原文链接 By: Krzysztof Czernek 简介 如果你是一名 JavaScript 开发人员,那么你很可能会 已经看到过"函数式编程"......

RockyDd
01/11
0
0
【探秘ES6】系列专栏(八):JS的第七种基本类型Symbols

ES6作为新一代JavaScript标准,已正式与广大前端开发者见面。为了让大家对ES6的诸多新特性有更深入的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获授权,将持续对该...

一配
2015/11/08
0
0
[译]ES6提示和技巧,使您的代码更清晰,更短,更容易阅读

原文地址:ES6 tips and tricks to make your code cleaner, shorter, and easier to read! 模板字面量(Template literals) 模板字面量比以前更容易处理字符串。以'开头,并且可以使用插入变量...

sunshine杨小咩
2018/09/29
0
0
【译】如何从头开始搭建React,Webpack4,Babel7工程

这是一篇非常适合新手的教程。 目录: 你将会学习到的知识 建立项目 配置webpack 配置Babel 编写React组件 HTML webpack plugin webpack dev server 总结 你将会学习到的知识 如何安装及配置...

EniviaQ
02/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何使用 rsync 备份 Linux 系统的一些介绍

备份一直是 Linux 世界的热门话题。回到 2017,David Both 为 Opensource.com 的读者在使用 rsync 备份 Linux 系统方面提了一些建议,在这年的更早时候,他发起了一项问卷调查询问大家,在 ...

Linux就该这么学
39分钟前
2
0
以太坊私有链搭建

https://blog.csdn.net/Blockchain_lemon/article/details/80589123

Moks角木
今天
3
0
自律给我自信-为什么要自律

为什么要自律 混一天和努力一天 看不到任何差别 3天看不到任何变化 7天也看不到任何效果 但是 1个月后, 会看到话题不同 3个月后, 会看到气场不同 6个月后, 会看到距离不同 3年后, 会看到...

周大壮
今天
4
0
读书replay计划说明

突然脑袋一闪,我有了这样一个主意:通过写博客的方式,将我阅读的书中的内容replay出来。 我一般会找着我感兴趣的书去读,一般也会读书中我感兴趣的章节,或者当下对我有用的章节,所以这个...

wanxiangming
今天
1
0
CentOS7安装xrdp环境可实现远程桌面访问

CentOS7安装xrdp环境可实现远程桌面访问 2018-07-14 06:39:28 分类:运维 阅读(2051) 评论(0) 在"Ubuntu系统安装xrdp桌面客户端及实现远程连接桌面"文章中有分享过在Ubuntu系统中安装XRDP环境...

linjin200
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部