HOME | EDIT | RSS | ARCHIVE | INDEX | ABOUT

Essential EcmaScript 6

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

Arrow Function

由于 arrow function 只在Firefox 22以上版本实现, 这里所有代码都可以在Firefox的Console中调试, 其他chrome 什么的都没有实现(完全)1. 另外每节的最后我都会给出完整代码的可执行的 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