簡介
this 實際上相當于一個參數,這個參數可能是開發中手動傳入的,也可能是 JS 或者第三方傳入的。
這個參數,通常指向的是函數執行時的“擁有者”。this 的機制,可以讓函數設計的更加簡潔,并且復用性更好。
this 是在函數執行時進行綁定的,綁定規則一共六條,分別是:
●new 綁定:使用 new 關鍵字創建對象時,this 會綁定到創建的對象上。
●顯式綁定:使用 call、apply 或 bind 方法顯式綁定時, this 為其第一個參數。
●隱式綁定:當函數掛在對象上執行時,系統會隱式地將 this 綁定到該對象上。
●默認綁定:當函數獨立執行時,嚴格模式 this 的默認綁定值為 undefined,否則為全局對象。
●箭頭函數綁定:使用箭頭函數時,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 的綁定規則。
1.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 會綁定到創建的對象上。
2.顯式綁定
情景二,使用 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 為其第一個參數。
3.隱式綁定
隱式綁定和顯式綁定不同的地方在于,顯式綁定由開發者來指定 this;而隱式綁定時,函數或方法都會有一個“擁有者”,這個“擁有者”指的是直接調用的函數或方法對象。
例一
先看一個最簡單的例子。
function bar() { console.log( this === obj ); }const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // trueobj.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 綁定為函數執行時的“擁有者”。
4.默認綁定
前一小段,討論了函數作為對象的方法執行時的情況。本小段,要討論的是,函數獨立執行的情況。
在函數直接調用的情況下,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。
5.箭頭函數綁定
箭頭函數實際上,只是一個語法糖,實際上箭頭函數中的 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 綁定值相同。
6.系統或第三方綁定
在 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 是由系統或者第三方綁定的。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com