ES6常用特性

ES6(ECMAScript2015)的出现,无疑给前端开发人员带来了新的惊喜,它包含了一些很棒的新特性,可以更加方便的实现很多复杂的操作,提高开发人员的效率。

Default Parameters(默认参数)

还记得我们以前不得不通过下面方式来定义默认参数:

//ES5
var link = function (height, color, url) {
    var height = height || 50;
    var color = color || 'red';
    var url = url || 'http://azat.co';
}

一切工作都是正常的,直到参数值是0后,就有问题了,因为在JavaScript中,0表示false。当然,如果你非要用0作为值,我们可以忽略这一缺陷并且使用逻辑OR就行了!但在ES6,我们可以直接把默认值放在函数申明里:

//ES6
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
  //do somethings
}

Template Literals(模板对象)

在其它语言中,使用模板和插入值是在字符串里面输出变量的一种方式。因此,在ES5,我们可以这样组合一个字符串:

//ES5
var name = 'Your name is ' + first + ' ' + last + '.';
var url = 'http://localhost:3000/api/messages/' + id;

幸运的是,在ES6中,我们可以使用新的语法$ {NAME},并把它放在反引号里:

//ES6
var name = `Your name is ${first} ${last}. `;
var url = `http://localhost:3000/api/messages/${id}`;

Multi-line Strings (多行字符串)

在ES5中,我们不得不使用以下方法来表示多行字符串:

//ES5
var roadPoem = 'Then took the other, as just as fair,nt'
    + 'And having perhaps the better claimnt'
    + 'Because it was grassy and wanted wear,nt'
    + 'Though as for that the passing therent'
    + 'Had worn them really about the same,nt';

然而在ES6中,仅仅用反引号就可以解决了:

//ES6
var roadPoem = `Then took the other, as just as fair,
    And having perhaps the better claim
    Because it was grassy and wanted wear,
    Though as for that the passing there
    Had worn them really about the same,`;

Destructuring Assignment (解构赋值)

解构可能是一个比较难以掌握的概念。先从一个简单的赋值讲起,其中house 和 mouse是key,同时house 和mouse也是一个变量,在ES5中是这样:

//ES5
var data = $('body').data(), // data has properties house and mouse
   house = data.house,
   mouse = data.mouse;

在ES6,我们可以使用这些语句代替上面的ES5代码:

//ES6
var { house, mouse} = $('body').data();

在数组中是这样的:

//ES6
var [col1,col2] = $('.column'),
    [line1,line2,line3, ,line5] = file.split('n');

提示

使用{}省去了写对象的属性的步骤,当然这个{}中的变量是与对象的属性名字保持一致的情况下。

Enhanced Object Literals (增强的对象字面量)

使用对象文本可以做许多让人意想不到的事情!通过ES6,我们可以把ES5中的对象变得更加接近于一个类。

下面是一个典型ES5对象文本,里面有一些方法和属性:

//ES5
var serviceBase = {port: 3000, url: 'azat.co'},  
    getAccounts = function(){return [1,2,3]}; 
var accountServiceES5 = {  
  port: serviceBase.port,    url: serviceBase.url,    getAccounts: getAccounts,  
  toString: function() {  
      return JSON.stringify(this.valueOf());  
  },  
  getUrl: function() {return "http://" + this.url + ':' + this.port},  valueOf_1_2_3: getAccounts()
}  

如果我们想让它更有意思,我们可以用Object.createserviceBase继承原型的方法:

//ES6
var serviceBase = {port: 3000, url: 'azat.co'},  
    getAccounts = function(){return [1,2,3]};

var accountServiceES6 = Object.create(serviceBase);//继承var accountServiceES6 = {
  getAccounts: getAccounts,
  toString: function() {
    return JSON.stringify(this.valueOf());
  },
  getUrl: function() {return "http://" + this.url + ':' + this.port},  valueOf_1_2_3: getAccounts()
}

Object.create的实现方式:

Object.create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};

Arrow Functions in(箭头函数)

以前我们使用闭包,this总是预期之外地产生改变,而箭头函数的迷人之处在于,现在你的this可以按照你的预期使用了,身处箭头函数里面,this还是原来的this

有了箭头函数在ES6中, 我们就不必用that = thisself = this_this = this.bind(this)。例如,下面的代码用ES5就不是很优雅:

//ES5
var _this = this;
$('.btn').click(function(event){
  _this.sendData();
})

在ES6中就不需要用 _this = this:

//ES6
$('.btn').click((event) =>{
  this.sendData();
})

不幸的是,ES6委员会决定,以前的function的传递方式也是一个很好的方案,所以它们仍然保留了以前的功能。

下面这是一个另外的例子,我们通过call传递文本给logUpperCase() 函数在ES5中:

//ES5
var logUpperCase = function() {
  var _this = this;
 
  this.string = this.string.toUpperCase();
  return function () {
    return console.log(_this.string);
  }
}

logUpperCase.call({ string: 'ES6 rocks' })();

而在ES6,我们并不需要用_this浪费时间:

//ES6
var logUpperCase = function() {
  this.string = this.string.toUpperCase();
  return () => console.log(this.string);
}
logUpperCase.call({ string: 'ES6 rocks' })();

请注意,只要你愿意,在ES6中=>可以混合和匹配老的函数一起使用。当在一行代码中用了箭头函数,它就变成了一个表达式。它将暗地里返回单个语句的结果。如果你超过了一行,将需要明确使用return

这是用ES5代码创建一个消息数组:

//ES5
var ids = ['aaa','bbb'];
var messages = ids.map(function (value) {
  return "ID is " + value; // explicit return
});

用ES6是这样:

//ES6
var ids = ['aaa','bbb'];
var messages = ids.map(value => `ID is ${value}`); // implicit return

请注意,这里用了字符串模板。

在箭头函数中,对于单个参数,括号()是可选的,但当你超过一个参数的时候你就需要他们。

在ES5代码有明确的返回功能:

//ES5
var ids = ['aaa', 'bbb'];
var messages = ids.map(function (value, index, list) {
  return 'ID of ' + index + ' element is ' + value + ' '; // explicit return
});

在ES6中有更加严谨的版本,参数需要被包含在括号里并且它是隐式的返回:

JavaScript

var ids = ['aaa','bbb'];
var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `); // implicit return
1
2
var ids = ['aaa','bbb'];
var messages = ids.map((value, index, list) => `ID of ${index} element is ${value} `); // implicit return

Promises

下面是一个简单的用setTimeout()实现的异步延迟加载函数:

//ES5
setTimeout(function(){
  console.log('Yay!');
}, 1000);

在ES6中,我们可以用promise重写:

//ES6
var wait1000 =  new Promise(function(resolve, reject) {
  setTimeout(resolve, 1000);
}).then(function() {
  console.log('Yay!');
});

或者用ES6的箭头函数:

var wait1000 =  new Promise((resolve, reject)=> {
  setTimeout(resolve, 1000);
}).then(()=> {
  console.log('Yay!');
});

到目前为止,代码的行数从三行增加到五行,并没有任何明显的好处。确实,如果我们有更多的嵌套逻辑在setTimeout()回调函数中,我们将发现更多好处:

//ES5
setTimeout(function(){
  console.log('Yay!');
  setTimeout(function(){
    console.log('Wheeyee!');
  }, 1000)
}, 1000);

在ES6中我们可以用promises重写:

//ES6
var wait1000  =  () => new Promise((resolve, reject)=> {
    setTimeout(resolve, 1000);
});

wait1000().then(function() {
    console.log('Yay!');
    return wait1000();
}).then(function() {
    console.log('Wheeyee!');
});

Block-Scoped Constructs Let and Const(块作用域和构造let和const)

let是一种新的变量声明方式,它允许你把变量作用域控制在块级里面。我们用大括号定义代码块,在ES5中,块级作用域起不了任何作用:

//ES5
function calculateTotalAmount (vip) {  
  var amount = 0;  
  if (vip) {  
    var amount = 1;  
  }  
  { // more crazy blocks!  
    var amount = 100;  
    {  
      var amount = 1000;  
    }  
  }    
  return amount;  
}  
console.log(calculateTotalAmount(true));  // 1000

ES6: 用let限制块级作用域

//ES6
function calculateTotalAmount(vip){
    var amouont  = 0; // probably should also be let, but you can mix var and let
    if (vip) {  
        let amount = 1; // first amount is still 0  
    }   
    { // more crazy blocks!  
        let amount = 100; // first amount is still 0  
        {  
            let amount = 1000; // first amount is still 0  
        }  
    }    
    return amount;  
} 
console.log(calculateTotalAmount(true));  //0 因为块作用域中有了let。

谈到const,就更加容易了,它就是一个不变量,也是块级作用域就像let一样。

提示

我们用let限制块级作用域。而var是限制函数作用域。

Classes(类)

ES6没有用函数,而是使用原型实现类。我们创建一个类baseModel ,并且在这个类里定义了一个constructor 和一个 getName()方法:

class baseModel {
    // class constructor,注意我们对options 和data使用了默认参数值。
    constructor(options, data) {
        this.name = 'Base';  
        this.url = 'http://azat.co/api';  
        this.data = data;  
        this.options = options;  
    }

    getName() { // class method  
        console.log(`Class name: ${this.name}`);
    }

    getUrl() { // class method  
         console.log(`Url: ${this.url}`);  
    }
} 

AccountModel 从类baseModel中继承而来:

class AccountModel extends baseModel {
    constructor(options, data) {
        super({private: true}, ['32', '5242']);
        this.url +='/accounts/';
    }
    get accountsData() {
        return this.data;
    }
}

// 调用
let accounts = new AccountModel(5);
accounts.getName();  // Class name:Base
console.log('Data is %s', accounts.accountsData); 
// Data is 32,5242 

//子类必须在constructor方法中调用super方法,否则新建实例时会报错。
//这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。
//如果不调用super方法,子类就得不到this对象。
  • 此处的继承中,子类必须在constructor方法中调用super方法,否则新建实例时会报错。

  • 此处的super方法继承了父类的所有方法,包括不在父类的constructor中的其他方法,当然可以改写父类方法,比如上述例子,继承了getName()getUrl()方法,以及constructor()中的this.name等属性,同时改写了this.url,增加了accountsData,且新增的方法前边要加上get

  • 子类调用super方法可以传入参数,对应constructor()函数的形参。

Modules (模块)

ES5导出

//ES5
module.exports = { 
    port: 3000,
    getAccounts: function() {
        //do somethings
    }
}

ES6导出

//ES6
export var port = 3000;
export function getAccounts(url) {
    //do somethings
}

ES5导入

//ES5
var service = require('module.js');
console.log(service.port); // 3000

ES6导入

我们需用import {name} from 'my-module'语法

//ES6
import {port, getAccounts} from 'module';
console.log(port); // 300

或者ES6整体导入

//ES6
import * as service from 'module';
console.log(service.port); // 3000