思考:javascript
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // ReferenceError: a is not defined ?
複製代碼
this 其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。java
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包 含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的 其中一個屬性,會在函數執行的過程當中用到。數組
這裏要說的
call,apply,bind
都是來改變this
的指向的瀏覽器
call,apply能夠屢次改變綁定對象;只是apply接受數組格式參數;緩存
bind()方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,傳入 bind() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。bash
bind 是返回對應函數,便於稍後調用;apply 、call 則是當即調用 。app
bind 這些改變上下文的 API 了,對於這些函數來講,this 取決於第一個參數,若是第一個參數爲空,那麼就是 window。函數
優先級: 構造綁定>顯示綁定>隱式綁定>默認的 this 綁定 post
bind只能被綁定一次;以第一次爲準;優化
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
foo.bind(obj).call(obj2) // name: obj
foo.bind(obj).bind(obj2)() // name: obj
複製代碼
bind 內部就是 包了一個apply;等到調用的時候再執行這個包含apply的函; 實際上,ES5 中內置的 Function.prototype.bind(..) 更加複雜。下面是 MDN 提供的一種bind(..) 實現:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
//一個函數去調用,也就是說bind,call,apply的this是個函數;
//而後再去改變這個函數裏面的this;
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 指向要bind的函數;
fToBind = this,
// 返回一個新函數
fNOP = function(){},
fBound = function(){
//fToBind.apply 改變綁定this;
// 執行的時候判斷,當前this等於fNOP而且傳入oThis,就設置成當前this,否則就改變成初始化傳入的oThis;
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 )
這段代碼請看 javascript 深刻解剖bind內部機制
硬綁定這種方式能夠把 this 強制綁定到指定的對象(除了使用 new 時),防止函數調用應用默認綁定規則。問題在於,硬綁定會大大下降函數的靈活性,使用硬綁定以後就沒法使用隱式綁定或者顯式綁定來修改 this。
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this; // 捕獲全部 curried 參數
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;
};
}
複製代碼
它會對指定的函 數進行封裝,首先檢查調用時的 this,若是 this 綁定到全局對象或者 undefined,那就把 指定的默認對象 obj 綁定到 this,不然不會修改 this。此外,這段代碼還支持可選的柯里化;
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 );// name: obj <---- 應用了軟綁定
複製代碼
若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值 在調用時會被忽略,實際應用的是 默認綁定規則:
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把數組「展開」成參數
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 進行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
複製代碼
用 null 來忽略 this 綁定可能會有反作用。若是某個函數確實使用了 this(好比第三方庫中的一個函數),那默認綁定規則會把 this 綁定到全局對象(在瀏覽 器中這個對象是 window),這將致使不可預計的後果(好比修改全局對象)。
優化:用
Object.create(null)
替代null
或者undefined