大家好,我不是谷阿莫,我不能用十分钟就能让大家精通ECMAScript 2015
,但我希望大家读完这篇文章后有能够再花多点点时间去探索ECMAScript 2015
。那么问题来了,ECMAScript 2015
会是个什么样的东东?如果说现在使用的Javascript是Pyhton2的话,那么ECMAScript 2015
就是全新的Python3了。我们需要也应该花一定的时间和精力来学习ECMAScript 2015
并考虑将来如何将它的新特性应用到我们的产品中。言归正传,我们一起来看看ECMAScript 2015
有哪些特性。
在上一个版本发布了四年之后,最新版本ECMAScript终于在2015年6月17日由ECMA国际会议上批准。在这个会议中,最新版本的ECMAScript并没有被命名为大家之前所认为的ECMAScript 6th Edition,而使用了ECMAScript 2015代替,而ECMAScript 2016也在制定中(看上去是一年出一个标准的节奏呀)。为了兼顾之前的叫法,接下来我还是用ES6
来称呼它。
ES6
和之前ES5
相比,对Javascript语法进行了大量创新和优化。ES6
提供了关于classes
,modules
,promises
,generators
,maps
,sets
,tuples
,pattern matching
,以及traits
的支持。虽然ES6
是向下兼容的,但如果现在就直接使用这些新特性,是无法在绝大多数浏览器以及服务器端环境下运行的。幸运的是,我们可以通过一些工具将基于ES6
转换成现在主流浏览器和服务器端环境能够运行的代码,现在我会具体介绍。
由于ES6
标准才制定出来,主流浏览器包括Chrome,Firefox等都没有做到完全的支持。我们可以通过ES6适配表来查看最完整的包括浏览器,服务器端,移动端等各个端对ES6
的支持率。
我们可以通过以下几种方式来运行ES6
代码:
Babel是十分著名的ES6
转ES5
产品,我们可以通过在线方式将代码转换成现在普通浏览器能够执行的ES5代码。
它的访问地址是:http://babeljs.io/repl/,可能需要翻墙。
node.js 0.12之后的版本能够通过 --harmony 命令行来运行ES6
代码。
joshua:markdown dongua$ more es6.js
"use strict";
/*********let作用域*********/
(function () {
{
let netease = "163.com";
//163.com
console.log(netease);
}
})();
joshua:markdown dongua$ node --harmony es6.js
163.com
我们也可以通过npm下载babel-node模块来运行ES6
代码。io.js最新版本不需要加--harmony参数就能够运行ES6
代码,如果有io.js环境的同学可以试试。
介绍完运行ES6
的环境,现在我们就来简单的了解一下ES6
的新特性,大家也可以通过以上介绍的合适的运行环境来运行一下下面的代码片段,或者自己可以写一些玩一下。
下面介绍的语法新特性是ES6
特有的,ES6
的语法新特性比之前任何一版的语法新特性都要多得多,可以这么说,学习ES6
可以说是重新学习了一门语言。如果我们要掌握ES6
,我们一定需要对ES6
新增语法有更深的认识。
我在此介绍一些基础的新特性,还有很多高级特性以后再和大家一起分享。
ES6
新增了两种定义变量的方式,分别是let
和const
。
ES6
建议使用let
取代var
。它的用法类似于var
,但是所声明的变量,只在let
所在的代码块内有效。我们来看一段示例代码:
{
let me = "163.com";
//163.com
console.log(netease);
}
//ReferenceError: netease is not defined
console.log(netease);
我们可以看到,let
在{}代码块中定义,因此代码块外调用netease
变量就会报引用错误。另外需要注意let
不允许在相同作用域内,重复声明同一个变量。
let
实际上为JavaScript新增了块级作用域。块级作用域的出现,使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。
和let
类似,const
作用域也在代码块中。但它不是代表定义常量,而是定义常量索引。常量索引顾名思义就是指向这个值的索引一旦定义就不能够改变。换句话说,索引在内存中的指针不能够改变,但是如果指向的值是引用类型,那么依然可以对它进行操作。我们来看一段代码:
const domains = [];
domains.push("163.com");
domains.push("126.com");
domains.push("188.com");
//3
console.log(domains.length);
如果我们在定义了domains
后再重新给它赋值,那么会报解析错误:
const domains = [];
//SyntaxError: Assignment to constant variable.
domains = [];
如果你定义一个指向string,number,boolean,undefined
或者null
的const
,变量一旦定义后就无法再做任何更改,从另外一个角度上来说,它也成为了常量。
ES6
允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。解构分配允许我们有像变量声明表达式描述一样在左侧定义变量,在右侧分配值。听起来有点混乱?让我们看看具体的例子。
传统方式定义:
var domains = ["163.com", "126.com", "188.com"];
var domain163,domain126,domain188;
domain163 = domains[0];
domain126 = domains[1];
domain188 = domains[2];
解构方式定义:
let domains = ["163.com", "126.com", "188.com"];
let [domain163,domain126,domain188] = domains;
右侧赋值可以不是一个变量,因此可以简化为:
let [domain163,domain126,domain188] = ["163.com", "126.com", "188.com"];
这样定义是否是十分简单?
数组的解构赋值还支持嵌套,例如:
let [one, [[two], three]] = [1, [[2], 3]];
//1, 2, 3
console.log(one, two, three);
我们还可以利用这个功能实现之前一句话无法实现的代码:
let main = "163.com", backup = "126.com";
//一句话交换变量值
[main, backup] = [backup, main];
解构不仅可以用于数组,还可以用于对象。我们看如何实现:
let {domain163,domain126,domain188} = {"domain163":"163.com","domain126":"126.com","domain188":"188.com"}
//163.com, 126.com, 188.com
console.log(domain163, domain126, domain188);
如果左侧的变量名和右侧对象属性名不能对应,可以这样使用:
let {"163": main} = {"163": "163.com"};
//163.com
console.log(main);
和数组一样,解构也可以用于嵌套结构的对象。
let info = {
url : [
"163.com",
{product: "mail"}
]
};
let {url : [domain, { product }]} = info;
//163.com, mail
console.log(domain, product);
对象的解构也可以指定默认值。
let {domain = "163.com"} = {};
//163.com
console.log(163.com);
在处理对象中,我们可以这样定义方法:
let info = {
//直接定义方法,而无需使用 domain : function(){}方式
domain() {
console.log("163.com");
}
};
//163.com
info.domain();
let info = {
_domain : "163.com",
get domain() {
return this._domain;
},
set domain(domain){
this._domain = domain;
}
};
//163.com
console.log(info.domain);
info.domain = "126.com";
//126.com
console.log(info.domain);
我们可以通过Getter和Setter来处理设置对象属性和获得对象属性时的逻辑。
我们可以把变量作为属性参数传递到一个定义中:
const sub = 'mail';
const config = {
[`${sub}domain`]: "mail.163.com"
};
//mail.163.com
console.log(config.maildomain);
下面是另外一个例子:
var index = 0;
var obj = {
[`key${index}`]: index++,
[`key${index}`]: index++,
[`key${index}`]: index++,
};
//0,1,2
console.log(obj.key0, obj.key1, obj.key2);
注意,这里属性名使用的是 反引号`
,而不是使用的引号'
或"
。
let info = {
//get:get
get
};
function get(){
return "163.com";
}
//163.com
console.log(info.get());
可以看到,在上下文中有定义了get这个函数,因此在info这个对象定义中,我们就无需再重复写get:get
,而直接使用get
定义就好了。
总所周知,for...in是循环获得键名,而for...of用来获取循环的键值。
let domains = ["163.com", "!26.com", "188.com"]
//分别输出163.com,126.com,188.com
for(let domain of domains){
console.log(domain);
}
不是所有对象都支持for...of,对象需要在支持了Iterator之后才能够使用for...of。
遍历器(Iterator)是一种接口规格,任何对象只要部署这个接口,就可以完成遍历操作。它的作用有两个,一是为各种数据结构,提供一个统一的、简便的接口,二是使得对象的属性能够按某种次序排列。在ES6
中,遍历操作特指for...of循环,即Iterator接口主要供for...of循环使用。 遍历器提供了一个指针,指向当前对象的某个属性,使用next方法,就可以将指针移动到下一个属性。next方法返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束。
Iterator接口返回的遍历器,原生具备next方法,不用自己部署。所以,真正需要部署的是Iterator接口,让其返回一个遍历器。在ES6
中,有三类数据结构原生具备Iterator接口:数组、类似数组的对象、Set和Map结构。除此之外,其他数据结构(主要是对象)的Iterator接口都需要自己部署。
下面就是如何部署Iterator接口。一个对象如果要有Iterator接口,必须部署一个@@iterator方法(原型链上的对象具有该方法也可),该方法部署在一个键名为Symbol.iterator的属性上,对应的键值是一个函数,该函数返回一个遍历器对象。
class MySpecialTree {
// ...
[Symbol.iterator]() {
// ...
return theIterator;
}
}
上面代码是一个类部署Iterator接口的写法。Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内。这里要注意,@@iterator的键名是Symbol.iterator,键值是一个方法(函数),该方法执行后,返回一个当前对象的遍历器。
下面是为对象添加Iterator接口的例子:
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
ES6
提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。
let s = new Set();
[1,1,2,3,5,4,5,2,2].map(x => s.add(x))
//1 2 3 4 5
for (let i of s) {
console.log(i)
}
Set可以接受一个数组作为参数,用来初始化:
var items = new Set([1,2,3,4,5,5,5,5]);
//5
console.log(items.size);
向Set加入值的时候,不会发生类型转换,5和“5”是两个不同的值。Set内部判断两个值是否精确相等,使用的是精确相等运算符(===)。这意味着,两个对象总是不相等的。
let set = new Set();
set.add({})
set.size // 1
set.add({})
set.size // 2
上面代码表示,由于两个空对象不是精确相等,所以它们被视为两个值。
Set提供三个遍历方法:
Set结构的默认遍历器就是它的values方法。这意味着,可以省略values方法,在for...of循环中直接使用Set。为了与Map结构保持一致,Set结构也有keys和entries方法,这时每个值的键名就是键值。
JavaScript的对象,本质上是键值对的集合,但是只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6
提供了map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应。
let map = new Map();
let netease = {"domain":"163.com"};
map.set(netease,"NTES");
//NTES
console.log(map.get(netease));
Map数据结构有以下属性和方法:
Map原生提供三个遍历器:
Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
ES6
引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。一个简单的类定义是这样的:
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '('+this.x+', '+this.y+')';
}
}
var point = new Point(2,3);
point.toString() // (2, 3)
上面代码定义了一个“类”,可以看到里面有一个constructor函数,这就是构造函数,而this关键字则代表实例对象。这个类除了构造方法,还定义了一个toString方法。注意,定义方法的时候,前面不需要加上function这个保留字,直接把函数定义放进去了就可以了。
Class之间可以通过extends关键字,实现继承。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 等同于super.constructor(x, y)
this.color = color;
}
toString() {
return this.color+' '+super();
}
}
ES6
的模块标准由两部分组成:
声明式语法(导入和导出用)。 编程式API加载器:设置模块如何加载以及如何有条件地加载模块。 ES6
模块不再需要开发人员去将整个JavaScript文件的尴尬地包装成一个对象或函数闭报,这和以前大多数异步模块装载器在浏览器中的做法一样。相反,可以在最顶层进行定义,而只有函数和显式定义的导出变量将可以暴露给模块的消费者:
let privateVar = "this is a variable private to the module";
export let publicVar = "and this one is public";
export function returnPrivateVar() {
return privateVar;
};
假设将上面的代码保存在mymodule.js中,我们现在可以用两种方法导入,或者是通过导入指定函数和变量,或者通过导入模块作为模块的对象:
import { returnPrivateVar, publicVar } from 'mymodule';
console.log(returnPrivateVar());
或者是:
import 'mymodule' as mm;
console.log(mm.returnPrivateVar());
新的模块标准也支持对模块的内嵌定义和动态模块加载。
我们可以看到ES6
在设计上的诸多优点,例如模块化设计,定义类更加规范,在处理变量以及对象上更加灵活和严谨,这会对前端开发,甚至基于ES6
的后端前台开发带来革命性的改革。现在各个环境对ES6
的支持不是很完美,我们也只有在生产环境中逐步升级到ES6
,可以考虑后端前台先尝试通过Node.js,io.js来率先使用,等积累了一定的类库以及用户浏览器普及后,逐步在前端使用。现在业界流行的主流框架,包括AngularJS,Meteor,TypeScript以及Ember等都在尽可能多的支持ES6
的新特性,如果有产品在使用这些框架,也可以关注一下官方的更新日志以及API。
本文还有很多ES6
的特性没有覆盖到(如果覆盖到了也就不一篇十分钟就能够读完的文章了)。本文也是抛砖引玉,能够让大家快速熟悉ES6
常用语法新特性,并能够很快速的对它其他性能进行研究。
也希望之后能够有更多介绍ES6
新特性的优秀文章面世。
**下面是一些属性方法扩展,觉得累了的同学可以现在休息休息,之后在用到的时候参考手册。**
ES6
新增的Set和Map结构。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者董桦授权发布。