HOME | EDIT | RSS | ABOUT | GITHUB

Essential EcmaScript 6

新的草案ECMAScript 6 (虽然说是草案,但你可以看到 Firefox 其实已经实现大部分的 feature)离我们越来越近了, 而且我们已经可以通过 babel 在项目中使用这些新的features. 是时候让我们 重新认识一下 JavaScript 了. 下面列出了一部分比较让人兴奋和期待的features. 剧透一下我最激动的还是 Tail Calling

Arrow Function

由于 arrow function 只在Firefox 22以上版本实现, 这里所有代码都可以在Firefox的Console中调试, 其他chrome 什么的都没有实现(完全) Chrome有一个 feature toggle 可以打开部分 es6 功能 chrome://flags/#enable-javascript-harmony . 另外每节的最后我都会给出完整代码的可执行的 jsbin 链接.

你可以用两种方式定义一个箭头函数

([param] [, param]) => {
   statements
}
// or
param => expression

单个表达式可以写成一行, 而多行语句则需要 block {} 括起来.

看看旧的匿名函数怎么写一个使数组中数字都乘2的函数.

var a = [1,2,3,4,5];
a.map(function(x){ return x*2 });

用箭头函数会变成

a.map(x => x*2);

只是少了 functionreturn 以及 block, 不是吗? 如果觉得差不多, 因为你看惯了 JavaScript 的匿名函数, 你的大脑编译器自动的忽略了,因为他们不需要显示的存在.

map(x => x*2) 要更 make sense, 因为我们需要的匿名函数只需要做一件事情, 我们需要的是 一个函数 f, 可以将给定 x, 映射到 y. 翻译这句话的最简单的方式不就是 f = (x => x*2)

Let

我喜欢用 let 替换了以前的 var, 为什么, 以前的var有什么不好.

var 的意思是变量, 它自己没有任何的scope,所以的作用范围非常难以推断. 但是我们通常只想在一个scope里给定一个值,而不影响scope外界的任何绑定.

想想以前 var 的scope是什么, function

var a = 'first assign'
function b (){
    var a = 'second assign'
    console.log(a)
}
console.log(a)
b()
console.log(a)

来看看 lisp 给了我们很好的模范如何解决绑定这种问题.

(let ((something 2))
  (+ something 1)
  ) ; => 3

es5 的 let 完全等价应该是

(function(something){
    return something +=1
}).call(this, 2)

let 内的任何操作都不会影响外部绑定. 这样更安全而且容易推断, 这也是很多库用来封装js模块的方式, 比如jquery, 比如coffee会自动 对每个模块添加类似的function wrapper.

而es6, let 给我们带来了scope. 注意看,除了括号成了中括号,好像就是 lisp 那个意思了.

let a = 'first assign'
{
    let a = 'second assign'
    console.log(a)
}
console.log(a)

DONE Proxy

名字解释了一切, 对, 代理, 就是能帮你做一些事情的东西.

JavaScript是动态语言,也就是说最关心的事情是行为.所以行为也能通過meta programming让其带那么一些行为.

试试把下列代码考到Firefox的Console中

  let github_api = function(){};
  github_api.path='https://api.github.com';
  let restful = function restfulize(api){
      return new Proxy(api, {
          get: function(receiver, name){
              receiver.path+='/'+name;
              return restfulize(receiver);
          },
          apply: function(receiver, that, args){
              console.log(`sending request to ${receiver.path}`)
          }
      })
  }

  restful(github_api).user.jcouyang()
// => "sending request to https://api.github.com/user/jcouyang"

简单的几行代码,我们就自制了一个接口非常流畅的restful api client. 再也不用麻烦的拼接字符串, 转成代理的方法适当接口更已读且易于重用.

magic到底在哪呢, proxy 给目标函数代理了两个方法, 一个 get, 一个 apply,

  • get 不管从 proxy 中取任何值都会运行 get.

一直返回新的相同但是path变化了的 proxy, 所以不管是 .user 还是 .jcouyang 都是拼接成 path, 并返回一个新的以新 path 为目标的proxy

  • apply 里面是运行这个proxy时要做的事情. 所以当我调用 jcouyang() 的时候, log就打出来了.

Destructuring

G9hwRUsSFrPpK.gif
(let [[first & rest] [1 2 3 4 5]]
     rest
     ) ; => (2 3 4 5)

终于也可以在 JavaScript 里面这样干了.

let [孔连顺, 张全蛋] = ['女神', '男神']
孔连顺 //=> 男神1
张全蛋 //=> 男神2

当然可以对Map这样干

let {女神, 男神} = {'男神': ['唐马儒', '张全蛋'], '女神': '孔连顺'}
女神 // => 孔连顺
男神 // => ['唐马儒', '张全蛋']

Tail Calling

这可以说是最令人高兴的feature了,在js里写递归实在是容易爆栈的一件事情.

tail-recur.gif

终于, 终于有了尾递归优化. 虽然大部分浏览器,包括firefox都没有实现, 但其实我们已经可以用中间编译器babel帮我们编译成 优化过的尾递归.

function a(b){
  if(b<0)return "hehe"
  return a(b-1)
}

duang的一下就变成了循环. 妈的再也不用担心我的 菊花 栈被爆了.

function a(_x) {
  var _again = true;

  _function: while (_again) {
    _again = false;
    var b = _x;

    if (b < 0) {
      return "hehe";
    }_x = b - 1;
    _again = true;
    continue _function;
  }
}

Template Strings

ruby和coffeescript里面这个很fancy的东西

hi='他是'
puts "#{hi} 你妹妹"

终于要可以在js里原生使用了

let i = '你们',
    love = '不能在一起',
    your = '他是',
    sister = '你妹妹'

console.log(`${i} ${love} ${your} ${sister}`)
// => "你们 不能在一起 他是 你妹妹"

Class

虽然只是 syntax sugar, 但是终于不用怪怪的用函数当对象模板了. 木哈哈哈

class Duck extends Bird {
    constructor() {
        super();
        this.name = "donald"
        //...
    }
    say() {
        return this.name + " quack";
    }
    static say() {
        return "quack";
    }
}

Promises

虽然已经习惯用更强大的 第三方库 干这个事情, 但是原生支持的话也是极好的.

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

Generator

对于python程序员来说, yield 这个关键字可能再熟悉不过了, 终于, js 也有 yield 了.

XFITRJv9IMhi0.gif
var fibonacci = {
  [Symbol.iterator]: function*() {
    var pre = 0, cur = 1;
    for (;;) {
      var temp = pre;
      pre = cur;
      cur += temp;
      yield cur;
    }
  }
}

这短短几行代码里有三个es6的新feature

  • Symbol: es6的新的primitive类型, Symbol.iterator 是一个全局的symbol
  • Iterator: 对象的 iterator 上挂的函数会在被遍历的时候x调用, 如 for..of
for (var n of fibonacci) {
  if (n > 100)
    break;
  console.log(n);
}
  • Generator: function* 声明该函数为生成器函数, 在每次被调用的时候返回 yield 的值.

Footnotes:

1

Chrome有一个 feature toggle 可以打开部分 es6 功能 chrome://flags/#enable-javascript-harmony