在理解javascript的this之前,首先先瞭解一下作用域。 作用域分為兩種: 詞法作用域和動態作用域的區別是:詞法作用域是在寫代碼或定義時確定的;動態作用域是在運行時確定的。 this的綁定規則 this是在調用時被綁定,取決於函數的調用位置。由此可以知道,一般情況下(非嚴格模式下),this ...
在理解javascript的this之前,首先先瞭解一下作用域。
作用域分為兩種:
- 1、詞法作用域:引擎在當前作用域或者嵌套的子作用域查找具有名稱標識符的變數。(引擎如何查找和在哪查找。定義過程發生在代碼書寫階段)
- 2、動態作用域:在運行時被動態確定的作用域。
詞法作用域和動態作用域的區別是:詞法作用域是在寫代碼或定義時確定的;動態作用域是在運行時確定的。
this的綁定規則
this是在調用時被綁定,取決於函數的調用位置。由此可以知道,一般情況下(非嚴格模式下),this都會根據函數調用(調用棧)的上下文來綁定對象。
一、jQuery特效預設綁定
預設綁定:預設綁定是指在非嚴格模式下,且沒有使用別的綁定規則時,this根據函數調用(調用棧)的上下文來綁定對象(全局對象)。(嚴格模式下則綁定undefined)
舉個慄子:
?1 2 3 4 5 6 7 8 9 |
function foo() {
console.log( this .a);
};
function bar() {
var a = 3;
foo();
}
var a = 2;
bar(); //調用棧在全局作用域,this綁定全局對象
|
運行結果為: 2
//加上"use strict"運行結果則會變成this is undefined
這裡的函數調用時,使用了預設綁定,函數調用(調用棧)的上下文是全局作用域,因此this綁定了全局對象(global)。
eg2:
?1 2 3 4 5 6 7 8 |
function foo() {
console.log( this .a)
};
var a = 2;
( function () {
"use strict"
foo();
})();
|
運行結果為: 2
這裡需要註意:對於預設綁定,決定this綁定對象的不是調用位置是否處於嚴格模式,而是函數體是否處於嚴格模式(函數體處於嚴格模式則this綁定undefined;否則this綁定全局對象)。另外:嚴格模式和非嚴格模式雖然有可能可以綁定,但是最好不混用。
間接引用一般也是會應用預設綁定規則。
?1 2 3 4 5 6 7 8 |
function foo() {
console.log( this .a);
};
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); //3
(p.foo = o.foo)(); //2
|
賦值表達式 p.foo = o.foo的返回值是直接引用目標函數foo。
二、響應式圖片隱式綁定
隱式綁定:由上下文對象調用,綁定到上下文對象。
舉個慄子:
?1 2 3 4 5 6 7 8 9 |
function foo() {
console.log( this .a);
};
var obj = {
a: 2,
foo: foo
};
obj.foo(); //2
foo(); //undefined
|
這段幻燈片代碼中,foo()被當做引用屬性添加到obj對象中,obj調用這個引用屬性函數時,會使用該引用屬性上下文,this會被綁定到obj對象。(這個函數嚴格來說不屬於obj對象,只是作為引用屬性)。屬於隱式綁定。
而下麵foo()函數的直接執行,並不是obj對象引用,所以上下文對象是全局對象。故this綁定了undefined。屬於預設綁定。
對象引用鏈中只有上一層或者說最後一層在調用位置中起作用。
註意:
1.隱式綁定的函數會丟失綁定對象。此時它會應用預設綁定,將this綁定到全局對象或者undefined上,取決於是否是嚴格模式。
eg:
?1 2 3 4 5 6 7 8 9 10 |
function foo() {
console.log( this .a);
};
var obj = {
a: 2;
foo: foo
}
var bar = obj.foo;
var a = 'biubiubiu' ;
bar();
|
運行結果:"biubiubiu"
解析:看似bar是obj.foo的一個引用,實際上bar是直接引用了函數foo,是一個單純的函數調用,故實為預設綁定。
2.參數傳遞就是隱式賦值,因此傳入函數時也會被隱式賦值。
eg:
?1 2 3 4 5 6 7 8 9 10 11 12 |
function foo() {
console.log( this .a);
};
var obj = {
a: 2,
foo: foo
};
function bar(fn) {
fn();
};
var a = "biubiubiu" ;
bar(obj.foo);
|
運行結果: "biubiubiu"
解析:實際上參數也是隱式賦值,但是參數傳入函數中,併在函數中執行。此時也是直接引用了函數foo,因此也是單純的函數調用,採用了預設綁定。
3.把函數傳入語言內置函數。(與上面情況基本相似,將自己聲明函數改成語言內置函數)回調函數丟失this的情況比較常見,況且還有調用回調函數的函數可能還會修改this。
三、分頁插件顯式綁定
顯式綁定:直接將this綁定到指定對象上。Javascript中絕大多數函數和自己所創建的函數都可以使用這兩種顯式綁定的方法。
1、.call()
2、.apply()
這兩種綁定方法,第一個參數是this綁定的對象。(如果傳入的參數是原始值(字元串類型、布爾類型、數字類型),這個原始值就會被轉成對象形式(new String、new Boolean、new Number)這個稱為:裝箱)
舉個慄子:
?1 2 3 4 5 6 7 |
function foo() {
console.log( this .a);
};
var obj = {
a: 2
};
foo.call(obj);
|
運行結果: 2
然鵝,顯示綁定並不能解決綁定丟失的問題。這個時候來了一位新朋友 -- 硬綁定(bind)。
3、.bind() (硬綁定是常見場景,故es5提供了該內置方法 Function.prototype.bind。)
bind()會返回一個新編碼函數,把this綁定在指定參數上,並調用函數。
舉個慄子:
?1 2 3 4 5 6 7 8 9 10 |
function foo(e) {
console.log( this .a + e);
return this .a + e;
};
var obj = {
a: 2
}
var bar = foo.bind(obj); //新編碼函數
var b = bar(3); // 2 3
console.log(b); // 5
|
bind()還有一個功能:將除了第一個用於綁定this的參數之外的其他參數傳給下層的函數(部分應用,是“柯里化”的一種)。
這裡涉及到一個概念:把null或者undefined作為this的綁定對象傳入call、apply、bind,這些值在調用的時候會被忽略,實際應用預設綁定規則。
應用場景:
- 1、使用apply()展開一個數組,並作為參數傳遞給一個函數。
- 2、bind()對參數進行柯里化(預先設置一些參數)。
舉個慄子:
?1 2 3 4 5 6 7 8 |
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
|
解析:傳入一個參數作為this綁定對象,如果不傳則使用占位符(null),此時會使用預設綁定規則。
上面這個例子可能會產生一定的副作用,如果需要運用這種場景並且更加安全。可以創建一個空對象(可以用任意喜歡的名字來命名)。
?1 2 3 4 5 |
var ∅ = Object.create( null );
//上面這個例子就可以改寫為:
foo.apply(∅,[2,3]); //a:2,b:3
var bar = foo.bind(∅,2);
bar(3); //a:2,b:3
|
註意:硬綁定之後不能使用隱式綁定和顯式綁定對this進行修改
在這裡介紹一種軟綁定的方法softBind(),檢查this綁定到全局對象或者undefined後,綁定this到指定的預設對象。綁定後效果和硬綁定一樣,但是保留隱式綁定或者顯式綁定修改this的能力。
四、new綁定
Javascript中的new機制與面向類語言的完全不同。在Javascript中,構造函數只是一些使用new操作符時被調用的函數,不屬於一個類,也不會實例化一個類。稱為對函數的“構造調用”。
舉個慄子:
?1 2 3 4 5 |
function foo(a) {
this .a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
|
使用new的過程會創建一個全新的對象,this會綁定這個新對象。如果函數沒有返回其他對象,則new表達式函數調用會返回該新對象。(這個新對象會連接prototype)
四種綁定規則的優先順序為:new>顯式>隱式>預設
箭頭函數
箭頭函數是根據外層作用域(函數或全局)來決定this。(詞法作用域取代this機制)
箭頭函數this會綁定調用時的對象,且箭頭函數的綁定無法修改(new也不行)。
其實可以理解為,箭頭函數的this在詞法上繼承的是它所在的作用域(函數或全局)的this,而它繼承的函數作用域的this綁定的是在該函數調用上下文對象,所以箭頭函數的this間接的綁定在調用上下文對象。
簡述: 箭頭函數this(綁定作用域this)-- 作用域this(綁定在調用上下文對象)。
故:箭頭函數this == 調用的上下文對象
舉個慄子:
?1 2 3 4 5 6 7 8 |
function foo() {
setTimeout( function () {
//這裡的this在詞法上繼承自foo()
console.log( this .a);
},100);
};
var obj = { a: 2 };
foo.call(obj); //2
|
其實這個慄子也等價於:
?1 2 3 4 5 6 7 |
function foo() {
var that = this ; //lexical capture of this
setTimeout( function () {
console.log(self.a)
},100);
}
...與上面一樣
|
所以,有兩種風格:this風格(四種規則)和詞法作用域風格(that = this和箭頭函數)可供使用。使用時儘量避免混用,否則會造成難以維護的後果。