理解this綁定優先級的前提是理解this的綁定規則,理解綁定規則以前,咱們先來了解一下函數"調用位置"。編程
一般來講,要想找到調用位置,最重要的是分析調用棧(在有的編程模式下,真正的調用位置可能被隱藏起來了,經過調用棧來分析調用位置最爲直接)。bash
來個梨子:app
function baz() {
// 當前調用棧是:baz
// 調用位置:全局做用域
console.log('baz');
bar();
}
function bar() {
// 當前調用棧是:baz -> bar
// 調用位置:baz中
console.log('bar');
foo();
}
function bar() {
// 當前調用棧是:baz -> bar -> foo
// 調用位置:bar中
console.log('foo');
}
baz(); // 全局調用
複製代碼
如咱們在梨子中標註的同樣,你能夠把調用棧理解成一個函數鏈式調用。其實咱們有一種更爲簡單的方式查找調用棧,那就是JavaScript開發者工具。如圖。函數
接下來咱們就看看函數在運行的過程當中調用位置如何決定this的綁定對象。工具
var number = 1;
function baz() {
console.log(this.number);
}
baz(); // 1
複製代碼
當函數baz被調用時,this.number被解析成全局變量number。函數在調用時,進行默認綁定,此時的this指向全局對象(非嚴格模式),嚴格模式下this爲undefined。ui
var number = 1;
function baz() {
"use strict"
console.log(this.number);
}
baz();
複製代碼
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
object.baz(); // 1
複製代碼
函數baz()的聲明方式,嚴格來講是不屬於object對象的,可是調用位置會使用object上下文來引用函數。因此咱們能夠說object對象"擁有"或者"包含"baz()函數的引用。this
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
var bar = object.baz();
var number = 2;
bar(); // 2
複製代碼
雖然bar是object.baz的一個引用,可是它是引用foo函數自己,因應用了默認綁定。this指向全局變量。spa
function baz() {
console.log(this.number);
}
function loadBaz(fn){
// fn其實就是引用的baz
fn(); // 回調函數的調用位置
}
var object = {
number: 1,
baz: baz
};
var number = 2;
loadBaz(object.baz); // 2
複製代碼
參數傳遞其實也是一種隱式的賦值,所以咱們在傳入函數時也會被隱藏賦值,因此,梨子2和梨子1是同樣的結果。prototype
function baz() {
console.log(this.number);
}
var object = {
number: 1,
baz: baz
};
baz.call(object); // 1
// 或者baz.apply(object); // 1
複製代碼
使用new來調用函數,或者說發生構造函數調用時,會執行下面的操做。code
function baz(number) {
this.number = number;
}
var bar = new baz(1);
console.log(bar.a); // 1
複製代碼
使用new來調用baz()時,咱們會構造一個新的對象並綁定到baz()調用中的this上。
前面簡單講解了this綁定的四條規則,你須要作的就是找到調用位置,判斷使用那一條規則。可是,有時候,在一個調用位置可能使用了多條規則,應該若是判斷了。這裏就須要判斷規則的優先級(如CSS的權重同樣)。
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
複製代碼
function foo(a) {
this.a = a;
}
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
複製代碼
function foo(a) {
this.a = a;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
複製代碼
what?出乎意料呀。bar被綁定到obj1上,可是new bar(3) 並無像咱們預計的那樣把obj1.a修改成3相反,new修改了綁定調用bar()中的this。那到底顯示綁定和new綁定誰的優先級高?
咱們來看看ES5內置的Function.prototype.bind()(顯示綁定-強綁定)的實現。
MDN:Function.prototype.bind()的實現
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
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() {
// this instanceof fBound === true時,說明返回的fBound被當作new的構造函數調用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 獲取調用時(fBound)的傳參.bind 返回的函數入參每每是這麼傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護原型關係
if (this.prototype) {
// 當執行Function.prototype.bind()時, this爲Function.prototype
// this.prototype(即Function.prototype.prototype)爲undefined
fNOP.prototype = this.prototype;
}
// 下行的代碼使fBound.prototype是fNOP的實例,所以
// 返回的fBound若做爲new的構造函數,new生成的新對象做爲this傳入fBound,新對象的__proto__就是fNOP的實例
fBound.prototype = new fNOP();
return fBound;
};
}
複製代碼
在這段代碼中,會判斷綁定函數是否被new調用,
。
。
。 之因此要在new中綁定函數,緣由是預先設置函數的一些參數,這樣在使用時,只須要傳入剩餘的參數。
根據上面的梨子:總結一下:
new綁定優先級 > 顯示綁定優先級 > 隱式綁定優先級 > 默認綁定優先級
但願這個文章能對閱讀的你有所幫助。讓咱們一塊兒成長吧。謝謝!
參考:《你不知道的JavaScript》