ES6/ES2015 核心内容(最常用的 ES6 特性)

ECMAScript 6(以下简称 ES6)是 JavaScript 语言的下一代标准。因为当前版本的 ES6 是在 2015 年发布的,所以又称 ECMAScript 2015。也就是说,ES6 就是 ES2015。虽然目前并不是所有浏览器都能兼容 ES6 全部特性,但越来越多的程序员在实际项目当中已经开始使用 ES6 了。所以就算你现在不打算使用 ES6,但为了看懂别人的你也该懂点ES6的语法了…

let, const, class, extends, super, arrow functions, template string, default,destructuring, rest arguments基本上这些就是ES6最经常用到的语法了。

let , const(用作声明变量)

与var相同,let和const同用作声明变量,但是他们俩实际是有特殊而且重要的用途。

关于let

1
2
3
4
5
6
7
var global = "global variables";
while(true){
var global = "local variables";
console.log(global);
break;
}
console.log(global);

这两次log出来的结果都是:local variables,因为在ES5中作用域只分为全局作用域和函数作用域,并没有其他语言中的块级作用域,这会有很多不合理的地方,在这个地方就表现为内层变量覆盖了外层变量。也就是两次说两次都是全局作用域中的变量,第一次覆盖了第二次global的声明。

let可以很好的解决这一问题:let则实际上为 JavaScript 新增了块级作用域。用它所声明的变量,只在let命令所在的代码块内有效。例如:

1
2
3
4
5
6
7
var global = "global variables";
while(true){
let global = "local variables";
console.log(global);
break;
}
console.log(global);

此时结果是:local variables global variables,但我试了下,我现在的火狐和360都是不支持的。

还有一个var带来的经常会错的地方,这是我经常会弄错的地方,就是用来计数的循环变量泄露为全局变量,下面这个例子应该很经典了:

1
2
3
4
5
6
7
var arr = [];
for(var i=0;i<5;i++){
arr[i] = function(){
console.log(i);
}
}
arr[3]();

最后不管调用哪一个函数结果都是5,这是由于变量var i是在全局范围内声明的,是全局变量,所以每次循环一次,i都会增加,并且覆盖先前的值。将上面的var改为let边不会出现这个问题。在ES5中应该有两种写法可以避免上面出现的问题:闭包。

1
2
3
4
5
6
7
8
9
var arr = [];
for(var i=0;i<5;i++){
(function(i){
arr[i] = function(){
console.log(i);
}
})(i);
}
arr[3]();

结果为3,还有一种写法就是拆开,把arr[i]对应的函数放在外面。

关于const

这里的const就跟我们平常别的语言一样了,用于声明常量如PI,声明后不可更改,更改后浏览器会报错用,const 来声明可以避免未来不小心重命名而导致出现 bug。

1
2
3
const PI = Math.PI
PI = 23 //Module build failed: SyntaxError: /es6/app.js: "PI" is read-only
const monent = require('moment')

class , extends , super

这三个特性涉及了 ES5 中最令人头疼的的几个部分:原型、构造函数,继承…你还在为它们复杂难懂的语法而烦恼吗?你还在为指针到底指向哪里而纠结万分吗?

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念。新的 class 写法让对象原型的写法更加清晰、更像面向对象编程的语法,也更加通俗易懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
constructor () {
this.type = "animal";
}
says (say) {
console.log(this.type + "says" + say);
}
}
class Cat extends Animal{
constructor () {
super();
this.type = "cat";
}
}
let animal = new Animal ();
animal.says("hello");
let cat = new Cat();
cat.says("hi");

这个例子中我们首先定义了一个Animal类,有一个 constructor构造方法和一个函数says。而这里面的this则代表的是实例对象本身。也就是说,constructor里面的属性和方法是每个被实例化的对象自己的,而constructor外的是所有对象共享的。

ES6中,这里可以通过class和extends来实现继承,比ES5中通过修改原型链来继承要方便和清晰的多,跟我们以前学习的比如PHP,C++都要类似,更容易接受。

super应该也陌生,它代表的是父类的实例(父类的this对象),子类必须在constructor中通过调用super,如果不调用,新建实例时会报错。因为子类并没有自己的this对象,而是继承了父类的this对象,然后进行加工和修改形成子类自己的特性。如果不调用,子类也就自然没有自己的this对象了。

ES6 的继承机制,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this。

template string

这个东西也是非常有用,当我们要插入大段的 html 内容到文档中时,传统的写法非常麻烦,所以之前我们通常会引用一些模板工具库,比如 mustache 等等。例如jquery里面当我们需要插入一段dom节点时:

1
2
3
4
5
6
$("#result").append(
"There are <b>" + basket.count + "</b> " +
"items in your basket, " +
"<em>" + basket.onSale +
"</em> are on sale!"
);

这需要对双引号和+都用的恰当才可。下面通过ES6的template string改进,用反引号(\)来标识起始,用${}`来引用变量,而且所有的空格和缩进都会被保留在输出之中,这个可以有对吧。

1
2
3
4
5
$("#result").append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);

destructuring(解构)

ES6中允许按照一定的模式,从对象和数组中提取值,对变量进行赋值,这称为解构。

1
2
3
4
let cat = 'ken'
let dog = 'lili'
let zoo = {cat: cat, dog: dog}
console.log(zoo) //Object {cat: "ken", dog: "lili"}

ES6中更改为:

1
2
3
4
let cat = 'ken'
let dog = 'lili'
let zoo = {cat, dog}
console.log(zoo) //Object {cat: "ken", dog: "lili"}

也可以反过来解析:

1
2
3
let dog = {type: 'animal', many: 2}
let { type, many} = dog
console.log(type, many) //animal 2

default , rest

default 很简单,意思就是默认值。大家可以看下面的例子,调用animal()方法时忘了传参数,传统的做法就是加上这一句type = type || ‘cat’来指定默认值。

1
2
3
4
5
function animal(type){
type = type || 'cat'
console.log(type)
}
animal()

如果用 ES6 我们而已直接这么写(跟c++差不多了):

1
2
3
4
function animal(type = 'cat'){
console.log(type)
}
animal()

最后一个 rest 语法也很简单,直接看例子:(而如果不用 ES6 的话,我们则得使用 ES5 的arguments)

1
2
3
4
function animals(...types){
console.log(types)
}
animals('cat', 'dog', 'fish') //["cat", "dog", "fish"]

arrow function

我看的这篇文章说:这个恐怕是 ES6 最最常用的一个新特性了,用它来写 function 比原来的写法要简洁清晰很多。我现在还没有真正的体会到。
直接上例子,比如原来这么写的可以像现在这样写:

1
2
function(i){ return i + 1; } //ES5
(i) => i + 1 //ES6

如果方程比较复杂,则需要用{}把代码包起来:

1
2
3
4
5
6
function(x, y) { 
x++;
y--;
return x + y;
}
(x, y) => {x++; y--; return x+y}

除了看上去更简洁以外,arrow function 还有一项超级无敌的功能!
长期以来,JavaScript 语言的this对象一直是一个令人头痛的问题,在对象方法中使用 this,必须非常小心。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout(function(){
console.log(this.type + ' says ' + say)
}, 1000)
}
}

var animal = new Animal()
animal.says('hi') //undefined says hi

运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。所以为了让它能够正确的运行,传统的解决方法有两种:

第一种是将 this 传给 self,再用 self 来指代 this

1
2
3
4
5
says(say){
var self = this;
setTimeout(function(){
console.log(self.type + ' says ' + say)
}, 1000)

第二种方法是用bind(this),即

1
2
3
4
says(say){
setTimeout(function(){
console.log(self.type + ' says ' + say)
}.bind(this), 1000)

但现在我们有了箭头函数,就不需要这么麻烦了:
当我们使用箭头函数时,函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,它的 this 是继承外面的,因此内部的 this 就是外层代码块的 this。

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout( () => {
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says(‘hi’) //animal says hi

完结:学习整理自SegmentFault,太感谢啦。看完以后对ES6有了一点新的认识,最起码看别人写ES6编码时能看懂一点。也达到了我的初衷。但是看的过程中也暴露了自己的一个问题,那就是对this不了解。应该好好去看看啦。回头再写一篇文章整理下。