不管在 javascript
的平常使用中仍是前端面試過程當中,this
的出鏡率都極高。這無疑說明了,this
的重要性。可是 this
很是靈活,致使不少人以爲 this
的行爲難以理解。本文從爲何要有 this
做爲切入點,總結了 this
的六大規則,但願能幫助你解答困惑。javascript
this 實際上至關於一個參數,這個參數多是開發中手動傳入的,也多是 JS 或者第三方傳入的。
這個參數,一般指向的是函數執行時的「擁有者」。this
的機制,可讓函數設計的更加簡潔,而且複用性更好。html
this
是在函數執行時進行綁定的,綁定規則一共六條,分別是:前端
new
綁定:使用 new
關鍵字建立對象時,this
會綁定到建立的對象上。java
顯式綁定:使用 call
、apply
或 bind
方法顯式綁定時, this
爲其第一個參數。node
隱式綁定:當函數掛在對象上執行時,系統會隱式地將 this
綁定到該對象上。git
默認綁定:當函數獨立執行時,嚴格模式 this
的默認綁定值爲 undefined
,不然爲全局對象。github
箭頭函數綁定:使用箭頭函數時,this
的綁定值等於其外層的普通函數(或者全局對象自己)的this
。面試
系統或第三方綁定:當函數做爲參數,傳入系統或者第三方提供的接口時,傳入函數中的 this
是由系統或者第三方綁定的。數組
this
的做用this 的機制提供了一個優雅的方式,隱式地傳遞一個對象,這可讓函數設計的更加簡潔,而且複用性更好。瀏覽器
考慮下面一個例子,有兩個按鈕,點擊後將其背景改成紅色。
function changeBackgroundColor(ele) { ele.style.backgroundColor = 'red'; } btn1.addEventListener('click',function () { changeBackgroundColor(btn1); }); btn2.addEventListener('click',function () { changeBackgroundColor(btn2); });
在這裏,咱們顯式地將被點擊的元素傳遞給了 changeBackgroundColor
函數。但實際上,這裏能夠利用 this
隱式傳遞上下文的特色,直接在函數獲取當前被點擊的元素。以下:
function changeBackgroundColor() { this.style.backgroundColor = 'red'; } btn1.addEventListener('click',changeBackgroundColor); btn2.addEventListener('click',changeBackgroundColor);
在第一個例子中,被點擊元素是經過 ele
,這個形式參數來代替的。而在第二個例子中,是經過一個特殊的關鍵字 this
來代替。this
它的做用和形式參數相似,其本質上是一個對象的引用,它的特殊性在於不須要手動傳值,因此使用起來會更加簡單和方便。
在實際使用中, this
究竟指向哪一個對象是最使人困惑的。本文歸類了六類情景,總結六條 this
的綁定規則。
new
綁定使用 new
建立對象的時候,類中的 this
指的是什麼?
class Person { constructor(name){ this.name = name; } getThis(){ return this } } const xiaoMing = new Person("小明"); console.log(xiaoMing.getThis() === xiaoMing); // true console.log(xiaoMing.getThis() === Person); // false console.log(xiaoMing.name === "小明"); // true
在上面例子中,使用了 ES6 的語法建立了 Person
類。在使用 new
關鍵字建立對象的過程當中,this
會由系統自動綁定到建立的對象上,也就是 xiaoMing
。
規則一:在使用 new
關鍵字建立對象時,this
會綁定到建立的對象上。
情景二,使用 call
、apply
和 bind
方法,顯式綁定 this
參數。
以 call
爲例,call
方法的第一個傳入的參數,是 this
引用的對象。
function foo() { console.log( this === obj ); // true console.log( this.a === 2 ); // true } const obj = { a: 2 }; foo.call( obj );
在顯式傳遞的狀況下,this
指向的對象很明顯,就是 call
、apply
或 bind
方法的第一個參數。
規則二:使用 call
、apply
或 bind
方法顯式綁定時, this
爲其第一個參數。
隱式綁定和顯式綁定不一樣的地方在於,顯式綁定由開發者來指定 this
;而隱式綁定時,函數或方法都會有一個「擁有者」,這個「擁有者」指的是直接調用的函數或方法對象。
先看一個最簡單的例子。
function bar() { console.log( this === obj ); } const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // true obj.bar(); // true
函數 foo
是直接掛在對象 obj
裏面的,函數 bar
是在外面定義的,而後掛在對象 obj
上的。不管函數是在何處定義,但最後函數調用時,它的「擁有者」是 obj
。因此 this
指向的是函數調用時的「擁有者」 obj
。
爲了更加深刻的理解,再考慮函數從新賦值到新的對象上的狀況,來看看下面的例子。
function bar() { console.log( this === obj1 ); // false console.log( this === obj2 ); // true } const obj1 = { foo: function () { console.log( this === obj1 ); // false console.log( this === obj2 ); // true }, bar: bar }; const obj2 = { foo: obj1.foo, bar: obj1.bar }; obj2.foo(); obj2.bar();
在該例子中,將 obj1
中的 foo
和 bar
方法賦值給了 obj2
。函數調用時,「擁有者」是 obj2
,而不是 obj1
。因此 this
指向的是 obj2
。
對象能夠多層嵌套,在這種狀況下執行函數,函數的「擁有者」是誰呢?
const obj1 = { obj2: { foo: function foo() { console.log( this === obj1 ); // false console.log( this === obj1.obj2 ); // true } } }; obj1.obj2.foo()
foo
方法/函數中的直接調用者是 obj2
,而不是 obj1
,因此函數的「擁有者」指向的是離它最近的直接調用者。
若是一個方法/函數,在它的直接對象上調用執行,又同時執行了 call
方法,那麼它是屬於隱式綁定仍是顯式綁定呢?
const obj1 = { a: 1, foo: function () { console.log(this === obj1); // false console.log(this === obj2); // true console.log(this.a === 2); // true } }; const obj2 = { a: 2 }; obj1.foo.call(obj2); // true
由上,能夠看出,若是顯式綁定存在,它就不可能屬於隱式綁定。
規則三:若是函數是掛在對象上執行的,這個時候系統會隱式的將 this
綁定爲函數執行時的「擁有者」。
前一小段,討論了函數做爲對象的方法執行時的狀況。本小段,要討論的是,函數獨立執行的狀況。
在函數直接調用的狀況下,this
綁定的行爲,稱之爲默認綁定。
爲了簡單起見,先討論在瀏覽器的非嚴格模式的下綁定行爲。
function foo() { console.log( this === window); // true } foo();
在上面的例子中,系統將 window
默認地綁定到函數的 this
上。
在這裏,先介紹一種咱們可能會在代碼中見到的顯式綁定 null
的寫法。
function foo() { console.log( this == window ); // true } foo.apply(null);
將例一默認綁定的狀況,改成了顯式綁定 null
的狀況。
在實際開發中,咱們可能會用到 apply
方法,並在第一個參數傳入 null
值,第二個參數傳入數組的方式來傳遞數組類型的參數。這是一種傳統的寫法,固然如今能夠用 ES6
的寫法來代替,可是這不在本文的討論範圍內。
在本例最須要關注的是,this
居然指向的 window
而不是 null
。我的測試的結果是,在函數獨立調用時,或者顯式調用,傳入的值爲 null
和 undefined
的狀況下,會將 window
默認綁定到 this
上。
在函數屢次調用,造成了一個調用棧的狀況下,默認綁定的規則也是成立的。
接着,探討下嚴格模式下,this
的默認綁定的值。
"use strict"; function foo() { console.log( this === undefined ); } foo(); // true foo.call(undefined); // true foo.call(null); // false
在嚴格模式下,this
的默認綁定的值爲 undefined
。
規則四:在函數獨立執行的狀況下,嚴格模式 this
的默認綁定值爲 undefined
,不然默認綁定的值爲 window
。
箭頭函數實際上,只是一個語法糖,實際上箭頭函數中的 this
其實是其外層函數(或者 window/global 自己)中的 this
。
// ES6 function foo() { setTimeout(() => { console.log(this === obj); // true }, 100); } const obj = { a : 1 } foo.call(obj); // ES5 function foo() { var _this = this; setTimeout(function () { console.log(_this === obj); // true }, 100); } var obj = { a : 1 } foo.call(obj);
規則五:使用箭頭函數時,this
的綁定值和其外層的普通函數(或者 window/global 自己) this
綁定值相同。
在 JavaScript 中,函數是第一公民,能夠將函數以值的方式,傳入任何系統或者第三方提供的函數中。如今討論,最後一種狀況。當將函數做爲值,傳入系統函數或者第三方函數中時,this
到底是如何綁定的。
咱們在文章一開始提到的,兩個按鈕例子,系統自動將 this
綁定爲點擊的按鈕。
function changeBackgroundColor() { console.log(this === btn1); // true } btn1.addEventListener('click',changeBackgroundColor);
接着測試系統提供的 setTimeout
接口在瀏覽器和 node 中綁定行爲。
// 瀏覽器 setTimeout(function () { console.log(this === window); // true },0) // node setTimeout(function () { console.log(this === global); // false console.log(this); // Timeout },0)
很神奇的是,setTimeout
在 node 和瀏覽器中的綁定行爲不一致。若是咱們將 node 的中的 this
打印出來,會發現它綁定是一個 Timeout
對象。
若是是第三發提供的接口,狀況會更加複雜。由於在其內部,會將什麼值綁定到傳入的函數的 this
上,事先是不知道的,除非查看文檔或者源碼。
系統或者第三方,在其內部,可能會使用前面的五種規則一種或多種規則,對傳入函數的 this
進行綁定。因此,規則六,實際上一條在由前五條規則上衍生出來的規則。
規則六:調用系統或者第三方提供的接口時,傳入函數中的 this
是由系統或者第三方綁定的。
參考文章:
查完規範後,用僞代碼再總結一下。
規範地址:
Construct:http://www.ecma-international...
Function Objects:http://www.ecma-international...
Function Calls:http://www.ecma-international...
ArrowFunction:http://www.ecma-international...
僞代碼
if (`newObj = new Object()`) { this = newObj } else if (`bind/call/apply(thisArgument,...)`) { if (`use strict`) { this = thisArgument } else { if (thisArgument == null || thisArgument == undefined) { this = window || global } else { this = ToObject(thisArgument) } } } else if (`Function Call`) { if (`obj.foo()`) { // base value . Reference = base value + reference name + strict reference // 例外: super.render(obj). this = childObj ? this = obj } else if (`foo()`) { // 例外: with statement. this = with object this = `use strict` ? undefined : window || global } }