一、this指向分析
- 函数在调用时,JavaScript会默认给this绑定一个值
- this的绑定和**定义的位置(编写的位置)**没有关系
- this的绑定和调用方式和调用的位置有关系
- this是在运行时绑定的
二、this绑定规则
2.1 默认绑定
- 普通的函数被独立调用
javascript
function foo() {
console.log('foo函数', this)
}
foo()
- 函数定义在对象中,但是独立调用
javascript
const obj = {
name: 'aklry',
bar: function() {
console.log('foo函数', this)
}
}
const baz = obj.bar
baz()
- 高阶函数
javascript
function test(fn) {
fn()
}
test(obj.bar)
以上三种情况均属于默认绑定,在非严格模式 下this指向window ,严格模式 下指向undefined。
2.2 隐式绑定
隐式绑定的this指向发起者,谁发起的函数执行,this就指向谁,与定义的位置无关。
javascript
function foo() {
console.log('foo函数', this)
}
const obj = {
bar: foo
}
obj.bar()
根据以上代码,foo函数虽然在全局声明,却由obj对象发起调用,因此this隐式绑定为obj对象。
2.3 new绑定
总所周知,ES5的函数拥有两种执行方式,一种是直接执行,另外一种是通过new 关键字来执行,那么通过new关键字执行函数的过程中发生了些什么呢?
- 创建一个空的对象
- 将空的对象赋值给this
- 执行函数体中的代码
- 将新的对象默认返回
因此通过new关键字来执行函数,this指向执行函数过程中创建的这个新的对象。
2.4 显式绑定
显式绑定通过函数执行的方式强制将this绑定给一个对象(非严格模式下),因此,先来介绍一下能够改变this指向的函数
- apply/call
apply和call都能够改变this指向,唯一的不同是两者的传参方式不同。
javascript
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指向。
javascript
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绑定规则优先级
- 显式绑定优先级高于隐式绑定
javascript
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绑定优先级高于隐式绑定
javascript
obj = {
name: 'aklry',
foo: function() {
console.log(this)
}
}
new obj.foo() // foo {}
- new绑定和显式绑定
(1)首先new不能和call/apply一起使用,new高于bind
javascript
const bindFn = foo.bind('aaa')
new bindFn() // foo {}
(2)bind高于apply/call
javascript
const bindFn = foo.bind('aaa')
bindFn.apply(123) // String('aaa')
三、this绑定规则之外的情况
- 显式绑定null/undefined,使用默认绑定规则
- 间接函数引用
javascript
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
javascript
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面试题
javascript
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();
javascript
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
javascript
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
javascript
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