转载自:http://www.ayqy.net/blog/箭头函数_es6笔记6/
(这篇本来是上周的内容,昨天忘记写啦,赶紧偷偷补上)
最近事情有点多,虽然sort过了,还是略显忙乱。但无论怎样,计划摆在那里,最终都会一件一件完成
废话适可而止,愿老妈身体赶紧好起来~
箭头函数(arrow function),就是C#中的lambda表达式,据说Java8也把它加入了豪华午餐。但不管怎样,JS正在从其它语言吸纳优秀的特性(比如yield, class, 默认参数等等),且不论这些特性好坏,这件事本身就是极好的(至少我们正在使用的是一个充满活力的工具)
只是Java用->箭头,C#用的箭头与JS一样:=>,这个箭头叫“lambda运算符”,行话读作”goes to”
lambda表达式(箭头函数)据说是定义函数最简洁的方法,语法上几乎没有冗余成分了。因为JS弱类型的特点,JS中的lambda表达式要比C#和Java中的更简洁(少了参数类型声明)
一句话,箭头函数就是lambda表达式,提供了更简洁的function定义方式
arg => returnVal语法是创建函数最简洁的方式,定义了一个形参为arg,返回值为returnVal的function
其它语法如下表:
箭头函数语法 语法等价代码含义 x => f(x) function(x) { return f(x); } y=f(x) (x, y)=>x + y; function(x, y) { return x + y; } y=f(x,y)=x+y (x, y)=>{g(x); g(y); return h(x, y);}; function(x, y) { g(x); g(y); return h(x, y); } g(x), g(y) y=f(x,y)==============h(x,y) ()=>({}); function() { return {}; } y={}P.S.第三列的“含义”指的是数学函数含义,lambda表达式本来就是数学家弄出来的
简单示例如下:
// 简单例子,简化匿名函数的定义 var arr = [1, 3, 21, 12]; console.log(arr.map(x => 2 * x)); // [2, 6, 42, 24] console.log(arr.sort((a, b) => a - b)); // [1, 3, 12, 21] arr.forEach((item, index, arr) => { if (index %2 == 0) { console.log(item); } if (index == arr.length - 1) { console.log(`last item is ${item}`); } });复杂一点的示例:
// 复杂例子 var app = { cache: {}, ajax: function(url, callback) { var self = this; function req(url) { var res = `data from ${url}`; console.log(`ajax request ${url}`); // cache res self.cache[url] = res; return res; } var data = req(url); callback(data); } } app.ajax('http://www.xxx.xx', function(data) { console.log(`receive: ${data}`); }); console.log(app.cache);用箭头函数改写上例:
// 用箭头函数改写 var es6App = { cache: {}, ajax(url, callback) { var req = url => { var res = `data from ${url}`; console.log(`ajax request ${url}`); // cache res this.cache[url] = res; return res; } var data = req(url); callback(data); } } es6App.ajax('http://www.q.xx', function(data) { console.log(`receive: ${data}`); }); console.log(es6App.cache);消除了that = this这种必要的废话,其实只要遵守一项原则就可以消除所有的that = this,见下文注意事项中的3.关于this
1个参数时,左边直接写参数名,0个或者多个参数时,参数列表要用()包裹起来
函数体只有1条语句时,右边值自动成为函数返回值,函数体不止1条语句时,函数体需要用{}包裹起来,并且需要手动return
P.S.当然,可能很容易想到不分青红皂白,把() => {}作为箭头函数的起手式,但不建议这样做,因为下一条说了{是有歧义的,可能会带来麻烦
{是唯一1个有歧义的字符,所以返回对象字面量时需要用()包裹,否则会被当作块语句解析
例如:
var f1 = () => {}; f1(); // 返回undefined // 等价于 // var f1 = function() {}; var f2 = () => ({}); f2(); // 返回空对象{} // 等价于 // var f2 = function() {return {};};箭头函数会从外围作用域继承this,为了避免that = this,需要遵守:除了对象上的直接函数属性值用function语法外,其它函数都用箭头函数
这个规则很容易理解,示例如下:
// 场景1 function MyType() {} MyType.prototype.fn = function() {/*定义箭头函数*/}; // 箭头函数中this指向MyType类型实例 // 场景2 var obj = {}; obj.fn = function() {/*定义箭头函数*/}; // 箭头函数中this指向obj区别在于function关键字定义的函数属性中,该函数的this指向这个函数属性所属的对象(匿名函数的this指向global对象或者undefined)。说白了,function能定义一个新this,而箭头函数不能,它只能从外层借一个this。所以,需要新this出现的时候用function定义函数,想沿用外层的this时就用箭头函数
箭头函数没有arguments对象,因为标准鼓励使用默认参数、可变参数、参数解构
例如:
// 一般函数 (function(a) {console.log(`a = ${a}, arguments[0] = ${arguments[0]}`)})(1); // log print: a = 1, arguments[0] = 1 // 与上面等价的箭头函数 (a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`))(1); // log print: Uncaught ReferenceError: arguments is not defined这与函数匿名不匿名无关,规则就是箭头函数中无法访问arguments对象(undefined),如下:
// 非匿名函数 var f = a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`); f(2); // log print: Uncaught ReferenceError: arguments is not defined就ES6箭头函数而言,上面的内容足以随心所欲地驾驭它了,下面我们扯点别的(有意思的)
看到两个单行注释语法不要大惊小怪,历史原因,但目前所有浏览器都支持。没什么用,冷知识吧
lambda演算中唯一基础数据类型是函数,邱奇数(church numerals)就是用高阶函数表示常见的基础数据类型(整数、布尔值、键值对、列表等等)
自然数都是数字,邱奇数都是函数,邱奇数的n是n阶函数,f^n(inc, base) === f(inc, f(inc, ...f(inc, base))),所有邱奇数都是有2个参数的函数
如何用函数表示自然数?内容比较多,这里给一个自然数集小例子:
// 定义自然数集合 var number = (function*(inc, base) { var n = zero; while(true) { yield n(inc, base); n = succ(n); } })(inc, 0); for (var n of number) { console.log(n); // 0, 1, 2, 3, 4 if (n > 3) { break; } }用邱奇数表示的自然数集如下:
// 0, 1, 2 var zero = (inc, base) => base; var one = (inc, base) => inc(base); var two = (inc, base) => inc(inc(base)); // 定义后继函数 f^n -> f^(n+1) // succ = ln.lf.lx.f (n f x) var succ = n => (inc, base) => inc(n(inc, base)); // 定义邱奇数集合 var church = (function*() { var fn = zero; while(true) { yield fn; fn = succ(fn); } })(); // test var [, , , three, four, five, six, seven] = church; console.log(three(inc, 0)); // 3 console.log(four(inc, 0)); // 4 console.log(five(inc, 0)); // 5 console.log(six(inc, 0)); // 6 console.log(seven(inc, 0)); // 7仔细想想的话会发现世界真奇妙,这样也行??感兴趣的话请查看笔者的Demo(实现了减法、乘法和减法)
Y组合子能实现匿名递归函数
Y组合子就是一个函数,如下:
var Y = F => G(G), var G = slef => F(self(self))其中有个不动点的概念。不动点:若F(f) = f,则f是不动点,在Y组合子中,G(G)是不动点
假设现有一个用来求阶乘的递归函数:
var fact = n => n === 0 ? 1 : n * fact(n - 1);这显然不是一个匿名递归,fact是函数名,递归调用它实现计算阶乘。那么如何实现一个匿名的递归函数?这有可能吗?
用Y组合子来一发就好了,如下:
// 定义Y组合子 // var Y = F => G(G); // var G = self => F(self(self)); var Y = F => ((g => g(g)) (g => (F((...x) => g(g)(...x))))); // 实现匿名递归求阶乘 var yFact = Y(f => n => n === 0 ? 1 : n * f(n - 1)); console.log(yFact(5)); // 120奇妙吧?函数式编程中有各种类似的奇妙变换,且不说FP的理解成本,执行效率,这些变换本身就是一些有意思的值得研究的东西,给思维多一点空间,让cpu跑起来
lambda表达式的极致简洁很诱人,定义函数就像写数学公式一样,支持函数式编程的语言本该如此