一、基本概念
- 即不指向自身,也不指向词法作用域
- this 实际上是在函数被调用时发生绑定,它指向什么取决于函数在哪里被调用
例如:
// 全局作用域中直接调用
var name = 'hello world'
function foo() {
consoel.log(this.name)
}
foo() // 'hello world' this 指向window,因为函数foo()是在全局作用域下调用,相当于 window.foo()
// 作为方法调用
function foo() {
console.log(this.name)
}
var obj = {
name: '小明',
foo: foo
}
obj.foo() // '小明', 此时 this 指向了对象 obj,因为函数 foo() 是作为了对象 obj 的一个方法调用的
二、调用位置
在理解 this
的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)
看下面的代码:
function baz() {
// 当前调用栈:baz
// 函数调用位置:全局作用域
console.log('baz')
bar() // bar()的调用位置
}
function bar() {
// 当前调用栈:baz > bar
// 函数调用位置:baz的作用域中
console.log('bar')
foo() // foo()的调用位置
}
function foo() {
// 当前调用栈:baz > bar > foo
// 函数调用位置:bar的作用域中
console.log('foo')
}
baz() // baz的调用位置
三、绑定规则
3.1默认绑定
function foo() {
console.log(this.a)
}
var a = 2
foo() // 2
foo()
是直接在全局作用域下调用的,this 指向的是全局作用域,然后在全局作用域中有一个声明的变量 a, 因此 this.a
访问到了全局作用域中的变量 a
3.2隐式绑定
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this
绑定到则会个上下文对象,上面代码中,调用 foo()
时,this 被绑定到了对象obj,因此 this.a
和 obj.a
是一样的
对象属性引用链中只有最顶层或最后一层会影响调用位置,例如:
function foo() {
console.log(this.a)
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo() // 42
3.2.1 隐式丢失
一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局或者undefined
上,取决于是否是严格模式
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo // 函数别名
var a = 'hello world'
bar() // 'hello world'
虽然
bar
是obj.foo
的一个引用,但实际上,它引用的是foo
函数本身,因此此时的bar()
其实是一个不带任何修饰的函数调用,因此它应用了默认绑定。
传入回调函数时
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
参数传递其实也是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样
3.3 显示绑定
使用 call()、apply()、bind()
都可以显示的绑定this
,看下面代码:
function foo() {
console.log(this.a)
}
var obj = { a:2 }
foo.call( obj ) // 2
- 通过
foo.call(...)
我们可以在调用foo时强制把this指向Obj - 如果传入了一个原始值(
string、number、bolean
)来当作this的绑定对象,这个原始值会被转化为对象形式。通常称为“装箱”
3.3.1 硬绑定
使用call、apply
function foo() {
console.log(this.a)
}
var obj = {
a: 2
}
var bar = function() {
foo.call(obj)
}
bar() // 2
setTimeout(bar, 100) // 2
bar.call(window) // 2
使用bind()函数绑定this
function foo(something) {
console.log(this.a, something)
return this.a + something
}
var obj = {
a: 2
}
var bar = foo.bind(obj)
bar(3)
3.3.2 new绑定
使用new操作符调用构造函数会经历下面4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋值给新对象(this指向这个新对象)
- 执行构造函数中的代码(为新对象添加属性和方法)
- 返回这个新对象
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2
使用 new
来调用foo()
时,foo()
函数中的 this 会绑定到 bar
对象上,因此,bar.a
实际上就是函数 foo()
里面的 this.a
四、绑定优先级
隐式绑定和显示绑定的优先级哪个更高?
function foo() {
console.log(this.a)
}
let obj1 = {
a: 2,
foo: foo
}
let obj2 = {
a: 3,
foo: foo
}
obj1.foo.call(obj2) // 3
obj2.foo.call(obj1) // 2
上面代码中可以看出,显示绑定的优先级更高
五、小结
如果要判断一个运行中的和桉树的
this
绑定,需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面四条规则来判断this
的绑定对象
- 由
new
调用?绑定到新创建的对象 - 由call或者apply(或者bind)调用?绑定到指定的对象
- 由上下文对象调用?绑定那个上下文对象
- 默认:在严格模式下绑定到
undefined
,否则绑定到全局对象