js–this指向的相關問題
- 2021 年 3 月 7 日
- 筆記
- javascript
前言
關於this的指向問題是前端面試中常考的知識點,也是我們開發學習中較難理解的問題。作為JavaScript的基礎,需要我們徹底理解這一關鍵詞。this作為JavaScript中非常複復雜的機制,值得我們付出更大的代價來學習理解。這裡分享一下我的學習筆記。
正文
1.this是什麼?this指向什麼?
學習this之前首先要知道this永遠指向一個對象,this就是函數運行時候所在的環境,我們在學習執行上下文的時候提到,每一個執行上下文都包含三個重要對象,分別是變量對象、作用域鏈和 this,JavaScript支持運行環境動態切換,也就是說,this的指向是動態的,並不是固定的指向一個對象,this的指向完全取絕於函數調用的位置。接下來我們看下下面的代碼:
var a=1 function foo(){ var a=3 console.log(a) } foo()//3
上面的代碼執行用到函數的作用域問題,foo()函數執行的時候,函數內部定義一個變量a等於3,然後輸出打印a,此時foo上下文中存在a變量,因此輸出3,若函數foo()內部沒有定義a這個變量,在執行打印這句代碼的時候,函數內部找不到該變量,此時會沿着作用域鏈向上次查找,即全局作用域,此時打印結果便為1。
var a=1 function foo(){ var a=3 console.log(this.a) } foo()//1
上面的代碼執行結果會打印出1,對比第一段代碼你會發現就這裡多了this這一個關鍵詞,為什麼加了this之後輸出結果會發生改變呢?莫非此時this指向window全局對象?
var a=1 function foo(){ var a=3 console.log(this.a) } var obj={a:100,foo:foo} obj.foo()//100
再來看下上面的代碼,打印結果變為100,對比前面兩段代碼,foo函數增加了obj對象的一層引用,輸出的this.a結果再次發生改變?難道此時this指向obj對象?
this的指向為什麼會發生改變,this的指向到底什麼時候發生的?是因為函數在JavaScript中既可以當作值傳遞和返回,也可以當作對象和構造函數。所有函數在運行的時候才能確定其當前的運行環境,所以this就產生了,this會根據函數的運行環境的改變而改變,同時,函數中的this也只能在函數運行時最終確定其運行環境。
2.this不指向它自身。
function foo(num) { console.log( "foo: " + num ); // 記錄 foo 被調用的次數 this.count++; } foo.count = 0; for (var i=0; i<5; i++) { foo( i ); } console.log("foo.count:"+foo.count) //foo: 0 //foo: 1 //foo: 2 //foo: 3 //foo: 4 //foo.count:0
上面的代碼打印輸出foo:01234,說明foo函數被調用了五次,然後打印foo.count輸出還是0,說明this.count中的this並不是指向的函數foo本身,foo.count只是函數foo的一個屬性,this無法指向他本身。我們可以通過arguments.callee來引用當前正在執行的函數對象,即對象本身。
3.this不指向它的作用域。
另一種常見的誤解就是this指向函數的作用域。我們需要牢記,this在任何情況下都不指向函數的詞法作用域,this的指向完全取決於函數調用的位置。因此不能使用this來引用一個函數詞法作用域內部的東西。
4.this的綁定規則。
通過函數調用的位置來確定函數綁定的this對象,需要遵守以下四條規則:
(1)默認綁定
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
上面的代碼中,foo()函數直接調用,不帶任何修飾的函數引用進行調用,因此採用默認綁定,此時this指向window對象,無法應用其他規則。在非 strict mode 下時,默認綁定才能綁定到全局對象,嚴格模式下 this 綁定在undefined。
(2)隱式綁定–需要考慮的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
上面的代碼中,foo函數被調用時 obj 對象 「 擁有 」 或者 「 包含 」 它。當函數引 用有上下文對象時,隱式綁定規則會把函數調用中的 this綁定到這個上下文對象,因為調 用 foo() 時 this 被綁定到 obj,因此 this.a 和 obj.a 是一樣的。
function foo() { console.log(this.a); } var obj2 = { a: 2, fn: foo }; var obj1 = { a: 1, o1: obj2 }; obj1.o1.fn();//2
上面的代碼需要值得注意的是:對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。
function foo() { console.log( this.a ); } var obj = { a: "local", foo: foo }; var bar = obj.foo; // 函數別名! var a = " global"; // a 是全局對象的屬性 bar(); // "global"
上面的代碼,雖然 bar 是 obj.foo 的一個引用,但是實際上,它引用的是 foo 函數本身,因此此時的 bar() 其實是一個不帶任何修飾的函數調用,因此應用了默認綁定。
function foo(){ console.log(this.a) } var a="global" var obj={a:"local",foo:foo} var bar=obj.foo function doFnc(fn){ fn() } doFnc(bar)//global
上面的代碼需要注意的是:被隱式綁定的函數會丟失綁定對象。
(3)顯示綁定或者硬綁定–call,apply,bind直接指定this的綁定對象
call() apply() bind()直接指定 this 的綁定對象,返回一個硬編碼的新函數,它會把參數設置為 this 的上下文並調用原始函數。但是如果你把 null 或者 undefined 作為 this 的綁定對象傳入 call、apply 或者 bind,這些值 在調用時會被忽略,實際應用的是默認綁定規則。foo.call( obj );以在調用 foo 時強制把它的 this 綁定到 obj 上。
(4)new綁定
JavaScript 中 new 的機制實 際上和面向類的語言完全不同。
在 JavaScript 中,構造函數只是一些 使用 new 操作符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上, 它們甚至都不能說是一種特殊的函數類型,它們只是被 new 操作符調用的普通函數而已。包括內置對象函數(比如 Number(..))在內的所有函數都可 以用 new 來調用,這種函數調用被稱為構造函數調用。
使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操作。
1. 創建(或者說構造)一個全新的對象。
2. 這個新對象會被執行 [[ 原型 ]] 連接。
3. 這個新對象會綁定到函數調用的 this。
4. 如果函數沒有返回其他對象,那麼 new 表達式中的函數調用會自動返回這個新對象
new 來調用 foo(..) 時,我們會構造一個新對象並把它綁定到 foo(..) 調用中的 this 上。
5.綁定的優先和一些特殊情況。
(1)間接綁定
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 的返回值是目標函數的引用
對比隱式綁定
function foo() { console.log( this.a ); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 ,fo:o.foo}; p.fo() //4 對比間接引用
(2)事件綁定中的this
1.行內綁定 <input type='button' onclick="this"> <input onclick="clickFuc"> function(){ this ...} //this指向window對象 2.事件監聽與動態綁定 <input type="button" value="按鈕" id="btn"> <script> var btn = document.getElementById('btn'); btn.onclick = function(){ this ; // this指向本節點對象 }</script> //動態綁定的事件本身就是對應節點的一個屬性,重新賦值為一個匿名函數,this自然就指向了本節點對象
(3)箭頭函數
ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法作用域來決定this,具體來說,箭頭函數會繼承外層函數調用的 this 綁定(無論 this 綁定到什麼)。這其實和 ES6 之前代碼中的 self = this 機制一樣。
總結
