ES6箭头函数总结

箭头函数是ES6新出的一种语法,它相当于匿名函数,并且简化了函数定义。本文总结箭头函数的基本语法,使用注意点等。

一、基本语法

  ES6允许使用“箭头”(=>)定义函数。
  下面看几种常用写法:

let func = () => {
    console.log('hello world')
};
// 等同于
let func = function () {
    console.log('hello world')
};


let sum = (num1, num2) => {
    return num1 + num2;
};
// 等同于
let sum = function(num1, num2) {
    return num1 + num2;
};

二、简写语法

  箭头函数还有一些简写语法和应用场景,下面演示一下:

  • 如果箭头函数只有一个参数,可以省略掉括号。

let func = num => {
    console.log(num);
};
// 等同于
let func = function (num) {
    console.log(num);
}

  • 当箭头函数仅有一个表达式的时候,可以省略{}和return。

let func = num => num;
// 等同于
let func = function (num) {
    return num;
}

  • 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

let sum = (num1, num2) => { return num1 + num2; }

  • 需要注意的一点:因为大括号被解释成代码块,如果箭头函数直接返回一个对象,那么必须要在大括号外面加上括号。

let person = name => ({ name: name, age: "18" });

  • 更加简洁的表达式:
  只用了两行,就定义了两个简单的工具函数

const isEven = n => n % 2 === 0;
const square = n => n * n;

  • 箭头函数的一个用处是简化回调函数,例子如下:

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭头函数写法
[1,2,3].map(x => x * x);

三、使用注意点

1. this指向问题

  我们知道普通函数的this指向调用它的那个对象
  但箭头函数的this永远指向其父作用域,任何方法都改变不了,包括call,apply,bind。

  这是因为箭头函数的作用域是词法作用域,词法作用域简单来讲就是,一切变量(包括this)都根据作用域链来查找。
  具体可以了解下词法作用域和动态作用域相关知识,以及《你不知道的JavaScript》一书中相关的内容。

  既然箭头函数本身没有this,那么自身的this会在函数声明的时候做绑定,它是根据上级的function中的this来做绑定的。如果上级的function也是箭头函数,就再往上级查找。
  * 有句话这么说:“函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。”我觉得不是很好理解,或者说这个准则写得看上去有点歧义,所以看过即可,不要绕进去了。

  综上,箭头函数中的this,首先从它的父级作用域中找,如果父级作用域还是箭头函数,再往上找祖父级作用域,如此直至找到this的指向(一直向上找到全局作用域,指向window)。

  举几个代码例子加深理解:

let person = {
    name: '傻强1号',
    init:function(){ 
        // 为body添加一个点击事件,看看这个点击后的this属性有什么不同
        document.body.onclick = function(){
            console.log(this.name);
            console.log(this);
        }
    }
}
person.init();
// 打印结果:
// undefined
// body这个标签对象

  上例中,onclick回调是一个function(匿名函数、普通函数),函数在执行时就是在节点对象的环境下,this指向当前节点。该节点没有name属性,故this.name是undefined。

let person = {
    name:'傻强2号',
    init:function(){
        // 为body添加一个点击事件,看看这个点击后的this属性有什么不同
        document.body.onclick = ()=>{
            console.log(this.name);
            console.log(this);
        }
    }
}
person.init();
// 打印结果:
// 傻强2号
// {name: "傻强2号", init: ƒ}

  上例中,init是一个function(匿名函数、普通函数),以person.init调用,其内部this就是person本身;而onclick回调是箭头函数,其内部的this,就是父作用域的this,就是person,能得到name。

let person = {
    name:'傻强3号',
    init:()=>{
        // 为body添加一个点击事件,看看这个点击后的this属性有什么不同
        document.body.onclick = ()=>{
            console.log(this.name);
            console.log(this);
        }
    }
}
person.init();
// 打印结果:
// ''
// window对象

  上例中,init是一个箭头函数,其内部的this为全局window;而onclick回调是箭头函数,其内部的this,就是父作用域的this,也就是init函数的this,即window。
  总结出的结论就是:如果上级也是箭头函数,再上级查找。

2. 箭头函数不能作为构造函数

  箭头函数不能作为构造函数,不能使用new。
  用代码来讲解:

// 正常构造函数如下:
function Person(p){
    // 完成初始化
    this.name = p.name;
}
// 如果用箭头函数作为构造函数,则如下
var Person = (p) => {
    this.name = p.name; // ???
}

  由于this必须是对象实例,而箭头函数是没有实例的,它既然没有this,此处的this指向别处,不能产生person实例,自相矛盾,所以,箭头函数不能做构造函数。
  此时如果强行去执行let p = new Person(),会报Uncaught TypeError: Person is not a constructor异常。

3. 箭头函数没有arguments

  箭头函数本身没有arguments,如果箭头函数在一个function内部,它会将外部函数的arguments拿过来使用。
  也就如下面代码所示:

function func0(n1,n2,n3){ // [1, 2, 3]
    let func = (n1,n2)=>{
        console.log(arguments);
    }
        func();
}       
func0(1,2,3);
// 打印结果:arguments对象,其中有[1, 2, 3]

  箭头函数中要想接收不定参数,应该使用rest参数...解决,如下代码所示:

let B = (b)=>{
    console.log(arguments);
}
B(1,3,5,7);   // 报错:Uncaught ReferenceError: arguments is not defined

let C = (...c) => {
    console.log(c);
}
C(2,4,6,8);  // 打印结果:[2, 4, 6, 8]

4. 箭头函数通过call和apply调用,不会改变this指向,只会传入参数

let obj2 = {
    a: 10,
    b: function(n) {
        let f = (n) => n + this.a;
        return f(n);
    },
    c: function(n) {
        let f = (n) => n + this.a;
        let m = {
            a: 20
        };
        return f.call(m,n);
    }
};
console.log(obj2.b(1)); // 11
console.log(obj2.c(1)); // 11

5. 箭头函数没有原型属性

var a = ()=>{
    return 1;
}

function b(){
    return 2;
}

console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}

6. 箭头函数不能作为Generator函数,不能使用yield关键字

7. 箭头函数返回对象时,要加一个小括号

  这一点其实上面在介绍箭头函数的简写语法时,已经说过了,这里再重复一遍加深印象。

var func = () => ({ foo: 1 }); // 正确
var func = () => { foo: 1 }; // 错误

8. 箭头函数在ES6 class中声明的方法为实例方法,不是原型方法

// demo1
class Super{
    sayName(){
        // do some thing here
    }
}
// 通过Super.prototype可以访问到sayName方法,这种形式定义的方法,都是定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName // true
// 所有实例化之后的对象共享prototypy上的sayName方法


// demo2
class Super{
    sayName =()=>{
        // do some thing here
    }
}
// 通过Super.prototype访问不到sayName方法,该方法没有定义在prototype上
var a = new Super()
var b = new Super()
a.sayName === b.sayName // false
// 实例化之后的对象各自拥有自己的sayName方法,比demo1需要更多的内存空间

  因此,在class中尽量少用箭头函数声明方法。

9. 多重箭头函数就是一个高阶函数,相当于内嵌函数

const add = x => y => y + x;
// 相当于
function add(x){
    return function(y){
        return y + x;
    };
}

10. 箭头函数常见错误

let a = {
    foo: 1,
    bar: () => console.log(this.foo)
}

a.bar()
// 打印结果:undefined

  上述代码中,bar函数中的this指向父作用域,而a对象没有作用域,因此this不是a,打印结果为undefined。

function A() {
    this.foo = 1
}

A.prototype.bar = () => console.log(this.foo)

let a = new A()
a.bar()
// 打印结果:undefined

  上述代码中,原型上使用箭头函数,this指向是其父作用域,并不是对象a,因此得不到预期结果。

四、总结用法

  • 在使用的时候,不必那么复杂,建议掌握一种就可以了 ()=>{}
  • 看到极为简单的情况也要认识 let func = num => num;
  • 一个参数: var f = num => num; 也可以 var f = (num) => num;
  • 没有参数: var f = ()=> 1;
  • 多个参数: var f = (n1,n2)=> n1 + n2;
  • 多行代码: var f = (n1,n2)=>{//code.... return n1 + n2;}

五、总结注意点

  • 箭头函数本身没有 this
  • 自身的 this 会在函数声明的时候做绑定
  • 根据上级的function中的this来做绑定,如果上级也是箭头函数,再上级查找,绑定以后就不再发生改变了,this不再多变
  • 箭头函数本没有this,绑定后不再多变
  • 箭头函数不可以做构造函数
  • 箭头函数本身也没有arguments
  • 如果该箭头函数外部包含function,在函数的调用时,箭头函数会将外部arguments拿来


参考
https://www.jianshu.com/p/fb5f900c663d


  转载请注明: 文渊博客 ES6箭头函数总结

  目录