JavaScript学习笔记(五)this关键字

JavaScript中this关键字的指向是一个让初学者(比如我...)很头疼的问题。本文的内容主要出自阮一峰老师JavaScript教程this 关键字一节,读完受益良多,在此简要总结,方便日后回顾。

1. this关键字的意义

在理解this指向问题之前,需要先了解设计这个关键字的初衷。在JavaScript中,存储引用类型(广义的对象)值的变量中存放的是地址。访问对象属性时,首先从变量获取对象地址,然后再从该地址读取对应的属性,此时属性所处的上下文(环境)就是当前对象。

如果对象的属性是一个函数,那么该属性本身存储的是函数的地址。调用对象方法时,先从变量获取对象地址,再从对象地址中读取方法地址,再调用函数,函数调用时的环境也是当前对象。

函数作为一个值,可以在不同的上下文中执行,而this关键字的作用正是用来指代函数执行时所处的环境。

const obj = {
    a: 1,
    b: function() {
        console.log(this.a)
    }
}

obj.b() // 1

上面这段代码的执行过程可以描述为:

  • 先通过变量obj获取对象地址
  • 再从对象地址获取函数b的地址
  • 通过函数b的地址调用函数,this指向当前环境,也就是obj对象,因此this.a就是1

2. this关键字的指向

this指向的就是引用this的环境(或者称之为对象)。

2.1 全局作用域

在全局作用域中,this指向window对象(浏览器环境)。还是上面的例子:

const obj = {
    a: 1,
    b: function() {
        console.log(this.a)
        console.log(this)
    }
}
window.a = 2
const temp = obj.b
temp() // 2 window {...}
obj.b() // 1 {a: 1, b: f}

可以看出,如果将obj.b函数赋值给变量temp,则变量temp存储了该函数的地址,调用temp()是在全局环境中直接获取了函数的地址,因此this指向window对象。而通过对象obj调用函数时,函数内部的this指向obj

2.3 构造函数

构造函数中的this指向通过new关键字调用构造函数的实例对象。

2.3 对象的方法

对象方法中的this指向方法执行时所在的对象。
如果对象的属性还是一个对象,那么嵌套的对象的方法中的this指向嵌套的对象,而不是最外层的对象。看下面这个例子:

const obj = {
    a: 1,
    b: {
        m: function() {
            console.log(this.a)
        }
    }
}
obj.b.m() // undefined

调用obj.b.m()时,其中的this指向属性b这个对象,因此this.aundefined

2.4 箭头函数

ES6新增了箭头函数语法。箭头函数没有this,如果在箭头函数中使用this,就当做一个普通的变量,会按照作用域规则从当前作用域向上级作用域逐级查找,直到全局作用域对象window

const obj = {
    a: 1,
    test: function() {
        console.log(this.a)
    }
}
obj.test() // 1

正常函数的this指向函数调用时的环境。

window.a = 2
const obj = {
    a: 1,
    test: () => {
        console.log(this.a)
    }
}
obj.test() // 2

箭头函数中的this就是一个普通变量,由于test()方法被调用时处于obj的环境且找不到this变量,会继续向上一级作用域也就是全局作用域查找,全局作用域中this就是window对象,因此最终打印结果为2。

3. 改变this指向

this随函数执行环境动态切换,虽然具有极强的灵活性,但也造成了this指向不明朗的困惑。在需要固定this指向的时候,可以通过以下三个定义在Function.prototype中的方法。

3.1 call()方法

基本用法为func.call(thisValue, arg1, arg2, ...)。其中,第一个参数是函数func执行时其内部this指向的作用域对象,剩余参数可选,为传入函数func的参数。
call()方法的一个重要的应用是调用对象的原生方法,例如将字符串转换为数组:

Array.prototype.slice.call('123') // ["1", "2", "3"]

以及将类数组对象转换为真正的数组:

Array.prototype.slice.call({0: 'a', 1: 'b', 2: 'c', length: 3}) // ["a", "b", "c"]

3.2 apply()方法

用法与call()类似,唯一的区别在于函数的参数以数组形式传入,即func.apply(thisValue, [arg1, arg2, ...])

3.3 bind()方法

call()apply()方法在被函数调用之后,会立即执行函数。但有时候我们不需要函数被立刻执行,而是需要返回一个新函数,可以使用bind()方法。看下面的例子:

const counter = {
    count: 0,
    inc: function() {
        this.count++
        console.log(this)
    }
}

function add(callback) {
    callback()
}

add(counter.inc) // Window {...}
counter.count // 0

counter对象的inc方法作为回调函数传入add函数,在调用add函数时,inc方法内部的this指向的是add方法被调用时所处的环境,也就是window对象。因此counter对象的count属性还是0。

此时可以使用bind()inc方法内部的this指向固定为counter对象:

add(counter.inc.bind(counter)) // {count: 1, inc: ƒ}
counter.count // 1

需要注意的是,函数没调用一次bind()方法,就返回一个新函数。如果这个新函数在多个地方被使用,应该提前将其缓存下来,而不是在每一个使用到的地方通过bind()生成。

你可能感兴趣的:(JavaScript学习笔记(五)this关键字)