JS this指向问题
在了解this指向之前,我们需要先知道:**函数在声明时,没有属于它自身的this,只有在函数调用时,this才有明确的指向。**因此,大多数情况下,我们只需要关注函数的调用位置,至于函数定义位置,有时候可能不那么重要。
一、this的默认绑定(优先级1)
①在script标签中,this 默认指向全局的 window
<script>
console.log({} === {}) // false 指针地址不同
console.log(this === window) // ture this 默认指向 window,指针地址相同
</script>
②函数独立调用,this 默认指向 window
<script>
function test() {
console.log(this)
}
test() // window 这里实际上可以理解为 window 在调用 test(),即 window.test()
</script>
二、隐式绑定(优先级2)
谁调用,this 指向谁
let age = 18;
let obj = {
age = 28,
test: function() {
console.log(this) // obj
function func() {
console.log(this) // window
}
func()
}
}
obj.test()
// 毫无疑问,obj.test()执行时,test函数中的this肯定是指向obj
// 而在obj.test()执行过程中,func函数被定义,这时func中的this,并无实际意义,只有在func()在被调用时,this才有了实际的意义,这时,并不是obj在调用它(并不是obj.func()),而是func()被独立调用,因此指向window
// 上例改造 1
let age = 18;
let obj = {
age = 28,
test: function() {
// 这里我们将func()返回出去,形成一个闭包
// 闭包:可以理解为,定义在函数内部的函数
return function func() {
console.log(this) // window
}
}
}
obj.test()() // 通过这种方式来调用,实际上仍然输出 window,因为obj.test()实际上就是 func
// 再次改造 2
let age = 18;
// 这次我们将函数定义在对象外部
function test() {
console.log(this)
}
let obj = {
age = 28,
test: test
}
obj.test() // 这里会输出什么呢? obj
// 由于obj中的test指向外部test()函数,因此我们可以直接把外部test()放到obj的test属性中,因此此处会输出 obj
let a = obj.test // 这里只存在赋值,并没有调用,obj.test ≠ obj.test()
a() // 这里的this又会输出什么呢? window
// 实际上,由于a指向obj.test,而obj.test又指向外部的test函数,所以a实际上指向的还是外部的test函数,因此 a() 相当于 test(),this指向 window
// 进阶改造 3
let age = 18;
function test() {
console.log(this)
}
// 定义一个函数,参数也为函数
function func(fn) {
fn(); // 独立调用
// 这里如果改写为 fn.call(obj) ,fn中的this就可以指向obj了
}
let obj = {
a: 2,
test: test
}
func(obj.test) // 这里会输出什么呢? window
// 由于obj.test指向外部test()函数,所以相当于将外部test()函数传给了func函数,而在func()函数中,test()被独立调用,因此指向window
三、显式绑定(call、apply、bind)(优先级3)
let a = 18
function test(a, b, c) {
console.log(a, b, c)
console.log(this)
}
let obj = {
a: 28,
test: test
}
let a = obj.test
// 简单看一下以下几种情况(this均指向obj)
obj.test(1, 2, 3)
a.call(obj, 1, 2, 3)
a.apply(obj, [1, 2, 3])
a.bind(obj)(1, 2, 3)
四、new 绑定(优先级4)
new过之后,this指向当前对象实例
function Person() {
this.a = 1;
this.test = function() {
console.log(this)
}
}
let p = new Person()
p.b = 2
p.test() // {a: 1, test: f, b: 2}
五、优先级问题
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
function test(b) {
this.a = b
}
let obj = {}
let func = test.bind(obj1)
func(2)
console.log(obj.2) // 由于bind将this指向obj1,所以此处输出 2
let o = new func(3)
console.log(obj.a) // 由于new将this指向 o,所以此处仍然输出 2
console.log(o.a) /// 3
六、箭头函数中的this
上面我们已经看过了非常多普通函数的this指向问题,那么箭头函数的this有哪些不一样呢?
箭头函数没有属于它自身的 this,它的this来自于外层作用域中的 this,会沿着作用域链一直往外层寻找 this。
let a = 0
function test() {
console.log(this)
function fn() {
console.log(this) // 上面我们已经知道,这里会输出 window
}
fn1()
let fn2 = () => {
console.log(this) // 如果我们使用箭头函数,这里将输出 obj对象
}
fn2()
}
let obj = {
a: 1,
test: test
}
obj.test()
// 上例改造:验证箭头函数的默认绑定
let a = 0
function test() {
console.log(this)
let fn2 = () => {
console.log(this)
}
return fn2() // 这里我们将 fn2 返回出去
}
let obj = {
a: 1,
test: test
}
obj.test()() // 这里是函数的独立调用,如果是普通函数,这里将输出 window,但 fn2 是箭头函数,这里将输出 obj对象
let o = test().call(obj) // 这里是函数的显式调用,如果是普通函数,这里this 将指向obj,但 test() 返回的是一个箭头函数 fn2(),这里将输出 window
由上例我们可以看出,箭头函数不会受到默认绑定规则(独立调用无效)和显式绑定的影响,它始终会沿着作用域链寻找可用 this。
// 再次改造:验证箭头函数的隐式绑定
let a = 0
let obj = {
a: 1,
test: () => {
console.log(this)
}
}
obj.test() // 这里是函数的隐式调用,如果是普通函数,这里将输出 obj对象,但 obj.test 是箭头函数,这里将往外层寻找可用 this,即 window
由上例我们可以看出,箭头函数不会受到隐式绑定规则的影响,它始终会沿着作用域链寻找可用 this。
// 再次改造:验证箭头函数的 new 绑定
let test() = () => {
console.log(this)
}
new test() // 报错: test is not a constructor
由上例我们可以看出,箭头函数不是构造函数,无法使用 new 关键字。
综上所述:
①普通函数中的四种 this 绑定规则对箭头函数均无效。
②箭头函数中的 this 始终来自于作用域链中的可用 this。