函数的this指向一直以来都是js的一个重难点,在读了一遍你不知道的js之后,对this又有了一些新的理解,在这里我就整理一下。
this的绑定规则分为4种:
1.默认绑定
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
上图就是一个很经典的默认绑定的例子,在全局下调用foo并且没有硬性更改this指向的时候,this会默认的指向了全局。这就是默认绑定。当然 在严格模式下这个写法会报错。
2.隐式绑定
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
无论你如何称呼这个模式,当 foo() 被调用时,它的前面确实加上了对 obj 的引用。当函 数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因 为调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。
但是有一个隐式绑定很常见的问题,就是在某些特定情况下会丢失绑定对象。从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
思考下面的代码:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 bar();
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。这种情况也很常见,在遇见的时候要注意。
3.显式绑定
如果我们不想在对象,内部包含函数引用,而想在某个对象上强制调用函数,可以使用函数的 call(..) 和 apply(..) 方法。
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
通过 foo.call(..),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者 new Number(..))。这通常被称为“装箱”。
除此之外还有一个bind方法。在这里就不具体介绍了,这就是显式绑定。
4.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 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。
this绑定的优先级
现在我们已经了解了函数调用中 this 绑定的四条规则,你需要做的就是找到函数的调用位 置并判断应当应用哪条规则。但是,如果某个调用位置可以应用多条规则该怎么办?为了 解决这个问题就必须给这些规则设定优先级,这就是我们接下来要介绍的内容。
先说结果:
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
默认绑定明显是最低级的,对它就不多做测试了,我们主要对前三个有争论的对象做一下测试。
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
可以看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。 现在我们需要搞清楚 new 绑定和隐式绑定的优先级谁高谁低:
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
可以看到 new 绑定比隐式绑定优先级高。但是 new 绑定和显式绑定谁的优先级更高呢? new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接 进行测试。但是我们可以bind()来测试它俩的优先级。
这个我们就不用举例子了,直接通过bind的源码来看,下面是MDN提供的一种bind()实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// 与 ECMAScript 5 最接近的
// 内部 IsCallable 函数
throw new TypeError("Function.prototype.bind - what is trying " +"to be bound is not callable");
}
var aArgs = Array.prototype.slice.call( arguments, 1 ), fToBind = this,
fNOP = function(){}, fBound = function(){
return fToBind.apply(
(this instanceof fNOP &&oThis ? this : oThis ),
aArgs.concat(Array.prototype.slice.call( arguments)
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
为了方便理解,提取里面的关键代码
this instanceof fNOP &&oThis ? this : oThis
// ... 以及:
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
显然可以看见,这段代码会判断硬绑定函数是否是被 new 调用,如果是的话就会使用新创建 的 this 替换硬绑定的 this。所以可以得出
new绑定优先于显式绑定。