ECMAScript 6

1、Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行

$ npm install --save-dev gulp-babel

2、最常用的ES6特性

let, const, class, extends, super, arrow functions, template string, destructuring, default, rest arguments

3、新特性简介

let、const
可以把let看成var,只是它定义的变量被限定在了特定范围内才能使用,而离开这个范围则无效。
const则很直观,用来定义常量,即无法被更改值的变量。
//var带来的不合理场景就是用来计数的循环变量泄露为全局变量
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

箭头函数:
我们经常需要给回调函数维护一个词法作用域的上下文 this
function Person(name) {

    this.name = name;

}

Person.prototype.prefixName = function (arr) {

    return arr.map(function (character) {

        return this.name + character; // Cannot read property 'name' of undefined

    });

};

一个常用的解决办法是把 this 存在一个变量中: 

function Person(name) {

    this.name = name;

}

Person.prototype.prefixName = function (arr) {

    var that = this; // Store the context of this

    return arr.map(function (character) {

        return that.name + character;

    });

};

我们也可以传递一个合适的 this 上下文: 

function Person(name) {

    this.name = name;

}

Person.prototype.prefixName = function (arr) {

    return arr.map(function (character) {

        return this.name + character;

    }, this);

}

我们还可以绑定上下文:

function Person(name) {

    this.name = name;

}

Person.prototype.prefixName = function (arr) {

    return arr.map(function (character) {

        return this.name + character;

    }.bind(this));
};

使用 箭头函数,this 将不会受到影响,并且我们可以重写上面的函数: 

function Person(name) {

    this.name = name;

}

Person.prototype.prefixName = function (arr) {

    return arr.map(character => this.name + character);

};

在我们写一个函数的时候,箭头函数更加简洁并且可以很简单地返回一个值

var squares = arr.map(function (x) { return x * x }); // Function Expression

const arr = [1, 2, 3, 4, 5];

const squares = arr.map(x => x * x); // Arrow Function for terser implementation

 

类Class

在 ES6 之前,我们通过构造函数来创造一个类,并且通过原型来扩展属性:

function Person(name, age, gender) {

    this.name   = name;
    this.age      = age;
    this.gender = gender;
}

Person.prototype.incrementAge = function () {

    return this.age += 1;
};

然后可以这样继承类:

function Personal(name, age, gender, occupation, hobby) {

    Person.call(this, name, age, gender);
    this.occupation = occupation;
    this.hobby = hobby;
}

Personal.prototype = Object.create(Person.prototype);
Personal.prototype.constructor = Personal;
Personal.prototype.incrementAge = function () {
    Person.prototype.incrementAge.call(this);
    this.age += 20;
    console.log(this.age);
};

在 ES6 中,提供了更多的语法糖,可以直接创造一个类:

class Person {
    constructor(name, age, gender) {
        this.name   = name;
        this.age    = age;
        this.gender = gender;
    }

    incrementAge() {

      this.age += 1;
    }
}

使用 extends 关键字来继承一个类: 

class Personal extends Person {

    constructor(name, age, gender, occupation, hobby) {

        super(name, age, gender);
        this.occupation = occupation;
        this.hobby = hobby;
    }

    incrementAge() {

        super.incrementAge();
        this.age += 20;
        console.log(this.age);
    }
}

字符串

.includes( )

var string = 'food';
var substring = 'foo';

console.log(string.indexOf(substring) > -1);

之前我们使用 indexOf() 函数的返回值是否 >-1 来判断字符串是否包含某些字符串,现在我们更简单地使用 .includes() 来返回一个布尔值来判断: 

const string = 'food';
const substring = 'foo';

console.log(string.includes(substring)); // true

.repeat()

function repeat(string, count) {
    var strings = [];
    while(strings.length < count) {

        strings.push(string);
    }
    return strings.join('');
}

在 ES6 中,可以更简便地实现:

// String.repeat(numberOfRepetitions)

'meow'.repeat(3); // 'meowmeowmeow'

使用 模版字符串 我们就可以不用对某些特殊自负进行转义处理了: 

var text = "This string contains \"double quotes\" which are escaped.";

let text = `This string contains "double quotes" which don't need to be escaped anymore.`;

模版字符串 还支持插入,可以把变量值和字符串连接起来.

var name = 'Tiger';
var age = 13;

console.log('My cat is named ' + name + ' and is ' + age + ' years old.');

更简单:

const name = 'Tiger';
const age = 13;

console.log(`My cat is named ${name} and is ${age} years old.`);

模版字符串 还支持表达式:

let today = new Date();

let text = `The time and date is ${today.toLocaleString()}`;

结构对象

var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'

//es6
let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;

console.log(occupation); // 'jedi'
console.log(father); // 'anakin'

模块

使用commonJs输出

module.exports = 1;

module.exports = { foo: 'bar' };

module.exports = ['foo', 'bar'];

module.exports = function bar () {};

使用es6输出

在 ES6 中我们可以暴露多个值,使用 Exports: 

export let name = 'David';

export let age  = 25;

或者暴露一个对象列表:

function sumTwo(a, b) {

    return a + b;
}

function sumThree(a, b, c) {

    return a + b + c;
}

export { sumTwo, sumThree };

模块导入:

import {

    sumTwo as addTwoNumbers,

    sumThree as sumThreeNumbers

} from 'math/addition’;

另外,我们可以导入所有的东西(整体加载):

import * as util from 'math/addition';

最后,我们可以从一个模块中导入一个值的列表:

import * as additionUtil from 'math/addition';

const { sumTwo, sumThree } = additionUtil;

可以像这样导入默认绑定的输出:

import api from 'math/addition';

// Same as: import { default as api } from 'math/addition’;

虽然最好保持出口的简单,但如果需要的话我们有时可以混合默认的进口和混合进口。当我们这样出口的时候:

// foos.js

export { foo as default, foo1, foo2 };

我们可以这样导入它们:

import foo, { foo1, foo2 } from 'food’;

当我们用 commonjs 的语法导入一个模块的出口时(比如 React),我们可以这样做:

import React from 'react';

const { Component, PropTypes } = React;

还有更精简的写法:

import React, { Component, PropTypes } from 'react’;

 

默认参数:

function addTwoNumbers(x, y) {

    x = x || 0;
    y = y || 0;
    return x + y;
}

ES6 中,函数的参数可以支持设置默认值:

function addTwoNumbers(x=0, y=0) {

    return x + y;
}

rest 参数

在 ES5 中,我们需要这么处理不定参数:
 
function logArguments() {

    for (var i=0; i < arguments.length; i++) {

        console.log(arguments[i]);
    }
}

使用 rest ,我们就可以处理不确定数目的参数: 

function logArguments(...args) {

    for (let arg of args) {

        console.log(arg);
    }
}

 

命名参数
在 ES5 中是使用配置对象的模式来处理命名参数,jQuery 中的使用:
function initializeCanvas(options) {

    var height = options.height || 600;

    var width  = options.width  || 400;

    var lineStroke = options.lineStroke || 'black';

}

我们可以利用解构的一个函数的形参实现相同的功能

function initializeCanvas(

    { height=600, width=400, lineStroke='black'}) {

        // Use variables height, width, lineStroke here

    }

如果我们想使整个值可选择,我们可以结构将一个空的对象:

function initializeCanvas(

    { height=600, width=400, lineStroke='black'} = {}) {

        // ...

    }

展开操作

在 ES5 中,我们可以 apply Math.max 方法来获得一个数组中的最大值: 

Math.max.apply(null, [-1, 100, 9001, -32]); // 9001

在 ES6 中,我们可以通过展开操作把一个数组的值作为参数传递给一个函数

Math.max(...[-1, 100, 9001, -32]); // 9001

我们可以更简洁地使用这个语法来合并数组

let cities = ['San Francisco', 'Los Angeles'];

let places = ['Miami', ...cities, 'Chicago']; // ['Miami', 'San Francisco', 'Los Angeles', 'Chicago']
symbols是不可改变并且独一无二的,可以在任意哈希中作一个key,调用 Symbol() 或者 Symbol(description) 可以创造一个独一无二的符号,但是在全局是看不到的。Symbol() 的一个使用情况是给一个类或者命名空间打上补丁,但是可以确定的是你不会去更新它。比如,你想给 React.Component 类添加一个 refreshComponent 方法,但是可以确定的是你不会在之后更新这个方法:
const refreshComponent = Symbol();

React.Component.prototype[refreshComponent] = () => {

    // do something

}

Symbol.for(key)

Symbol.for(key) 同样会创造一个独一无二并且不可改变的 Symbol,但是它可以全局看到,两个相同的调用 Symbol.for(key) 会返回同一个 Symbol 类

Symbol('foo') === Symbol('foo') // false

Symbol.for('foo') === Symbol('foo') // false

Symbol.for('foo') === Symbol.for('foo') // true

map

Maps 在 JavaScript 中是一个非常必须的数据结构,在 ES6 之前,我们通过对象来实现哈希表

var map = new Object();

map[key1] = 'value1';

map[key2] = 'value2’;

实际上 Maps 允许我们对值进行 setget 和 search 操作:

let map = new Map();

> map.set('name', 'david');

> map.get('name'); // david

> map.has('name'); // true

Maps 更令人惊奇的部分就是它不仅限于使用字符串作为 key,还可以用其他任何类型的数据作为 key:

let map = new Map([

    ['name', 'david'],

    [true, 'false'],

    [1, 'one'],

    [{}, 'object'],

    [function () {}, 'function']
]);

for (let key of map.keys()) {

    console.log(typeof key);

    // > string, boolean, number, object, function
}

WeakMaps

在 ES6 之前,为了存储私有变量,我们有各种各样的方法去实现,其中一种方法就是用命名约定:

class Person {

    constructor(age) {

        this._age = age;
    }

    _incrementAge() {

        this._age += 1;
    }
}

但是命名约定在代码中仍然会令人混淆并且并不会真正的保持私有变量不被访问。现在,我们可以使用 WeakMaps 来存储变量: 

let _age = new WeakMap();

class Person {

    constructor(age) {

        _age.set(this, age);

    }

    incrementAge() {

        let age = _age.get(this) + 1;

        _age.set(this, age);

        if (age > 50) {

            console.log('Midlife crisis');
        }
    }
}

一个更实际的实践就是可以 WeakMaps 储存 DOM 元素,而不会污染元素本身:

let map = new WeakMap();

let el  = document.getElementById('someElement');

// Store a weak reference to the element with a key

map.set(el, 'reference');

// Access the value of the element

let value = map.get(el); // 'reference'

// Remove the reference

el.parentNode.removeChild(el);

el = null;

// map is empty, since the element is destroyed

Promises

Promises 可以让我们远离平行的代码:
func1(function (value1) {

    func2(value1, function (value2) {

        func3(value2, function (value3) {

            func4(value3, function (value4) {

                func5(value4, function (value5) {

                    // Do something with value 5
                });
            });
        });
    });
});

转变成垂直代码:

func1(value1)

    .then(func2)

    .then(func3)

    .then(func4)

    .then(func5, value5 => {

        // Do something with value 5

    });

Generators 生成器

就像 Promises 可以帮我们避免回调地狱,Generators 可以帮助我们让代码风格更整洁--用同步的代码风格来写异步代码,它本质上是一个可以暂停计算并且可以随后返回表达式的值的函数。Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。

function* sillyGenerator() {

    yield 1;

    yield 2;

    yield 3;

    yield 4;

}

var generator = sillyGenerator();

console.log(generator.next()); // { value: 1, done: false }

console.log(generator.next()); // { value: 2, done: false }

console.log(generator.next()); // { value: 3, done: false }

console.log(generator.next()); // { value: 4, done: false }

next 可以回去到下一个 yield 返回的值,当然上面的代码是非常不自然的,我们可以利用 Generators 来用同步的方式来写异步操作

function request(url) {

    getJSON(url, function(response) {

        generator.next(response);
    });
}

这里的 generator 函数将会返回需要的数据:

function* getData() {

    var entry1 = yield request('http://some_api/item1');

    var data1  = JSON.parse(entry1);

    var entry2 = yield request('http://some_api/item2');

    var data2  = JSON.parse(entry2);
}

通过 yield,我们可以保证 entry1 有 data1 中我们需要解析并储存的数据。

虽然我们可以利用 Generators 来用同步的方式来写异步操作,但是确认错误的传播变得不再清晰,我们可以在 Generators 中加上 Promise: 

function request(url) {

    return new Promise((resolve, reject) => {

        getJSON(url, resolve);

    });

}

然后我们写一个函数逐步调用 next 并且利用 request 方法产生一个 Promise: 

function iterateGenerator(gen) {

    var generator = gen();

    (function iterate(val) {

        var ret = generator.next();

        if(!ret.done) {

            ret.value.then(iterate);

        }

    })();

}

在 Generators 中加上 Promise 之后我们可以更清晰的使用 Promise 中的 .catch 和 reject来捕捉错误,让我们使用新的 Generator,和之前的还是蛮相似的 

iterateGenerator(function* getData() {

    var entry1 = yield request('http://some_api/item1');

    var data1  = JSON.parse(entry1);

    var entry2 = yield request('http://some_api/item2');

    var data2  = JSON.parse(entry2);

});

Async Await

当 ES6 真正到来的时候,async await 可以用更少的处理实现 Promise 和 Generators 所实现的异步处理 

var request = require('request');



function getJSON(url) {

  return new Promise(function(resolve, reject) {

    request(url, function(error, response, body) {

      resolve(body);
    });
  });
}

async function main() {

  var data = await getJSON();

  console.log(data); // NOT undefined!
}

main();

在 js 引擎中,它所实现的和 Generators 其实是一样的,我更推荐在 Generators + Promises 之上使用 async await

 
 
posted @ 2017-03-14 15:35  圣耀  阅读(169)  评论(0编辑  收藏  举报