前言
在我剛開始學js和寫js時,以及在工作中,我都被this
這傢伙困擾過,迷惑過。經過我查閱書籍和反覆實踐,終於大致搞懂了關於this
這個機制,其實,它並不難,還挺有意思的。下面我就來總結和解析下什麼是this
。
什麼是this?為什麼要用this?
'this'是JS中一個機制,也是一個關鍵字,它被自動定義在所有函式的作用域中。
在設計API中,使用'this'會以一種更加優雅的方式來傳遞一個物件的引用,使得API設計更加簡潔,擴充性更強。例如在物件導向程式設計中,"this"的使用會非常頻繁。
我開始對this的誤解
誤解一:this指向函式自身
demo:
function fn() {
this.count++;
}
fn.count = 0;
fn();
fn();
console.log(fn.count);// 0
複製程式碼
看完上面那段程式碼,如果你感到驚訝的話,不要慌,我當時也是這種感覺,哈哈,this就是這樣,對初學者迷惑極大,Follow me,讓我帶你逐步擊退這些迷惑。
事實上,上面程式碼中,函式fn
內部的this指向是全域性物件window
或者global
(Node.js中),並沒有指向函式fn
自身。為什麼this指向全域性物件去了?因為非嚴格模式下,全域性作用域中函式被獨立呼叫時,它的this預設指向(繫結)window
或者global
;在嚴格模式中,它的this為undefined
。
注意:在全域性物件中,瀏覽器執行環境的是
window
,Node環境中是global
誤解二:this指向函式的作用域
丟擲個demo:
function fn() {
var a = 1;
this.b();// 這裡this指向的是window
}
function b() {
console.log(this.a);// 這個this指向的也是window
}
fn();// 報錯a is not defined
複製程式碼
上面程式碼開始是希望函式b
能通過this來訪問函式fn
作用域變數a,但是事實並不能。函式b
裡的this不會在詞法作用域中查到函式fn
裡的變數a,它們的this均指向window
。如果想要函式b
訪問函式fn
裡的a
變數,可以使用閉包解決
function fn() {
var a = 1;
function b() {
console.log(a)
}
b();
}
fn();// 1
複製程式碼
揭開this這一機制的真實面紗
總的來說,你只要牢記這兩點:
- this不指向函式自身,也不指向函式作用域
- this是在函式被呼叫時發生的繫結,它的(繫結)指向完全取決於函式在哪裡被呼叫,並不是在編寫時繫結
其中,第一點已經舉例證明,現在來細說第二點。
函式被呼叫時,this的繫結規則(重點)
預設繫結
當函式被獨立呼叫時,也就是直接函式名( )
,沒有new啊,call,apply,作為物件的方法呼叫這些,函式裡的this會被預設繫結到全域性物件(在非嚴格模式下),也就是上文中兩個demo,裡面的函式的this均指向全域性物件。
給上文誤解一裡demo加點料加深理解:
var count = 0;
function fn() {
this.count++;// this指向window
}
fn.count = 0;
fn();// 實際呼叫的是window.fn()
fn();
console.log(fn.count);// 0
console.log(count);// 2 實際呼叫的是window.count
複製程式碼
隱式繫結
這條規則具體是這樣的,函式如果被某個物件(舉例obj物件)擁有,且作為obj物件的方法呼叫時,函式裡面的this就是obj物件。
舉個demo:
function fn () {
console.log(this.a);
}
var obj = {
a: 1,
fn: fn
}
obj.fn();// 1
// 或者
var obj = {
a: 1,
fn: function() {
console.log(this.a);
}
}
obj.fn();// 1
複製程式碼
這個規則會有幾種常見的“怪現象”,舉個demo,在回撥函式中:
function cb(fn) {
fn&&fn();// 看,呼叫位置在這!
}
function fn () {
console.log(this.a);
}
var a = 2;// window的
var obj = {
a: 1,
fn: fn
};
cb(obj.fn);// 2
複製程式碼
為什麼會這樣?其實,是醬紫的,函式fn
並沒有作為obj物件的方法被呼叫,而是通過obj物件傳入給函式cb
,作為回撥進行呼叫,也就是直接fn()
呼叫了,觸發了上面的預設繫結,this被繫結到window
物件中。
那麼,有沒有方法讓函式fn
訪問obj
物件的屬性a
呢?答案是有的,如下:
function cb(fn) {
fn&&fn();// 看,呼叫位置在這!
}
function fn () {
console.log(this.a);
}
var a = 2;// window的
var obj = {
a: 1,
fn: fn
};
cb(obj.fn.bind(obj));// 1
複製程式碼
哈哈,終於可以獲取obj
物件的屬性a
啦,關於bind
繫結,就要說說有關顯式繫結了。
顯式繫結
js提供了call
,apply
以及ES5提供的bind
方法給我們來強制繫結函式的this。
demo:
function fn() {
console.log(this.a);
};
var obj = {a: 1};
fn.call(obj);// 1
複製程式碼
call
和apply
的區別是呼叫函式時,傳參的方法不同。
demo:
var obj = {
a: 1
};
function fn(params) {
console.log(this.a,params)
};
fn.call(obj,2); // call傳參是一個個傳
fn.apply(obj,[2]); // apply傳參是傳一個陣列
複製程式碼
說說bind
。bind
會返回一個硬編碼的新函式,它會把你指定的引數設定為this的上下文並呼叫原始函式(來自:《你不知道的JavaScript》)
在上文隱式繫結的末尾,就用了bind
來繫結this實現訪問obj物件裡的a屬性。
你可以用call
或者apply
來簡單模擬實現bind
方法。
if(!Function.prototype.bind) {
Function.prototype.bind = function(context) {
var self = this,args = Array.prototype.slice.call(arguments, 1),;
return function() {
self.apply(context, args.concat(Array.prototype.slice.call(arguments)))
}
}
}
複製程式碼
new繫結
在使用new來呼叫函式,會執行下面的操作
- 建立一個全新的物件
- 這個新物件會被執行[[Prototype]]連線
- 這個新物件會指向(繫結)到函式呼叫的this
- 如果函式沒有返回其他物件,那麼new函式呼叫會自動返回這個新物件
demo:
function fn(a) {
this.a = a;
}
var obj = new fn(1);
obj.a // 1
// 以上程式碼,可以這麼理解
var obj = {};// 建立新物件
obj.__proto__ = fn.prototype;// 關聯原型鏈
fn.call(obj);// 繫結this;
複製程式碼
關於箭頭函式裡的this
箭頭函式裡this,不採用上面的4種原則,而是根據外層(函式或者全域性)作用域來決定this,它會繼承外層函式呼叫的this繫結。
箭頭函式能解決什麼有關this的問題?
舉個demo:
var a = 123;
var obj = {
a: 1,
fn: function() {
// 若obj.fn(), 這個作用域this指向obj;
return function() {
// 這種情況下,這個閉包裡的this是無法訪問外部作用域的this
console.log(this.a);// this指向全域性
}
}
}
obj.fn()();// 123
// ES5
var obj = {
a: 1,
fn: function() {
// 用self儲存當前this的引用;
var self = this;
return function() {
console.log(self.a);// 1
}
}
}
obj.fn()();// 1
// 使用箭頭函式
var obj = {
a: 1,
fn: function() {
return () => {
// 箭頭繼承了外層函式的this的繫結
console.log(this.a);
}
}
}
obj.fn()();// 1
複製程式碼
箭頭函式書寫簡潔,語義清晰明瞭。
還有在setTimeout
回撥函式中,同樣可以使用
function Fn(a) {
this.a = a;
setTimeout(() => {
console.log(this.a);
},0)
};
new Fn(1);// 1
複製程式碼
還有,在React開發中,用箭頭函式或者bind
可以解決函式中this的繫結問題。
小結
這篇文章的內容都是我在讀《你不知道的JavaScript》和《高程3》中總結出來的,加上一些自己的理解。如果有不對的地方,煩請指出,一起討論,如果對你有幫助,我很開心^_^