參考:阮一峯《javascript的this用法》及《JS中this關鍵字詳解》javascript
this是Javascript語言的一個關鍵字它表明函數運行時,自動生成的一個內部對象,只能在函數內部使用。定義:this是包含它的函數做爲方法被調用時所屬的對象。總原則,this指的是,調用函數的那個對象,this
永遠指向函數運行時所在的對象!而非建立時的。
html
如下是基於瀏覽器環境作的測試:java
做爲函數調用: 程序員
function $(){
this.count = 1;
return this;
}
window.onload = function(){
console.info($());
}
控制檯返回結果以下:編程
一個window對象。segmentfault
對於內部函數,即聲明在另一個函數體內的函數,這種綁定到全局對象的方式會產生一個問題,即它會隱式聲明全局變量。代碼以下:數組
var point = {
x: 0,
y: 0,
moveTo: function(x, y) {
var fn1 = function(x) {
this.x = this.x + x;
return this.x;
};
var fn2 = function(y) {
this.y = this.y + y;
};
return fn1();
}
}
console.log(point.moveTo());
結果是:瀏覽器
而若將fn1中return的值改成this的話,打印結果:app
一個window全局對象。這屬於 JavaScript 的設計缺陷,正確的設計方式是內部函數的 this 應該綁定到其外層函數對應的對象上,這個設計錯誤錯誤的後果是方法不能利用內部函數來幫助它工做,由於內部函數的this被綁定了錯誤的值,因此不能共享該方法對對象的訪問權。爲了規避這一設計缺陷,聰明的 JavaScript 程序員想出了變量替代的方法,約定俗成,該變量通常被命名爲 that。以下:框架
var point = {
x: 0,
y: 0,
moveTo: function(x, y) {
var that = this;
var x = x;
var y = y;
var fn1 = function(x) {
that.x = that.x + x;
return that;
};
var fn2 = function(y) {
that.y = that.y + y;
};
return fn1(x);
}
}
console.log(point.moveTo(1,1));
返回結果:
函數調用中,this老是指向函數的直接調用者(而非間接調用者)。若是在嚴格模式下,有可能輸出undefined,嚴格模式下,禁止this關鍵字指向全局對象。以下:
'use strict'; console.log(this === window); // true var foo = function() { console.log(this === window); console.log('this:',this); }; foo(); window.foo();
控制檯打印結果是:
使用that的另外一個例子:
var myObj = { value: 0, increment: function(inc) { this.value += typeof inc === 'number' ? inc: 1; } } myObj.increment(); document.writeln(myObj.value); myObj.increment(2); document.writeln(myObj.value); function add(num1, num2){ return num1 + num2; } console.log(add(40,27)); myObj.double = function() { var that = this; var helper = function() { that.value = add(that.value, that.value); } helper(); } myObj.getValue = function() { var that = this; return that.value; } myObj.double(); document.writeln(myObj.getValue());
做爲對象方法調用:
若是一個調用表達式包含一個屬性存取表達式(即一個.點表達式或者[subscript]下標表達式),那麼它被當作一個方法來調用。另外一段代碼:
var o = {};
o.fn = $;
function $(){
console.log(this);
}
window.onload = function(){
o.fn();
}
控制檯返回結果以下:
調用函數的o對象。
在事件中,this指向觸發這個事件的對象,特殊的是,IE中的attachEvent中的this老是指向全局對象Window;
另一種嵌套調用:
var personA={
name:"xl",
showName:function(){
console.log(this.name);
}
}
var personB={
name:"XL",
sayName:personA.showName
}
personB.sayName();
控制檯輸出結果:
從這裏很明顯的看出,在js中this關鍵字指向是直接調用它的對象,而非間接的。
做爲函數及對象方法的混合調用:
var myObject={
foo : "",
func : function(){
var self = this;
console.log("outer func : this.foo = " + this.foo );
console.log("outer func : self.foo = " + self.foo );
(function(){
console.log("inner func : this.foo = " + this.foo );
console.log("inner func : self.foo = " + self.foo );
}());
}
}
myObject.func();
輸出結果以下:
證實函數內部函數的調用是由全局對象引起的,這在上面有所闡述。
將構造函數做爲函數調用:
function Person(name) {
this.name = name;
}
var personA = Person("xl");
// console.log(personA.name);
console.log(window.name);
console.log(name);
var personB = new Person("xl");
console.log(personB.name);
控制檯打印結果:
註釋掉的console由於那麼已經成爲全局對象的屬性,所以打印爲未定義變量報錯。第二個顯式調用,第三個隱式調用。最後一個則是做爲構造方法調用。
做爲構造函數調用:
JavaScript 支持面向對象式編程,與主流的面向對象式編程語言不一樣,JavaScript 並無類(class)的概念,而是使用基於原型(prototype)的繼承方式。相應的,JavaScript 中的構造函數也很特殊,若是不使用 new 調用,則和普通函數同樣。做爲又一項約定俗成的準則,構造函數以大寫字母開頭,提醒調用者使用正確的方式調用。若是調用正確,this 綁定到新建立的對象上。
經過new實例化一個函數:
function $(){
console.log(this);
}
var fn = new $();
控制檯輸出以下:
this就指這個新對象。
使用apply或call調用:
使用apply方法((固然使用Function.call也是能夠的)),另外一個方法 call 也具有一樣功能,不一樣的是最後的參數不是做爲一個數組統一傳入,而是分開傳入的。這兩個方法異常強大,他們容許切換函數執行的上下文環境(context),即 this 綁定的對象。不少 JavaScript 中的技巧以及類庫都用到了該方法。
function $(){
console.log(this);
}
var o = {};
o.m = $;
o.m.apply();
控制檯打印結果:
注:Function.apply(obj,args)方法能接收兩個參數
obj:這個對象將代替Function類裏this對象
args:這個是數組,它將做爲參數傳給Function(args-->arguments)
apply()的參數爲空時,默認調用全局對象。
若是將call的第一個destination的值設爲一個對象,以下:
function $(){
console.log(this);
}
var u = {};
var o = {};
o.m = $;
o.m.apply(u,null);
控制檯打印結果以下:
也即this指向了apply綁定的那個對象。
做爲構造函數及apply/call的混用:
//下面這段代碼模擬了new操做符(實例化對象)的內部過程 function person(name){ var o={}; o.__proto__=Person.prototype; //原型繼承 Person.call(o,name); return o; } var personB=person("xl"); console.log(personB.name); // 輸出 xl
在person
裏面首先建立一個空對象o,將o的proto指向Person.prototype完成對原型的屬性和方法的繼承。Person.call(o,name)
這裏即函數Person
做爲apply/call
調用(具體內容下方),將Person
對象裏的this
改成o,即完成了o.name=name
操做。返回對象o。
做爲回調函數的this:
咱們來看看 this 在 JavaScript 中常常被誤用的一種狀況:回調函數。JavaScript 支持函數式編程,函數屬於一級對象,能夠做爲參數被傳遞。請看下面的例子 myObject.handler 做爲回調函數,會在 onclick 事件被觸發時調用,但此時,該函數已經在另一個執行環境(ExecutionContext)中執行了,this 天然也不會綁定到 myObject 對象上。
button.onclick = obj.handler;
代碼以下:
<p id="p">click me</p>
<script type="text/javascript">
var obj = {
handler: function() {
console.log(this);
}
}
var p = document.getElementById("p");
p.onclick = obj.handler;
執行結果以下:
很顯然指向是觸發click事件的dom元素對象,而非obj對象。這是 JavaScript 新手們常常犯的一個錯誤,爲了不這種錯誤,許多 JavaScript 框架都提供了手動綁定 this 的方法。好比 Dojo 就提供了 lang.hitch,該方法接受一個對象和函數做爲參數,返回一個新函數,執行時 this 綁定到傳入的對象上。使用 Dojo,能夠將上面的例子改成:button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已經提供了內置的 bind 方法供你們使用。
eval方法中this的指向:
JavaScript 中的 eval 方法能夠將字符串轉換爲 JavaScript 代碼,使用 eval 方法時,this 指向哪裏呢?答案很簡單,看誰在調用 eval 方法,調用者的執行環境(ExecutionContext)中的 this 就被 eval 方法繼承下來了。
var name="XL";
var person={
name:"xl",
showName:function(){
eval("console.log(this.name)");
}
}
person.showName(); //輸出 "xl"
var a=person.showName;
a(); //輸出 "XL"
第一次的執行環境是person對象,第二個是global全局環境。
Function.prototype.bind()方法:
1.var name = "XL";
function Person(name) {
this.name = name;
console.log(this);
this.sayName = function() {
console.log(this);
setTimeout(function(){
console.log(this);
console.log("my name is " + this.name);
},50)
}
}
var person = new Person("xl");
person.sayName();
2.var name="XL"; function Person(name){ this.name=name; this.sayName=function(){ setTimeout(function(){ console.log("my name is "+this.name); }.bind(this),50) //注意這個地方使用的bind()方法,綁定setTimeout裏面的匿名函數的this一直指向Person對象 } } var person=new Person("xl"); person.sayName(); //輸出 「my name is xl」;
這裏setTimeout(function(){console.log(this.name)}.bind(this),50);
,匿名函數使用bind(this)
方法後建立了新的函數,這個新的函數無論在什麼地方執行,this
都指向的調用它的對象
,而非window。而若是不加bind在第一段代碼中,window執行環境中建立了一個變量person,被賦值了Person的實例,此時的調用順序是person調用了sayName這個方法,這個方法被賦值了一個函數(此處有問題,該賦值函數是匿名仍是非匿名?下篇討論),建立了一個執行環境,此時setTimeOut函數開始執行,建立一個環境。匿名函數及setTimeout/setInterval在非手動改變指向額狀況下都在全局做用域當中。
SO,第一段代碼的打印結果是:
sf的解釋:setTimeout/setInterval/匿名函數執行
的時候,this
默認指向window對象
,除非手動改變this的指向。在《javascript高級程序設計》當中,寫到:「超時調用的代碼(setTimeout
)都是在全局做用域中執行的,所以函數中的this的值,在非嚴格模式下是指向window對象,在嚴格模式下是指向undefined」。本文都是在非嚴格模式下的狀況。
第二段代碼的打印結果是:
這至關於手動將this進行了強制轉向。
另外一種手動轉向的方法:
var name="XL"; function Person(){ this.name="xl"; var that=this; this.showName=function(){ console.log(that.name); } setTimeout(this.showName,50) } var person=new Person(); //輸出 "xl"
借用了上面提到的that保存this指針值進行復用的技巧。
匿名函數中的this:
var name="XL"; var person={ name:"xl", showName:function(){ console.log(this.name); } sayName:function(){ (function(callback){ callback(); })(this.showName) } } person.sayName(); //輸出 XL var name="XL"; var person={ name:"xl", showName:function(){ console.log(this.name); } sayName:function(){ var that=this; (function(callback){ callback(); })(that.showName) } } person.sayName() ; //輸出 "xl"
此處採用了that技巧保存this指針。
箭頭函數(點擊這裏):
箭頭函數看上去是匿名函數的一種簡寫,但實際上,箭頭函數和匿名函數有個明顯的區別:箭頭函數內部的this
是詞法做用域,由上下文肯定。示例代碼:
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth還是1990
return fn.call({birth:2000}, year);
}
};
console.log(obj.getAge(2015)); // 25
控制檯打印結果:
this的四種使用場景
面向對象的語言中,this 關鍵字的含義是明確且具體的,即指代當前對象。通常在編譯期肯定下來,或稱爲編譯期綁定。而在 JavaScript 中,this 是動態綁定,或稱爲運行期綁定,這就致使 JavaScript 中的 this 關鍵字有能力具有多重含義,它能夠是全局對象、當前對象或者任意對象,這徹底取決於函數的調用方式,帶來靈活性也帶來困惑。js「超級」遲綁定( very late binding)使得函數能夠對this高度複用。經過this可取得它們所屬對象的上下文的方法稱爲公共方法。使用this關鍵字在面嚮對象語言中多數狀況下是爲了不命名衝突。總的來講,JavaScript 中函數的調用有以上幾種方式:做爲對象方法調用,做爲函數調用,做爲構造函數調用,和使用 apply 或 call 調用。JavaScript 中的函數既能夠被看成普通函數執行,也能夠做爲對象的方法執行,這是致使 this 含義如此豐富的主要緣由。
函數的執行環境
JavaScript 中的函數既能夠被看成普通函數執行,也能夠做爲對象的方法執行,這是致使 this 含義如此豐富的主要緣由。一個函數被執行時,會建立一個執行環境(ExecutionContext),函數的全部的行爲均發生在此執行環境中,構建該執行環境時,JavaScript 首先會建立 arguments
變量,其中包含調用函數時傳入的參數。接下來建立做用域鏈。而後初始化變量,首先初始化函數的形參表,值爲 arguments
變量中對應的值,若是 arguments
變量中沒有對應值,則該形參初始化爲 undefined
。若是該函數中含有內部函數,則初始化這些內部函數。若是沒有,繼續初始化該函數內定義的局部變量,須要注意的是此時這些變量初始化爲 undefined
,其賦值操做在執行環境(ExecutionContext)建立成功後,函數執行時纔會執行,這點對於咱們理解 JavaScript 中的變量做用域很是重要,鑑於篇幅,咱們先不在這裏討論這個話題。最後爲 this
變量賦值,如前所述,會根據函數調用方式的不一樣,賦給 this
全局對象,當前對象等。至此函數的執行環境(ExecutionContext)建立成功,函數開始逐行執行,所需變量均從以前構建好的執行環境(ExecutionContext)中讀取。
本文是綜合了互聯網多篇文章結論以後親測的結論,目的也在於細化本身的知識體系,並進而更深入的理解js的內核實現。通過此番梳理,對this的靈活指向已經有了一個比較清晰的認識,不過還需在實際的工程中不斷運用以達到徹底掌握。不過通過此一番,不必死記硬背以上多種場景,若需使用的時候,在對印位置打印出來看看不就知道了?