Essential EcmaScript 2017

欧阳继超

oyanglulu@gmail.com

Table of Contents

Overview

大部分浏览器都已经实现 90%+ 的 ES6 features 了,如果现在还在看 ES6入门 我呵呵了。若是想快速回顾,不妨看看 Essential EcmaScript 6.

下面我们来看看 2017 草稿中到底有些什么新黑科技:

  • Object.values/Object.entries
  • String padding
  • Object.getOwnPropertyDescriptors
  • Trailing commas in function parameter lists and calls
  • Async Functions

如果要问这些 proposals 都是哪来的,其实都可以在 tc39 的 这个 页面找到

image.png

能到达这个页面的都是 stage 4 的 proposals

而到达 stage 4 的,就会出现在 draft 上 https://tc39.github.io/ecma262/

glossary (强行科普)

tc39

Technical Committees 39

ecma-262

ecma 262 号规范

stage

  • 0 strawman
  • 1 proposal
  • 2 draft
  • 3 candidate
  • 4 finished

Fun Facts

busted.gif

有人知道 ECMA-334 和 408 分别是什么语言的规范吗?

ES7 (EcmaScript 2016)

年份和版本号不要搞混了,7是指第七个版本,2016是release的年份,就像 ES6 也叫 ES2015 一样

ES7 很少被提及,是因为它的 feature 太少了,而且 没什么卵用

Array.prototype.includes

这个没什么好说的,跟lodash的 _.includes 一样一样的

终于从语言层面上让 indexOf > -1 更语义化了些。

Exponentiation Operator

幂操作符, 这个应该是从 python 抄来的

2**3 === 8

babel的实现是简单的语法糖到 Math.pow

ES8 (EcmaScript 2017)

下面来看看激动人心的ES8,先来看前面几个 没什么卵用 的小改动

0day-accident.gif

Object.values

var obj = { foo: "bar", baz: 42 };
var array = [1,2,3]
var symbol = Symbol('abc')
var fn = function(){ return {foo: 'bar'} }
fn.baz = 42;
var string = 'foobar'
var number = 123
console.log(Object.values(obj));
console.log(Object.values(array));
console.log(Object.values(symbol));
console.log(Object.values(string));
console.log(Object.values(number));
console.log(Object.values(fn));
console.log(Object.values(true));
[ 'bar', 42 ]
[ 1, 2, 3 ]
[]
[ 'f', 'o', 'o', 'b', 'a', 'r' ]
[]
[ 42 ]
[]

Object.entries

var obj = { foo: "bar", baz: 42 };
var array = [1,2,3]
var symbol = Symbol('abc')
var fn = function(){ return {foo: 'bar'} }
fn.baz = 42;
var string = 'foobar'
var number = 123
console.log(Object.entries(obj));
console.log(Object.entries(array));
console.log(Object.entries(symbol));
console.log(Object.entries(string));
console.log(Object.entries(number));
console.log(Object.entries(fn));
console.log(Object.entries(true));
[ [ 'foo', 'bar' ], [ 'baz', 42 ] ]
[ [ '0', 1 ], [ '1', 2 ], [ '2', 3 ] ]
[]
[ [ '0', 'f' ],
  [ '1', 'o' ],
  [ '2', 'o' ],
  [ '3', 'b' ],
  [ '4', 'a' ],
  [ '5', 'r' ] ]
[]
[ [ 'baz', 42 ] ]
[]

String padding

还记得 leftpad 吗? left-pad-npm.png

有了ES2017,麻麻再也不用担心Leftpad作者生气了

String.prototype.padStart

'abc'.padStart(10);         // "       abc"
'abc'.padStart(10, "foo");  // "foofoofabc"

String.prototype.padEnd

'abc'.padEnd(10);         // "abc       "
'abc'.padEnd(10, "foo");  // "abcfoofoof"

Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptor 的复数形式

现在继承可以这么简单的写

function superclass() {}
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
    // define your methods and properties here
}));

shallow clone

const shallowClone = (object) => Object.create(
  Object.getPrototypeOf(object),
  Object.getOwnPropertyDescriptors(object)
);

Trailing commas in function parameter lists and calls

这个东西没什么用,跟数组一样,只有code diff的时候

  1: function clownPuppiesEverywhere(
  2:   param1,
  3:   param2,
+ 4:   param3,  // updated to add new parameter
  5: ) { /* ... */ }

才有那么一丢丢用

Async Functions

终于到 async function 了,但是在开始之前,我们来回顾一下 ES6 的 Promise 和 generator

nibbler-eat-chickens.gif

Promise

new Promise((resolve, reject) => {
    console.log('first')
    setTimeout(resolve, 1000);
}).then(() => {
    console.log('next 1s')
    throw new Error("hmm");
}).catch(err => {
    console.log('error:'+err)
})
first
next 1s
error:Error: hmm

generator

现代浏览器都已经支持 generator 了, 写一个fibonacci数列生成器

  function* fibonacci() {
      var pre = 0, cur = 1;
      for (;;) {
          var temp = pre;
          pre = cur;
          cur += temp;
          yield cur;
      }
  }
let fib = fibonacci()
console.log(fib.next())
console.log(fib.next())
console.log(fib.next())
console.log(fib.next())
console.log(fib.next())
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 8, done: false }

但是promise只会把callback hell,改成 then hell

var asyncVal1 = Promise.resolve(1)
var asyncVal2 = Promise.resolve(2)
var asyncVal3 = Promise.resolve(3)
asyncVal1.then(val1=>(
    asyncVal2.then(val2=>(
        asyncVal3.then(val3=>val1+val2+val3)
    ))
))
.then(log('sum of val 1 2 3'))

虽然这种情况可以用 Promise.all 来解决

Promise.all([asyncVal1, asyncVal2, asyncVal3])
       .then(([val1,val2,val3])=>val1+val2+val3)

但是并不是所有then hell都可以用promise的combinator就能解决的, 而generator的支持,彻底让我们离开了 then hell

coroutine

使用 task 或者 when 你可以使用 generator 和 promise 来写 coroutine

spawn(function*(asyncVal1, asyncVal2, asyncVal3) {
    let val1 = yield asyncVal1;
    let val2 = yield asyncVal2;
    let val3 = yield asyncVal3;
    return val1 + val2 + val3
});

最重要的是,还可以命令式的 try catch

spawn(function*(asyncVal1, asyncVal2, asyncVal3) {
    try {
        let val1 = yield asyncVal1;
        let val2 = yield asyncVal2;
        let val3 = yield asyncVal3;
        return val1 + val2 + val3
    } catch(e) {
        return NaN
    }
});

现在有了 async function,不再需要第三方库的支持

async function asyncSum(asyncVal1, asyncVal2, asyncVal3) {
    let val1 = await asyncVal1;
    let val2 = await asyncVal2;
    let val3 = await asyncVal3;
    return val1 + val2 + val3
};

但是不用太激动

其实 async 只是 spawn 的语法糖

async function <name>?<argumentlist><body>

// =>

function <name>?<argumentlist>{ return spawn(function*() <body>, this); }

而spawn的实现也很简单

返回一个 Promise

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
      ...
    });
}

call the generator function genF

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.callself; // <--
        ...
    });
}

异步的递归step

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            next = nextF();  // <-- 2
            Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); }); // <-- 3
            });
        }
        step(function() { return gen.next(undefined); }); // <-- 1
    });
}

resolve or reject promise

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            var next;
            try {
                next = nextF();
            } catch(e) {
                // finished with failure, reject the promise
                reject(e);  // <--
                return;
            }
            if(next.done) {
                // finished with success, resolve the promise
                resolve(next.value);  // <--
                return;
            }
            // not finished, chain off the yielded promise and `step` again
            Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); });
            }, function(e) {
                step(function() { return gen.throw(e); }); // <--
            });
        }
        step(function() { return gen.next(undefined); });
    });
}

Refs

/

Essential EcmaScript 2017 - 欧阳继超