一篇文章搞定this指向问题

一、this指向分析

  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定和定义的位置(编写的位置)没有关系
  3. this的绑定和调用方式和调用的位置有关系
  4. this是在运行时绑定的

二、this绑定规则

2.1 默认绑定

  • 普通的函数被独立调用
function foo() {
	console.log('foo函数', this)
}
foo()
  • 函数定义在对象中,但是独立调用
const obj = {
	name: 'aklry',
	bar: function() {
		console.log('foo函数', this)
	}
}
const baz = obj.bar
baz()
  • 高阶函数
function test(fn) {
	fn()
}
test(obj.bar)

以上三种情况均属于默认绑定,在非严格模式下this指向window严格模式下指向undefined。

2.2 隐式绑定

隐式绑定的this指向发起者,谁发起的函数执行,this就指向谁,与定义的位置无关。

function foo() {
	console.log('foo函数', this)
}
const obj = {
	bar: foo
}
obj.bar()

根据以上代码,foo函数虽然在全局声明,却由obj对象发起调用,因此this隐式绑定为obj对象。

2.3 new绑定

总所周知,ES5的函数拥有两种执行方式,一种是直接执行,另外一种是通过new关键字来执行,那么通过new关键字执行函数的过程中发生了些什么呢?

  1. 创建一个空的对象
  2. 将空的对象赋值给this
  3. 执行函数体中的代码
  4. 将新的对象默认返回

因此通过new关键字来执行函数,this指向执行函数过程中创建的这个新的对象。

2.4 显式绑定

显式绑定通过函数执行的方式强制将this绑定给一个对象(非严格模式下),因此,先来介绍一下能够改变this指向的函数

  • apply/call

apply和call都能够改变this指向,唯一的不同是两者的传参方式不同。

function foo(name, height, age) {
    console.log('foo函数', this)
    console.log('打印参数', name, height, age)
}
// 一般调用
foo('aklry', 1.70, 20)
// apply调用
foo.apply(123, [1.70, 20])
// call调用
foo.call(123, 1.70, 20)
  • bind

bind的效果与apply/call类似,都能够改变this指向,不同点就是bind返回了一个新函数,这个新函数的指向就是我们更改之后的this指向。

function foo(name, height, age) {
    console.log('foo函数', this)
    console.log('打印参数', name, height, age)
}
const baz = foo.bind(123, 'aklry', 1.70)
baz(20)

bind使用时的传参方式与call类似,如果我们的参数没有传完整,并且在执行新函数时传入参数,那么在执行新函数时传入的参数会默认赋值给剩余的参数。

除此之外,在非严格模式下apply/call、apply绑定的this指向是一个对象,如果传入基本数据类型,则浏览器会将其转换为包装类对象。

2.5 this绑定规则优先级

  • 显式绑定优先级高于隐式绑定
function foo() {
	console.log('foo函数', this)
}
let obj = { name: 'aklry', foo: foo }
obj.foo.apply(123) // Number(123)

const bar = foo.bind('aaa')
obj = {
	name: 'aklry',
	baz: bar
}
obj.baz() // String('aaa')
  • new绑定优先级高于隐式绑定
obj = {
	name: 'aklry',
	foo: function() {
		console.log(this)
	}
}
new obj.foo() // foo {}
  • new绑定和显式绑定

(1)首先new不能和call/apply一起使用,new高于bind

const bindFn = foo.bind('aaa')
new bindFn() // foo {}

(2)bind高于apply/call

const bindFn = foo.bind('aaa')
bindFn.apply(123) // String('aaa')

三、this绑定规则之外的情况

  • 显式绑定null/undefined,使用默认绑定规则
  • 间接函数引用
const obj1 = {
	name: 'obj1',
	foo: function() {
		console.log('obj1', this)
	}
}
const obj2 = { name: 'obj2' }
(obj2.foo = obj1.foo)() // 左侧赋值表达式返回foo这个函数,因此这种写法是独立函数调用 -> 默认绑定
  • 箭头函数

(1)普通函数中默认有this标识符,而箭头函数默认不绑定this

(2)箭头函数没有绑定this,因此使用箭头函数时,如果使用this,那么会逐层作用域查找this

const obj = {
	name: 'obj',
	foo: function() {
		const bar = () => {
			console.log('bar', this) // obj
		}
		return bar
	}
}
const baz = obj.foo()
baz()

根据以上代码,obj.foo()执行返回bar这个箭头函数,由于箭头函数没有this,因此会逐层作用域查找,箭头函数的上层作用域为foo函数,而foo函数的this取决于调用者,根据代码,foo函数由obj对象调用,因此this指向obj对象,所以箭头函数捕获上层作用的this即obj对象。

四、this面试题

var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); // 独立函数调用 -> 默认绑定 -> this(window) -> window
  person.sayName(); // 隐式绑定 -> this(person) -> person
  (person.sayName)(); // 隐式绑定 -> this(person) -> person
  (b = person.sayName)(); // 间接函数引用 -> 默认绑定 -> this(window) -> window
}
sayName();
var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); // 隐式绑定 -> this(person) -> person1
person1.foo1.call(person2); // 隐式绑定 -> this(person) -> person2

person1.foo2(); // 箭头函数 -> 作用域查找 -> this(window) -> window
person1.foo2.call(person2); // 箭头函数 -> 作用域查找 -> this(window) -> window

person1.foo3()(); // 独立函数调用 -> 默认绑定 -> this(window) -> window
person1.foo3.call(person2)(); // 独立函数调用 -> 默认绑定 -> this(window) -> window
person1.foo3().call(person2); // 显式绑定 -> this(person2) -> person2

person1.foo4()(); // 箭头函数 -> 作用域查找 -> this(person1) -> person1
person1.foo4.call(person2)(); // 箭头函数 -> 作用域查找 -> 显式绑定 -> this(person2) -> person1
person1.foo4().call(person2); // 箭头函数 -> 作用域查找 -> this(person1) -> person1
var name = 'window'
function Person(name) {
    this.name = name
    this.foo1 = function () {
        console.log(this.name)
    }
    this.foo2 = () => console.log(this.name)
    this.foo3 = function () {
        return function () {
            console.log(this.name)
        }
    }
    this.foo4 = function () {
        return () => {
            console.log(this.name)
        }
    }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1
person1.foo1.call(person2) // person2

person1.foo2() //person1
person1.foo2.call(person2) // person1

person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2

person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
var name = 'window'
function Person(name) {
    this.name = name
    this.obj = {
        name: 'obj',
        foo1: function () {
            return function () {
                console.log(this.name)
            }
        },
        foo2: function () {
            return () => {
                console.log(this.name)
            }
        }
    }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aklry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值