箭头函数


在 ES6 之前,使用function创建匿名函数是很熟悉的一件事:

> var arr = [1, 2, 5, 2, 5, 6, 9];
undefined
> arr.filter(function(elem) {
...     return elem % 2 === 0;
... }).map(function(elem) {
...     return elem * 10;
... });
[ 20, 20, 60 ]
>

可读性很差,在 ES6 中,可以写成:

> let arr = [1, 2, 5, 2, 5, 6, 9];
undefined
> arr.filter(elem => elem % 2 == 0).map(elem => elem * 10);
[ 20, 20, 60 ]
>

elem => elem % 2这个表达式,在 ES6 中称为箭头函数(Arrow Function),是类似其他语言中被称为 Lambda 表达式的东西,不过,Lambda 表达式这名词其实没有严谨的定义,在 ES6 中就用个箭头函数名词来区别吧!

如果有两个以上的参数,箭头函数的参数列必须使用()包含起来,没有参数的话,就只要写()就可以了,在只有一行表达式的情况下,=>右边的表达式返回值会自动return为箭头函数的返回值。如果要换行的话,可以使用{},最后要返回值时可以使用return

> let plus = (a, b) => a + b;
undefined
> plus(1, 2);
3
> let minus = (a, b) => {
...     return a - b;
... }
undefined
> minus(10, 5)
5
>

箭头函数的目的之一,是让那些运算本体很简单的函数,可以用简洁的方式编写,因此,虽然可以使用{},然而,若本体很长的话,并不建议使用箭头函数。

另一方面,箭头函数也不单纯只是function匿名函数的简写形式,它在this方面的解析方式,并不是依照function的既有方式,例如。底下是function的形式:

this.x = 1;

var o = {
    x: 10,
    foo : function() {
        console.log(this.x);
    }
};

o.foo();

你应该知道执行结果会显示 10 吧!换成箭头函数的话会如何呢?

this.x = 1;

let o = {
    x: 10,
    foo : () => console.log(this.x)
};

o.foo();

执行结果会显示 1,也就是说,箭头函数中的this,并不是依当时调用者来绑定的,而是依当时的语汇(Lexical)环境来绑定,白话的话,就是哪个环境包含箭头函数,而当时环境中的this,就是箭头函数的this,一旦绑定了this的对象,就不会再改变,就算使用o.foo.call(o)也不会改变。

在上面的例子中,包含箭头函数的环境是全局,在全局中写this是什么对象,就是上例中箭头函数中this的对象,此时就像是:

let who = this;

this.x = 1;

let o = {
    x: 10,
    foo : function() {
        console.log(who.x);
    }
};

o.foo();

再来看一个例子:

var o = {
    x   : 10,
    foo : function() {
        return {
            x : 20,
            doFoo : function() {
                console.log(this.x);
            }
        }
    }
};

o.foo().doFoo();

这个比较难一些,也是为什么function中的this难以掌握的例子之一,调用o.foo会返回一个对象,该对象有个doFoo方法,实际上是调用o.foo返回的对象在调用doFoo方法,因此doFoo方法中的this会是调用o.foo返回的对象,该对象上的x特性是 20,也就是最后的显示结果。

换成箭头函数的话:

let o = {
    x   : 10,
    foo : function() {
        return {
            x : 20,
            doFoo : () => console.log(this.x)
        }
    }
};

o.foo().doFoo();

哪个环境包含了箭头函数呢?foo特性参考的函数!在该函数中写this的话,代表着o参考的对象,因此箭头函数中的this代表的就是o参考的对象,因此会显示 10,就像以底下的作用:

let o = {
    x   : 10,
    foo : function() {
        var who = this;
        return {
            x : 20,
            doFoo : function() {
                console.log(who.x);
            }
        }
    }
};

o.foo().doFoo();

如果想更进一步探索的话,箭头函数中若出现super,也是依当时语汇环境决定的,super可以出现在〈对象字面量简化与增强〉谈过的方法简便定义形式,代表着对象的原型(__proto__),因此底下的程序最后会显示 10:

var o = {
    foo() {

        var o2 = {
            foo2() {
                console.log(super.x);
            }
        };

        o2.__proto__ = {x: 10};     

        o2.foo2();
    }
};

o.__proto__ = {x : 1};

o.foo();

__proto__算是被列入标准了(尽管不建议使用,ES6 提供了标准化的Object.setPrototypeOf,之后有机会再介绍),在上例中,分别设定了两个对象的__proto__,而super是依哪个对象调用了方法而绑定的。

如果改成箭头函数的话:

var o = {
    foo() {

        var o2 = {
            foo2 : () => console.log(super.x)
        };

        o2.__proto__ = {x: 10};     

        o2.foo2();
    }
};

o.__proto__ = {x : 1};

o.foo();

其实如果单看o2的定义,是不能有super出现的,然而由于有foo方法的包覆,而super来自于foo环境,因此上面的程序得以执行成功,并且显示 1 的结果。


展开阅读全文