this绑定
前言
日常灵魂拷问,你能复述this绑定的四条基本原则吗?
this绑定规则
你是否常常因为不知道怎么判断this绑定到谁身上而懊悔;你是否常常因为this的调用出错而无语;你是否常常因为this的各种骚操作而不知所措。
只要你看了这篇文章,上面的问题都能迎刃而解。
默认绑定
最常用的函数调用类型:独立函数类型
思考以下代码:
function foo() {
console.log(this.a)
}
var a = 2
foo() // 2
为什么输出a呢?因为函数调用时应用了this的默认绑定,因此this指向全局对象。
因此可以得知:当函数直接使用不带任何修饰的函数引用进行调用时,只能使用默认绑定,无法应用其他规则。
当函数内部使用严格模式,则不能将全局对象用于默认绑定,this会绑定到undefined
隐式绑定
另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
思考下面代码:
function foo() {
console.log( this.a )
}
var obj = {
a: 2,
foo
}
obj.foo(); //2
当obj调用foo时,会使用obj上下文来引用函数。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因此调用foo()时this被绑定到obj,this.a和obj.a是一样的。
注意:对象熟悉引用链中只有上一层或者说最近一层在调用位置中起作用。如下:
function foo() {
console.log(this.a)
}
let obj1 = {
a: 1,
foo
}
let obj2 = {
a: 2,
obj1
}
obj2.obj1.foo()
隐式绑定丢失
隐式绑定有一个问题就是会丢失绑定对象,然后应用默认绑定,从而使this绑定到全局对象或者undefined上。
思考下面代码:
function foo() {
console.log( this.a )
}
var obj = {
a: 1,
foo
}
var bar = obj.foo;
var a = 2;
bar(); //2
这里bar虽然使obj.foo的引用,但是实际上引用的foo函数,因此bar() === foo(),所以应用了默认绑定。
参数传递也会隐式丢失。
function foo() {
console.log( this.a )
}
var obj = {
a: 1,
foo
}
var a = 2;
function doFoo(fn) {
fn()
}
doFoo(obj.foo); // 2
相当于doFoo内容进行以下赋值:
fn = obj.foo
fn()
所以也是默认绑定
显示绑定
JavaScript提供了call和apply方法能实现显示绑定。
它们的第一个参数是一个对象,接着在调用函数时将其绑定到this上,因此你可以直接指定this的绑定对象。
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
foo.call(obj); //1
硬绑定
硬绑定可以解决隐式丢失的问题,上码:
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
var bar = function() {
foo.call(obj);
}
在bar的内部,我们用call将obj绑定到了foo上,之后不管怎样调用bar,this都会指向obj。
因为硬绑定非常的实用,所以ES5提供内置方法Function.prototype.bind()。
示例:
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
var bar = foo.bind(obj)
bar() // 1
软绑定
硬绑定让this强制绑定到指定对象,但是会大大降低函数的灵活性,使用硬绑定后无法使用隐式绑定或者显示绑定来修改this(可以使用new),所以有了软绑定。但是没咋懂,大概如下:
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
var curried = [].slice.call(arguments, 1);
var bound = function() {
return fn.apply(
(!this || this === (window || global))? obj : this,
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
}
}
new绑定
首先,我们来了解,使用new来调用函数,会执行下面的操作:
- 创建一个全新的对象
- 这个新对象会被执行[[Prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
思考下面代码:
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a) //2
在用new调用foo时,会构造一个新对象并把它绑定到foo()调用中的this上。
绑定优先级排序
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
按照优先级可以更改函数的绑定
行百里者半九十