箭头函数_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.参数列表与返回值的语法

1个参数时,左边直接写参数名,0个或者多个参数时,参数列表要用()包裹起来

函数体只有1条语句时,右边值自动成为函数返回值,函数体不止1条语句时,函数体需要用{}包裹起来,并且需要手动return

P.S.当然,可能很容易想到不分青红皂白,把() => {}作为箭头函数的起手式,但不建议这样做,因为下一条说了{是有歧义的,可能会带来麻烦

2.有歧义的字符

{唯一1个有歧义的字符,所以返回对象字面量时需要用()包裹,否则会被当作块语句解析

例如:

var f1 = () => {};
f1();   // 返回undefined
// 等价于
// var f1 = function() {};

var f2 = () => ({});
f2();   // 返回空对象{}
// 等价于
// var f2 = function() {return {};};

3.关于this

箭头函数会从外围作用域继承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时就用箭头函数

4.关于arguments对象

箭头函数没有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箭头函数而言,上面的内容足以随心所欲地驾驭它了,下面我们扯点别的(有意思的)

1.JS中支持的所有箭头

到ES6为止,目前js中支持的箭头
箭头 含义
<!– 单行注释
–> 在行首表示单行注释,在其它位置表示“趋向于”(n –> 0等价于n– > 0)
<= 比较运算符,小于等于
=> 箭头函数

看到两个单行注释语法不要大惊小怪,历史原因,但目前所有浏览器都支持。没什么用,冷知识吧

2.lambda演算与邱奇数

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(实现了减法、乘法和减法)

3.Y组合子与函数式编程

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表达式的极致简洁很诱人,定义函数就像写数学公式一样,支持函数式编程的语言本该如此

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code