this 關鍵字是 JavaScript 中最復雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。但是即使是非常有經驗的 JavaScript 開發者也很難說清它到底指向什么。
this是什么?
指向函數本身?
光從字面意思上來看,很容易讓人覺得this就是指向函數本身,事實上真是這樣嗎?我們可以看一個例子。
function foo() { this.count = this.count ? this.count + 1 : 1; } for (let i = 0; i < 5; i++) { foo(); } console.log(foo.count); // undefined
可以看到,foo.count輸出的并不是我們期待的5,而是一開始賦值的0。也就是說this其實并沒有指向函數本身。
指向作用域?
還有一種比較常見的誤解是,this指向了函數的作用域。
function foo() { var a = 2; bar(); } function bar() { console.log(this.a); } foo(); // undefined
這段代碼中,bar在foo中運行,輸出this.a得到的卻是undefined。也就是說this也不是指向函數的作用域的。
這也不是,那也不是,this到底是什么呢?在函數執行過程中,會創建一個執行上下文(一個記錄),this就是這個上下文中的一個屬性,在執行過程中用到。而this的指向則是取決于函數在哪里被調用。
this的綁定規則
this的綁定有四條可以遵循的規則,下面將一一介紹。
1.默認綁定
獨立函數調用,非嚴格模式下,指向window;嚴格模式下指向undefined。 這里說的獨立函數可以理解成除開后面三種情況的一般函數調用。
// 非嚴格模式 var name = 'Willem'; function foo() { console.log(this.name); } foo(); // Willem // 執行時啟用嚴格模式 (function() { 'use strict'; foo(); // Willem bar(); // Cannot read property 'name' of undefined })(); // 函數體使用嚴格模式 function bar() { 'use strict'; console.log(this.name); }
上述代碼中,分別在普通環境中輸出Willem,說明指向的確實是window對象。需要特別注意的一點是:嚴格模式下指向undefined指的是函數體內啟用了嚴格模式,而不是調用時。
2. 隱式綁定
隱式綁定說的是,在函數執行時,是否被某個對象擁有或包含。換句話說,在函數運行時,是否是作為某個對象的屬性的方式運行的,這樣說還是不是很清楚,來個栗子:
function foo() { console.log(this.a); } var a = 1; var obj = { a: 2, foo }; obj.foo(); // 2 var obj2 = { a: 3, obj }; obj2.obj.foo(); // 2
示例中,foo被當做了obj的一個屬性進行執行,此時obj作為了函數的上下文,此時this指向了obj,this.a等價于obj.a。在對象屬性鏈式的調用中,只有最后一層會對調用位置產生影響,也就是說最后一層會影響this指向。
有很多前端的小伙伴面試時或許還見過這樣的題:
function foo() { console.log(this.a); } var a = 1; var obj = { a: 2, foo }; var bar = obj.foo; bar(); // 1
這是隱式綁定最常見的一個問題,隱式丟失:被隱式綁定的函數會丟失綁定對象。雖然bar是對obj.foo的一個引用,但實際上引用的還是foo函數本身,bar函數就是一個獨立函數的調用,參考第一條,此時this指向了window|undefined。
還有一種經典的回調函數的this指向問題也是隱式丟失。
function foo() { console.log(this.a); } function doFoo(fn) { fn(); } var a = 1; var obj = { a: 2, foo }; doFoo(obj.foo); // 1
小結:在隱式綁定中,賦值的情況下(回調是隱式賦值)需要特別注意隱式丟失的問題 。
3. 顯示綁定
JavaScript中的Function提供了兩個方法call和apply,傳入的第一個參數是一個對象,會把this綁定到這個對象。如果是傳入的是一個原始值(字符串、數字、布爾),會被轉換成它的對象形式(new String(), new Boolean(), new Number())。
function foo() { console.log(this.a); } var obj = { a: 1 }; foo.call(obj); // 1
雖然我們可以使用call和apply顯式指定this的指向,但是還是會存在丟失綁定的問題。可以通過所謂的硬綁定(bind函數)來解決,這里就不過多贅述了。
4. new
最后要介紹的是使用new來做this的綁定的修改,有手動實現過new的童鞋應該比較清楚,js中的new和其他語言的new完全不同。
new的執行過程:
- 創建一個空對象
- 當前空對象執行原型對接
- 返回函數執行結果或者當前這個空對象
function Foo(a) { this.a = a; } var bar = new Foo(2); bar.a; // 2
使用 new 來調用函數時,我們會構造一個新對象并把它綁定到 函數調用中的 this上。
優先級
最后簡單說一下優先級的關系:new > 顯示綁定 > 隱式綁定 > 默認綁定。
推薦學習:《javascript基礎教程》
站長資訊網