新的草案 ECMAScript 6 (虽然说是草案,但你可以看到 Firefox 其实已经实现大部分的 feature)离我们越来越近了, 而且我们已经可以通过 babel 在项目中使用这些新的features. 是时候让我们 重新认识一下 JavaScript 了. 下面列出了一部分比较让人兴奋和期待的features. 剧透一下我最激动的还是 Tail Calling
由于 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);
只是少了 function
和 return
以及 block, 不是吗? 如果觉得差不多, 因为你看惯了 JavaScript 的匿名函数, 你的大脑编译器自动的忽略了,因为他们不需要显示的存在.
而 map(x => x*2)
要更 make sense, 因为我们需要的匿名函数只需要做一件事情, 我们需要的是 一个函数 f
, 可以将给定 x
, 映射到 y
. 翻译这句话的最简单的方式不就是 f = (x => x*2)
我喜欢用 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)
名字解释了一切, 对, 代理, 就是能帮你做一些事情的东西.
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就打出来了. (let [[first & rest] [1 2 3 4 5]] rest ) ; => (2 3 4 5)
终于也可以在 JavaScript 里面这样干了.
let [孔连顺, 张全蛋] = ['女神', '男神'] 孔连顺 //=> 男神1 张全蛋 //=> 男神2
当然可以对Map这样干
let {女神, 男神} = {'男神': ['唐马儒', '张全蛋'], '女神': '孔连顺'} 女神 // => 孔连顺 男神 // => ['唐马儒', '张全蛋']
这可以说是最令人高兴的feature了,在js里写递归实在是容易爆栈的一件事情.
images/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; } }
ruby和coffeescript里面这个很fancy的东西
hi='他是' puts "#{hi} 你妹妹"
终于要可以在js里原生使用了
let i = '你们', love = '不能在一起', your = '他是', sister = '你妹妹' console.log(`${i} ${love} ${your} ${sister}`) // => "你们 不能在一起 他是 你妹妹"
虽然只是 syntax sugar, 但是终于不用怪怪的用函数当对象模板了. 木哈哈哈
class Duck extends Bird { constructor() { super(); this.name = "donald" //... } say() { return this.name + " quack"; } static say() { return "quack"; } }
虽然已经习惯用更强大的 第三方库 干这个事情, 但是原生支持的话也是极好的.
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') })
对于python程序员来说, yield
这个关键字可能再熟悉不过了, 终于, js 也有 yield
了.
var fibonacci = { [Symbol.iterator]: function*() { var pre = 0, cur = 1; for (;;) { var temp = pre; pre = cur; cur += temp; yield cur; } } }
这短短几行代码里有三个es6的新feature
Symbol.iterator
是一个全局的symbol iterator
上挂的函数会在被遍历的时候x调用, 如 for..of
for (var n of fibonacci) { (forof) if (n > 100) break; console.log(n); }
function*
声明该函数为生成器函数, 在每次被调用的时候返回 yield
的值.