this机制是js中很重要的知识点,它和作用域查询是两个容易混淆的知识点。本文将详细介绍this的4种绑定规则。
默认绑定
- 全局环境中,this指向window。
- 独立函数调用时,this指向window。严格模式下指向undefined。
- 作为对象的方法调用时,this指向原对象
// 示例1
console.log(window === this) // true
// 示例2
function foo() {
console.log(window === this) // true
}
foo()
// 示例3
var obj = {
foo: function() {
console.log(this === obj) // true
// 注意,这里是独立调用
function foo2() {
console.log(this === window) // true
}
foo2()
}
}
obj.foo()
隐式绑定
被直接对象所包含的函数调用时,this隐式绑定到该直接对象。
function foo() {
console.log(this.a)
}
var o1 = {
a: 1,
foo: foo,
o2: {
a: 2,
foo: foo
}
}
// foo()函数的直接对象是o1,this隐式绑定到o1
console.log(o1.foo()) // 1
// foo()函数的直接对象是o2,this隐式绑定到o2
console.log(o1.o2.foo()) // 2
隐式丢失
隐式丢失是指被隐式绑定的函数丢失绑定对象,进而默认绑定到window对象。
函数别名
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
var fn = obj.foo;
fn() // 0
示例中造成隐式丢失的原因是在赋值时,只把foo()函数赋给了fn,而fn与obj对象之间没有任何联系。
// 等价于
var a = 0;
var fn = function foo(){
console.log(this.a)
}
fn() // 0
参数传递
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
function foo2(fn) {
fn()
}
foo2(obj.foo) // 0
和上面的原因类似,把函数作为参数传递给foo2时,只是把foo函数赋给了fn,fn和obj之间没有联系。
// 等价于
var a = 0;
function foo2(fn) {
fn()
}
foo2(function foo() {
console.log(this.a)
})
内置函数
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
setTimeout(obj.foo, 100) // 0
内置函数也会造成隐式丢失,原因同上。
// 相当于
var a = 0;
setTimeout(function foo() {
console.log(this.a)
}, 100)
间接引用
var a = 0;
function foo() {
console.log(this.a)
}
var o1 = {a: 1, foo:foo}
var o2 = {a: 2}
// 将o1.foo函数赋值给o2.foo函数后再调用,相当于立即调用foo函数
;(o2.foo = o1.foo)() // 0
var a = 0;
function foo() {
console.log(this.a)
}
var o1 = {a: 1, foo:foo}
var o2 = {a: 2}
o2.foo = o1.foo
// 将o1.foo函数赋值给o2.foo函数后再调用,是作为o2对象的方法调用
o2.foo() // 2
在JavaScript引擎内部,对象o1和方法o1.foo存储在两个不同的内存地址,所以只有o1.foo()这样调用时,this指向o1。
显示绑定
通过bind()、apply()、bind()方法把this绑定到对象上,叫做显示绑定。对于被调用的函数来说叫做间接调用。
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a:1
}
foo.call(obj) // 1
非严格模式下null或undefined值会被转换为全局对象,严格模式下this值是指定值。
// 非严格模式
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a:1
}
foo.call(null) // 0
// 严格模式
'use strict'
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a:1
}
foo.call(null) // Uncaught TypeError
硬绑定
硬绑定是显示绑定的一种,使this不能再修改。
var a = 0;
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
var foo2 = function() {
foo.call(obj)
}
foo2() // 1
setTimeout(foo2, 100) // 1
foo2.call(window) // 1
new绑定
当函数作为构造函数调用时,函数中的this绑定被称作new绑定。
var obj = {
a: 1
}
function foo() {
this.a = 2
}
var test = new foo()
console.log(test) // {a: 2}
当使用构造函数方式调用对象的方法时,this不再指向原始对象。
var o = {
m: function(){
console.log(this === o) // false
console.log(this === obj) // false
}
}
var obj = new o.m();
优先级
this的四种绑定机制,如果同时存在两种以上的绑定规则,它们会遵循下面的绑定顺序:
- 是否是new绑定?如果是,this绑定的是新创建的对象
var fn = new foo();
- 是否是显式绑定?如果是,this绑定的是指定的对象
var fn = foo.call(obj2);
- 是否是隐式绑定?如果是,this绑定的是属于的对象
var fn = obj1.foo();
- 如果都不是,则使用默认绑定
var fn = foo();
结语
this的四种绑定规则:默认绑定、隐式绑定、显式绑定和new绑定,分别对应函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。
日常开发中很多的错误都是由于this的隐式丢失造成的,平常多观察多思考可以减少错误的发生。