【Javascript】This指向问题

前言:终于找了一个早晨看书,把this搞了个透彻!旧文总结的太乱了,都是这里学一点,那里看个视频的,学习基础知识还是得买书!
旧的链接:https://blog.csdn.net/qs52955339/article/details/115759345?spm=1001.2014.3001.5501
留着做个纪念吧。

this到底指向谁

this要从多角度理解:全局环境下的this,箭头函数中的this,构造函数中的this,this的显隐性和优先级等。

  • 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的this会被绑定到Undefined上,在非严格模式下则会被绑定到全局对象window/global上。
  • 一般使用new方法调用构造函数时,构造函数内的this会被绑定到新创建的对象上。
  • 一般通过call/apply/bind方法显示调用函数时,函数体内的this会被绑定到指定参数的对象上。
  • 一般通过上下文对象调用函数时,函数体内的this会被绑定到该对象上。
  • 在箭头函数中,this的指向是由外层(函数或全局)作用域来决定的。
全局环境中的this
function f1() {
    console.log(this)
}

function f2() {
    'use strict'
    console.log(this)
}

f1() // window
f2() // undefined
const foo = {
    bar: 10,
    fn: function () {
        console.log(this)
        console.log(this.bar)
    }
}
foo.fn() // {bar:10, fn: f}  10
//注:在执行函数时不考虑显式绑定,如果函数中的this是被上一级的对象所调用,那么this指向的就是上一级的对象;否则指向全局环境

let fn1 = foo.fn
fn1() // window  undefined
// 注:赋值给fn1后,fn1仍然是在window的全局环境中执行的
上下文对象调用中的this
const student = {
    name: 'Lucas',
    fn: function () {
        return this
    }
}

console.log(student.fn() === student) // true
const person = {
    name: 'Lucas',
    brother: {
        name: 'Mike',
        fn: function () {
            return this.name
        }
    }
}

console.log(person.brother.fn())  // Mike
// this会指向最后调用它的对象
const o1 = {
    text: 'o1',
    fn: function () {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: function () {
        return o1.fn()
    }
}
const o3 = {
    text: 'o3',
    fn: function () {
        let fn = o1.fn
        return fn()
    }
}
console.log(o1.fn())  // o1
console.log(o2.fn())  // o1
console.log(o3.fn())  // undefined
// o3.fn()通过赋值进行了“裸奔调用”,因此这里的this指向了window

如果需要console.log(o2.fn()) 输出o2,不使用bind、call、apply方法,那我们可以这样改造一下:

const o1 = {
    text: 'o1',
    fn: function () {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: o1.fn
}
console.log(o2.fn())  // o2
//提前赋值操作,将函数fn挂载到o2对象上,fn作为o2对象的方法被调用
通过bind、call、apply改变this指向

它们都是用来改变相关函数this指向的,但是call和apply都是直接进行相关函数调用的,它俩的区别主要体现在参数设定上。bind不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的this指向,可以手动调用它。

用代码来总结,以下3段代码是等价的:

const target = {}
fn.call(target, 'arg1', 'arg2')

const target = {}
fn.call(target, ['arg1', 'arg2'])

const target = {}
fn.call(target, 'arg1', 'arg2')()
const foo = {
    name: 'Lucas',
    logName: function () {
        console.log(this.name)
    }
}
const bar = {
    name: 'Mike'
}
console.log(foo.logName.call(bar))
//执行结果为Mike

注:对call、apply、bind的高级考察一般结合构造函数及组合来实现继承。

构造函数和this
function Foo() {
    this.bar = 'Lucas'
}
const instance = new Foo()
console.log(instance.bar) //Lucas

new操作符调用构造函数时具体做了什么?以下答案为简版

  • 创建一个新对象
  • 将构造函数的this指向这个新的对象
  • 为这个对象添加属性、方法等
  • 返回新的对象

用代码可以这样表述:

let obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)

需要指出的是,如果在构造函数中出现了显式return的情况,那么可以分为以下两种场景:

场景1:执行以下代码将输出undefined,此时instance返回的是空对象o

function Foo() {
    this.user = 'Lucas'
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.user) //undefined

场景2:执行以下代码将输出Lucas,instance此时返回的是目标对象实例this

function Foo() {
    this.user = 'Lucas'
    return 1
}
const instance = new Foo()
console.log(instance.user) // Lucas

所以,如果构造函数中显式返回一个值,且返回的是一个对象(返回复杂类型),那么this指向的就是返回的这个对象;如果返回的不是一个对象(返回基本类型),那么this仍然指向实例。

箭头函数中的this

箭头函数中的this指向是由其所属函数或全局作用域决定的。

在这段代码中,this出现在setTimeut()的匿名函数中,因此this指向window对象。

const foo = {
    fn: function () {
        setTimeout(function () {
            console.log(this)
        })
    }
}
console.log(foo.fn())

如果需要让this指向foo这个对象,可以改用箭头函数来解决:

const foo = {
    fn: function () {
        setTimeout(() => {
            console.log(this)
        })
    }
}
console.log(foo.fn())
this优先级

我们把call、apply、bind、new对this进行绑定的情况称为显式绑定,而把根据调用关系确定this指向的情况称为隐式绑定,现在我们来讨论谁的优先级更高。

function foo(a) {
    console.log(this.a)
}
const obj1 = {
    a: 1,
    foo: foo
}
const obj2 = {
    a: 2,
    foo: foo
}
obj1.foo.call(obj2) //2
obj2.foo.call(obj1) //1

从上面这段代码可以看出,call、apply这种显式绑定一般来说优先级更高。

function foo(a) {
    this.a = a
}
const obj1 = {}

let bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) //2
// 通过bind将bar函数中的this绑定为obj1后,执行bar(2),bij1.a值为2
let baz = new bar(3)
console.log(baz.a) //3

bar函数本身是通过bind方法构造的函数,其内部已经将this绑定为obj1,当它再次作为构造函数通过new被调用时,返回的实例就已经与obj1解绑了。也就是说,new绑定修改了bind绑定中的this指向,因此new绑定的优先级比显式bind绑定的更高。

function foo() {
    return a => {
        console.log(this.a)
    }
}
const obj1 = {
    a: 2
}
const obj2 = {
    a: 3
}
const bar = foo.call(obj1)
console.log(bar.call(obj2))
//输出为2

由于foo中的this绑定到了obj1上,所以bar(引用箭头函数中)的this也会绑定到obj1上,箭头函数的绑定无法被修改。

let a = 123
const foo = () => a => {
    console.log(this.a)
}
const obj1 = {
    a: 2
}
const obj2 = {
    a: 3
}
let bar = foo.call(obj1)
console.log(bar.call(obj2))
// 将foo完全写成这样的箭头函数,则会输出123

现在我们将第一处变量a的声明修改一下:

const a = 123
const foo = () => a => {
    console.log(this.a)
}
const obj1 = {
    a: 2
}
const obj2 = {
    a: 3
}
let bar = foo.call(obj1)
console.log(bar.call(obj2))

答案为undefined,因为使用const声明的变量不会挂载到window全局对象上,因此,当this指向window,自然就找不到a变量了。

本文内容来源于《前端开发 核心知识进阶》侯策。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值