摘要:如下在第一個例子中,被點擊元素是通過,這個形式參數(shù)來代替的。它的作用和形式參數(shù)類似,其本質(zhì)上是一個對象的引用,它的特殊性在于不需要手動傳值,所以使用起來會更加簡單和方便。
無論在 javascript 的日常使用中還是前端面試過程中,this 的出鏡率都極高。這無疑說明了,this 的重要性。但是 this 非常靈活,導(dǎo)致很多人覺得 this 的行為難以理解。本文從為什么要有 this 作為切入點,總結(jié)了 this 的六大規(guī)則,希望能幫助你解答困惑。
簡介this 實際上相當(dāng)于一個參數(shù),這個參數(shù)可能是開發(fā)中手動傳入的,也可能是 JS 或者第三方傳入的。
這個參數(shù),通常指向的是函數(shù)執(zhí)行時的“擁有者”。this 的機(jī)制,可以讓函數(shù)設(shè)計的更加簡潔,并且復(fù)用性更好。
this 是在函數(shù)執(zhí)行時進(jìn)行綁定的,綁定規(guī)則一共六條,分別是:
new 綁定:使用 new 關(guān)鍵字創(chuàng)建對象時,this 會綁定到創(chuàng)建的對象上。
顯式綁定:使用 call、apply 或 bind 方法顯式綁定時, this 為其第一個參數(shù)。
隱式綁定:當(dāng)函數(shù)掛在對象上執(zhí)行時,系統(tǒng)會隱式地將 this 綁定到該對象上。
默認(rèn)綁定:當(dāng)函數(shù)獨立執(zhí)行時,嚴(yán)格模式 this 的默認(rèn)綁定值為 undefined,否則為全局對象。
箭頭函數(shù)綁定:使用箭頭函數(shù)時,this的綁定值等于其外層的普通函數(shù)(或者全局對象本身)的this。
系統(tǒng)或第三方綁定:當(dāng)函數(shù)作為參數(shù),傳入系統(tǒng)或者第三方提供的接口時,傳入函數(shù)中的 this 是由系統(tǒng)或者第三方綁定的。
this 的作用this 的機(jī)制提供了一個優(yōu)雅的方式,隱式地傳遞一個對象,這可以讓函數(shù)設(shè)計的更加簡潔,并且復(fù)用性更好。
考慮下面一個例子,有兩個按鈕,點擊后將其背景改為紅色。
function changeBackgroundColor(ele) { ele.style.backgroundColor = "red"; } btn1.addEventListener("click",function () { changeBackgroundColor(btn1); }); btn2.addEventListener("click",function () { changeBackgroundColor(btn2); });
在這里,我們顯式地將被點擊的元素傳遞給了 changeBackgroundColor 函數(shù)。但實際上,這里可以利用 this 隱式傳遞上下文的特點,直接在函數(shù)獲取當(dāng)前被點擊的元素。如下:
function changeBackgroundColor() { this.style.backgroundColor = "red"; } btn1.addEventListener("click",changeBackgroundColor); btn2.addEventListener("click",changeBackgroundColor);
在第一個例子中,被點擊元素是通過 ele ,這個形式參數(shù)來代替的。而在第二個例子中,是通過一個特殊的關(guān)鍵字 this 來代替。this 它的作用和形式參數(shù)類似,其本質(zhì)上是一個對象的引用,它的特殊性在于不需要手動傳值,所以使用起來會更加簡單和方便。
六大規(guī)則在實際使用中, this 究竟指向哪個對象是最令人困惑的。本文歸類了六類情景,總結(jié)六條 this 的綁定規(guī)則。
new 綁定使用 new 創(chuàng)建對象的時候,類中的 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 的語法創(chuàng)建了 Person 類。在使用 new 關(guān)鍵字創(chuàng)建對象的過程中,this 會由系統(tǒng)自動綁定到創(chuàng)建的對象上,也就是 xiaoMing。
規(guī)則一:在使用 new 關(guān)鍵字創(chuàng)建對象時,this 會綁定到創(chuàng)建的對象上。
顯式綁定情景二,使用 call、apply 和 bind 方法,顯式綁定 this 參數(shù)。
以 call 為例,call 方法的第一個傳入的參數(shù),是 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 方法的第一個參數(shù)。
規(guī)則二:使用 call、apply 或 bind 方法顯式綁定時, this 為其第一個參數(shù)。
隱式綁定隱式綁定和顯式綁定不同的地方在于,顯式綁定由開發(fā)者來指定 this;而隱式綁定時,函數(shù)或方法都會有一個“擁有者”,這個“擁有者”指的是直接調(diào)用的函數(shù)或方法對象。
例一先看一個最簡單的例子。
function bar() { console.log( this === obj ); } const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // true obj.bar(); // true
函數(shù) foo 是直接掛在對象 obj 里面的,函數(shù) bar 是在外面定義的,然后掛在對象 obj 上的。無論函數(shù)是在何處定義,但最后函數(shù)調(diào)用時,它的“擁有者”是 obj。所以 this 指向的是函數(shù)調(diào)用時的“擁有者” obj。
例二為了更加深入的理解,再考慮函數(shù)重新賦值到新的對象上的情況,來看看下面的例子。
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。函數(shù)調(diào)用時,“擁有者”是 obj2,而不是 obj1。所以 this 指向的是 obj2。
例三對象可以多層嵌套,在這種情況下執(zhí)行函數(shù),函數(shù)的“擁有者”是誰呢?
const obj1 = { obj2: { foo: function foo() { console.log( this === obj1 ); // false console.log( this === obj1.obj2 ); // true } } }; obj1.obj2.foo()
foo 方法/函數(shù)中的直接調(diào)用者是 obj2,而不是 obj1,所以函數(shù)的“擁有者”指向的是離它最近的直接調(diào)用者。
例四如果一個方法/函數(shù),在它的直接對象上調(diào)用執(zhí)行,又同時執(zhí)行了 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
由上,可以看出,如果顯式綁定存在,它就不可能屬于隱式綁定。
規(guī)則三:如果函數(shù)是掛在對象上執(zhí)行的,這個時候系統(tǒng)會隱式的將 this 綁定為函數(shù)執(zhí)行時的“擁有者”。
默認(rèn)綁定前一小段,討論了函數(shù)作為對象的方法執(zhí)行時的情況。本小段,要討論的是,函數(shù)獨立執(zhí)行的情況。
在函數(shù)直接調(diào)用的情況下,this 綁定的行為,稱之為默認(rèn)綁定。
例一為了簡單起見,先討論在瀏覽器的非嚴(yán)格模式的下綁定行為。
function foo() { console.log( this === window); // true } foo();
在上面的例子中,系統(tǒng)將 window 默認(rèn)地綁定到函數(shù)的 this 上。
例二在這里,先介紹一種我們可能會在代碼中見到的顯式綁定 null 的寫法。
function foo() { console.log( this == window ); // true } foo.apply(null);
將例一默認(rèn)綁定的情況,改為了顯式綁定 null 的情況。
在實際開發(fā)中,我們可能會用到 apply 方法,并在第一個參數(shù)傳入 null 值,第二個參數(shù)傳入數(shù)組的方式來傳遞數(shù)組類型的參數(shù)。這是一種傳統(tǒng)的寫法,當(dāng)然現(xiàn)在可以用 ES6 的寫法來代替,但是這不在本文的討論范圍內(nèi)。
在本例最需要關(guān)注的是,this 竟然指向的 window 而不是 null。個人測試的結(jié)果是,在函數(shù)獨立調(diào)用時,或者顯式調(diào)用,傳入的值為 null 和 undefined 的情況下,會將 window 默認(rèn)綁定到 this 上。
在函數(shù)多次調(diào)用,形成了一個調(diào)用棧的情況下,默認(rèn)綁定的規(guī)則也是成立的。
例三接著,探討下嚴(yán)格模式下,this 的默認(rèn)綁定的值。
"use strict"; function foo() { console.log( this === undefined ); } foo(); // true foo.call(undefined); // true foo.call(null); // false
在嚴(yán)格模式下,this 的默認(rèn)綁定的值為 undefined。
規(guī)則四:在函數(shù)獨立執(zhí)行的情況下,嚴(yán)格模式 this 的默認(rèn)綁定值為 undefined,否則默認(rèn)綁定的值為 window。
箭頭函數(shù)綁定箭頭函數(shù)實際上,只是一個語法糖,實際上箭頭函數(shù)中的 this 實際上是其外層函數(shù)(或者 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);
規(guī)則五:使用箭頭函數(shù)時,this 的綁定值和其外層的普通函數(shù)(或者 window/global 本身) this 綁定值相同。
系統(tǒng)或第三方綁定在 JavaScript 中,函數(shù)是第一公民,可以將函數(shù)以值的方式,傳入任何系統(tǒng)或者第三方提供的函數(shù)中。現(xiàn)在討論,最后一種情況。當(dāng)將函數(shù)作為值,傳入系統(tǒng)函數(shù)或者第三方函數(shù)中時,this 究竟是如何綁定的。
我們在文章一開始提到的,兩個按鈕例子,系統(tǒng)自動將 this 綁定為點擊的按鈕。
function changeBackgroundColor() { console.log(this === btn1); // true } btn1.addEventListener("click",changeBackgroundColor);
接著測試系統(tǒng)提供的 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 打印出來,會發(fā)現(xiàn)它綁定是一個 Timeout 對象。
如果是第三發(fā)提供的接口,情況會更加復(fù)雜。因為在其內(nèi)部,會將什么值綁定到傳入的函數(shù)的 this 上,事先是不知道的,除非查看文檔或者源碼。
系統(tǒng)或者第三方,在其內(nèi)部,可能會使用前面的五種規(guī)則一種或多種規(guī)則,對傳入函數(shù)的 this 進(jìn)行綁定。所以,規(guī)則六,實際上一條在由前五條規(guī)則上衍生出來的規(guī)則。
規(guī)則六:調(diào)用系統(tǒng)或者第三方提供的接口時,傳入函數(shù)中的 this 是由系統(tǒng)或者第三方綁定的。
參考文章:
You-Dont-Know-JS
The this keyword
MDN this
后期補充查完規(guī)范后,用偽代碼再總結(jié)一下。
規(guī)范地址:
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 } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81254.html
摘要:原文鏈接原文作者你想知道的關(guān)于作用域的一切譯中有許多章節(jié)是關(guān)于的但是對于初學(xué)者來說甚至是一些有經(jīng)驗的開發(fā)者這些有關(guān)作用域的章節(jié)既不直接也不容易理解這篇文章的目的就是為了幫助那些想更深一步學(xué)習(xí)了解作用域的開發(fā)者尤其是當(dāng)他們聽到一些關(guān)于作用域的 原文鏈接: Everything you wanted to know about JavaScript scope原文作者: Todd Mott...
摘要:沒有箭頭函數(shù)沒有自己的對象,這不一定是件壞事,因為箭頭函數(shù)可以訪問外圍函數(shù)的對象那如果我們就是要訪問箭頭函數(shù)的參數(shù)呢你可以通過命名參數(shù)或者參數(shù)的形式訪問參數(shù)不能通過關(guān)鍵字調(diào)用函數(shù)有兩個內(nèi)部方法和。 1、基本語法回顧 我們先來回顧下箭頭函數(shù)的基本語法。ES6 增加了箭頭函數(shù): var f = v => v; // 等同于 var f = function (v) { return ...
摘要:實例化一個對象實例化時,構(gòu)造器接受的第一個參數(shù)就是正則表達(dá)式的內(nèi)容,為類型。那么當(dāng)我們在創(chuàng)建一個正則表達(dá)式的過程中,要加上以上的的話,定義如下使用字面量時直接把跟在正則表達(dá)式后面可以給多個實例化一個時作為構(gòu)造器的第二個參數(shù),類型。 這里只講在JavaScript中的正則表達(dá)式 1: 如何創(chuàng)建一個正則表達(dá)式 在JS中有 2 種方式創(chuàng)建一個正則表達(dá)式: 1: 通過正則表達(dá)式字面量 co...
摘要:這里是結(jié)論,將是更驚艷的那一個。瀏覽器隔一段時間像服務(wù)器發(fā)送一個請求,詢問這里有沒有需要更新的消息。在響應(yīng)回來時,才會繼續(xù)發(fā)出第二個請求。但是,顯然的,這對我們要做的事來說并不算是什么問題。 我們都知道的是setTimout是用來延遲一個簡單的動作的,然而,setInterval的目的是用來重復(fù)執(zhí)行某個動作的。 然后,以上只是一半的事實。因為如果一個函數(shù)需要在一個間隔時間內(nèi)重復(fù)的執(zhí)行,...
摘要:上篇文章掌握緩存從請求到響應(yīng)過程的一切上我們討論了關(guān)于利用頭來解決緩存問題,這篇文章我們將介紹緩存和之間的關(guān)系。文件將會被緩存起來,這時如果你想讓你的新文件起作用,那么用最新的版本號命名它就可以。緩存不同供應(yīng)商清除緩存的方式不一樣。 作者:Ulrich Kautz 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=...
閱讀 1802·2021-11-24 09:39
閱讀 2290·2021-09-30 09:47
閱讀 4144·2021-09-22 15:57
閱讀 1873·2019-08-29 18:36
閱讀 3577·2019-08-29 12:21
閱讀 590·2019-08-29 12:17
閱讀 1263·2019-08-29 11:25
閱讀 724·2019-08-28 18:26