一文读懂JS中的this指向

96 阅读4分钟

一文读懂JS中的this指向

初识

在JS中this是一个非常重要的关键字,下面通过几个函数调用场景初步认识一下

function foo() {
    console.log(this.a)
}
var a = 1
foo() // 1


const obj = {
    a: 2,
    foo: foo
}

obj.foo() // 2


const c = new foo() // undefined
  • 首先我们需要知道在函数直接调用时,this指向全局对象。 所以当直接执行foo时this指向全局window,此时this.a = 1
  • 当函数作为一个对象的方法被调用时,此时this指向这个对象。 所以obj.foo(),打印this.a = obj.a = 2
  • 第三次调用是通过关键字new调用,我们知道通过new调用时,会返回一个新的对象,此时this指向的是这个新对象c,c中并没有a这个属性,因此this.a 就是undefined

其实当我们理解上述函数调用就可以解决大部分this指向问题,下面我们来看一下箭头函数中的this指向

箭头函数中的this指向

下面通过一个🌰,认识一下箭头函数中的this

function a() {
    return () => {
        return () => {
            console.log(this) // window
        }
    }
}

首先我们要知道因为箭头函数没有prototype,因此箭头函数本身是没有this的。箭头函数的this其实是继承于在定义的时候外层第一个普通函数的this。所以这个上述代码就不难看出这个this就是a的this指向window。 读到这里我们已经了解了JS基础的this指向,下面我们介绍一下bindcallapply三个方法

bind,apply,call方法修改this指向

注意:这三个方法不能修改箭头函数的this指向

bind方法

bind()方法,会接受多个参数:

  1. 第一个参数为this绑定的对象;
  2. 后续的参数为执行函数时传入的参数。

在执行完fn.bind()后,会返回一个新的函数,新函数除了this指向为执行bind方法时绑定的this,其他没有变化。

function foo() {
    console.log(this.a)
}
var a = 1
foo() // 1


const obj = {
    a: 2,
}

const resFoo = foo.bind(obj)
resFoo() // 2

手写bind

手写之前,我们需要知道bind在执行的时候都做了哪些事情

  • 通过链式调用,返回一个新参数
  • 接受多个参数,第一个为新的this指向,后续为传入的参数
  • 如果没有传参数,默认指向window
// 1. 第一步实现链式调用,将myBind定义在函数原型上
Function.prototype.myBind = function (context, ...args) {
    // 2. 判断是否为函数调用
    if (typeof this !== 'function') {
        throw TypeError('Error')
    }
    // 3. 判断有没有传参,没传则默认为window
    context = context || window
    context.fn = this
    // 4. 返回新的函数
    return function Fn(...args2) {
        const newArr = [...args, ...args2]
        // 5. 将fn作为context的属性执行,使fn的this指向context
        const result = context.fn(...newArrs)
        // 6. fn作为暂存属性使用完立即删除,避免被访问
        delete context.fn
        return result
    }
}

apply方法

经过函数.apply()会立即执行

apply()方法,接受两个参数

  1. 第一个参数为this绑定的对象
  2. 第二个参数为参数数组
function foo(...args) {
    console.log(this.a)
    console.log(args)
}
var a = 1
foo() // 1, []

const obj = {
    a: 2,
}

foo.apply(obj, [1, 2, 3]) // 2, [1, 2, 3]

手写apply

还是老步骤,在开始手写之前总结一下这个方法在调用过程中会做哪些事情

  1. apply会立即执行
  2. 接受两个参数,第一个参数为新的this指向,第二个参数为为函数接收的实参列表
Function.prototype.myApply = function (context, args) {
    if (typeof this !== 'function') {
        throw new TypeError("Error");
    }
    let res = null
    context = context || window
    context.fn = this
    res = context.fn(...args)
    delete context.fn
    // 因为函数会立即执行将res接受函数执行的返回值并作为apply的return返回
    return res
}

call方法

经过函数.call()会立即执行

call()方法,接受多个参数

  1. 第一个参数为this绑定的对象
  2. 后面的参数为执行函数时传入的参数
function foo(...args) {
    console.log(this.a)
    console.log(args)
}
var a = 1
foo() // 1, []

const obj = {
    a: 2,
}

foo.call(obj, 1, 2, 3) // 2, [1, 2, 3]

手写call方法

总结call方法的使用,发现和apply和bind有很多相似之处,话不多说直接上代码

Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError("Error");
    }
    let res = null
    context = context || window
    context.fn = this
    res = context.fn(...args)
    delete context.fn
    return res
}

总结一下

最后我们对本文知识点进行一下总结:

1. 如何判断this指向

  1. 函数调用: 当一个函数不是作为对象属性,而是作为函数来调用的时候,this指向全局对象
  2. 方法调用: 如果一个函数作为一个对象的方法来调用时,this 指向这个对象
  3. 构造器调用: 如果函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象
  4. apply,bind, call的调用: 三个方法第一个参数为this的指向,如果参数为空则默认指向window

2. this绑定的优先级

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

  • 显示绑定:apply,bind,call这些函数
  • 隐式绑定:对象将函数当作属性调用
  • 默认绑定:函数调用
  • 箭头函数的this一旦绑定,就不会被任何方式改变