前言:终于找了一个早晨看书,把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变量了。
本文内容来源于《前端开发 核心知识进阶》侯策。