JavaScript 之 this 与箭头函数

723 阅读3分钟

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。 由于箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。

前言

阅读下面的内容要先看下《你不知道的javascript(上)》中的第二部分:this和对象原型(里面对于this指向的理论部分主要来源于该书)。

问题

这周在写代码的时候,听同事在说箭头函数无法更改this的指向,并且使用下面代码进行验证:

// (1) 使用箭头函数,this一直指向window
var a = 0
var obj1 = {
    a: 1,
    fn: () => { console.log(this.a) }
}
obj1.fn() // 结果:0

var obj2 = {
    a: 2
}
obj2.fn = obj1.fn
obj2.fn() // 结果:0
// (2)不使用箭头函数,this的指向会更改
var obj3 = {
    a: 3,
    fn: function() { console.log(this.a) }
}
obj3.fn() // 结果:3

第一段代码,无论fn绑定在哪个对象上,this.a始终指向全局的a(在非严格模式下的浏览器中我们可认为是window)

然而,这种说法是有问题的。

先理解this

this代表什么取决于:

  1. 调用位置
  2. 四种绑定规则
var a = 0
test() // 调用位置在此,通过【默认绑定规则】,可知test的this绑定到了window上
function test() {
    console.log(this) // this是window
    var obj1 = {
        a: 1,
        fn: function() {
            console.log(this) // this是obj1
            console.log(this.a)  
        }
    }
    obj1.fn() // 调用位置在此,通过【隐式绑定规则】,可知fn中的this绑定到了obj1上
    var obj2 = {
        a: 2
    }
    obj2.fn = obj1.fn
    obj2.fn() // 调用位置在此,【隐式绑定规则】,可知fn内的this绑定到了obj2
}

箭头函数中的this

其实这么起标题不太准确(然而想不出好的名字),因为箭头函数是不会创建自己的this的(MDN),那么下面代码的this是来源于哪里的呢?

var obj3 = {
    a: 3,
    fn: () => {
        console.log(this.a)
    }
}
obj3.fn() // 调用位置在此,this指向window
arrowFoo1() // (1)此处是调用位置,使用【默认的绑定规则】,this = window
function arrowFoo1() {
    var obj1 = {
        vv: 'svv1',
        fn: () => {
            console.log(this.vv, 'vv的值是')
        }
    }
    // (2)调用位置是此处,若fn不是箭头函数,那么这里也会有自己的this(即【隐式调用规则】,this绑定为obj1),而fn是箭头函数,则它没有this,那么fn内部调用的this是谁?
    // 这时候需要根据作用域的规则,往外层查找,找到哪里?找到调用obj1.fn的调用栈,即arrowFoo1函数的this,那么从(1)中可知,那个this指向的是window
    obj1.fn()
    var obj2 = {
        vv: 'svv2',
    }
    obj2.fn = obj1.fn
    obj2.fn()
}

因此,箭头函数里面要是用到了this,那么其实它是通过作用域链,往外进行查找的,找到了就直接返回咯。

拓展练习

为加强对于this的理解,在网上找了些相关的问题,如下:

(1)练习1

问题:

function foo() {
    return function () {
        return () => {
            console.log('id:' + this.id)
        }
    }
}

foo().call({id: 1})() // 问题1:结果是什么?
foo.call({id: 1})()() // 问题2:结果是什么?

解析:

问题1中打印的结果是1,问题2打印的结果是undefined,下面是具体的解析。

// --------------------- 问题1 ---------------------
// (1)执行完 foo() 后,得出下面的函数,     this = window
// function () {
//     return () => {
//         console.log('id:' + this.id)
//     }
// }

// (2)执行 foo().call({id: 1}) 后,      this = {id: 1}
// this 绑定为 {id: 1}的对象

// (3)最终        箭头函数自身没有this,往外层找,找到(2)中的 this = {id: 1}
// foo().call({id: 1})()  就是执行  () => { console.log('id:' + this.id) } 而箭头函数没有this,那么用的是作用域链外层的this
foo().call({id: 1})() // 1 <= 调用位置

// --------------------- 问题2 ---------------------
// (1)执行foo.call({id: 1}),得           this = {id: 1}
// function () {
//     return () => {
//         console.log('id:' + this.id)
//     }
// }

// (2)执行 foo.call({id: 1})() 后,       this = window
foo.call({id: 1})()()

参考文献

[1] 《你不知道的javascript》

[2] JS中的箭头函数与this