Javascript的this指向問題,有些人可能覺得很簡單,有些人卻覺得撲朔迷離,看完本文之後相應會對this的掌握有一個直觀的判斷,而不是"開局全靠猜"。
敲黑板
- function函數this指向由
調用方式
確定,跟定義環境無關。 - 箭頭函數this指向由
定義環境
決定,與調用方式無關
,也不可以bind(this)
。
嚴格模式
- 非嚴格模式下,全局作用域下的this指向
window
- 嚴格模式下,全局作用域下的this指向
undefined
以下討論均爲非嚴格模式
,這個不影響今天的討論。
實踐
說結論往往是讓人難以理解的,下面通過不同的調用場景對this做一個說明。
1. 直接調用
function test() {
console.log(this);
}
test(); // 輸出undefined
直接調用是最簡單的, 大部分人在這裏都能回答的很好。
總結
直接調用時this指向
全局作用域
。
- 非嚴格模式this指向window
- 嚴格模式this指向undefined
2. 對象調用
'use strict'
var n = 1;
var a = {
n: 2,
b: function() {
console.log(this.n);
}
};
a.b(); // 輸出2
var b = a.b;
b(); // 輸出1
面試題:請問上述例子輸出什麼?
非嚴格模式下,輸出2和1,嚴格模式下輸出2和一個報錯(this指向undefined,訪問undefined的n屬性肯定報錯)
那如果你這麼回答,滿分
!
分析
知其然還要知其所以然,我們分析一下:
爲什麼輸出2?
因爲a.b()
是對象調用方式,所以b()中的this指向a
爲什麼輸出1?
這個非常有意思,而且也很有迷惑性,面試的時候經常問到,也經常有人被問倒。
var b = a.b
把a.b
賦值給變量b
,b就是一個函數,請注意: 這裏只是賦值,沒有調用,所以b中的this指向還不確定
。
b();
調用函數b
,這是什麼調用方式? 普通調用
,所以this指向全局作用域。
總結
對象調用方式下this指向調用對象。
是否GET? 如果沒有GET,請關注公衆號NodeJs之路
,我在線給你答疑。
開胃菜已經吃了,下面來點"有難度的(實際上也沒啥難度)"。
3. 嵌套對象調用
var n = 1;
var a = {
n: 2,
b: {
n: 3,
c: function() {
console.log(this.n)
}
}
};
面試題:請問上述例子中function中的this指向哪裏?
正確答案:未確定調用環境
的情況下,this的指向不確定
。錯誤答案:指向a.b對象,Too young too simple!
var n = 1;
var a = {
n: 2,
b: {
n: 3,
c: function() {
console.log(this.n)
}
}
};
a.b.c(); // 輸出3
a.c = a.b.c;
a.c(); // 輸出2
var c = a.b.c;
c(); // 輸出1
這道題跟之前那道對象調用
很像。
爲什麼輸出3?
對象調用方式下指向調用對象,a.b.c()中c()是通過a.b
對象調用,指向對象a.b
爲什麼輸出?
a.c = a.b.c 給a對象定義一個函數c,注意,此時沒有調用!this指向不確定a.c() 通過a對象來調用c(),所以this指向對象
a
爲什麼輸出1?
var c = a.b.c 函數賦值給普通變量,注意,此時沒有調用!c(); 普通方式調用,指向window
總結
嵌套對象調用方式下,this指向最終調用
函數的對象。
a.b.c.d.e.f.g.h()
h函數中的this指向a.b.c.d.e.f.g
4. 構造函數方式調用
var name = 1;
function Person() {
this.name = 2;
}
var p1= Person(); // p1爲undefined
console.log(p1.name); // 報錯
var p2 = new Person();
console.log(p2.name); // 輸出2
p1爲什麼是undefined?
這道題比較坑,跟調用方式和this指向無關,因爲Person函數沒有返回值,js中,默認會返回undefined.
p2.name爲什麼是2?
使用new操作符時,構造函數的返回值默認
指向對象實例,所以p2.name就是Person()中的this.name
如果在Person()
函數中加上return this
的話,Person()
返回值還是this
,因爲這是普通調用。
5. 構造函數中指明返回值
原則上構造函數不應該有返回值,但是如果真的寫了會怎麼樣?我們來探討一下。
返回複雜對象
function Person() {
this.name = 2;
return {};
}
var p1 = new Person();
console.log(p1.name)// 輸出undefined
返回簡單對象
雖然Js只有對象,但是有一些如string,number這種一般叫做簡單對象,date,regex,array,object等等叫複雜對象。
function Person() {
this.name = 2;
return 1;
}
var p1 = new Person();
console.log(p1.name)// 輸出2
返回null
使用typeof null
返回的是[object]
,證明null是個對象,不過咱們來看看構造函數返回null的表現。
function Person() {
this.name = 2;
return null;
}
var p1 = new Person();
console.log(p1.name)// 輸出2
總結
構造函數中this指向對象實例本身,如果構造函數指明瞭返回值,那麼表現如下:
- 返回普通值,this指向不變,還是對象實例本身
- 返回複雜對象,this指向新對象,也就是你new Person()返回的是那個新對象
6. bind
var a = {
n: 1
};
var b = {
n: 2
}
function f() {
console.log(this.n);
}
var fa = f.bind(a);
var fb = fa.bind(b);
fa(); // 輸出1
fb(); // 輸出1
第1個輸出1應該不難理解,bind可以更改function內部的this指向。多次bind已經bind過的函數,this指向不變。
bind的實現原理有點複雜,將在下一篇文章進行詳細解讀。
總結
bind可以手動綁定function的this,this
指向第1次
bind時的this。
7. apply/call
這兩個函數在this指向上表現一致,放到一起講
var a = {
n: 1
};
var b = {
n: 2
}
function f() {
console.log(this.n);
}
f.call(a); // 輸出1
f.apply(b); // 輸出2
call和apply的第1個參數爲function執行時的this,這個this是確定的,對未使用過bind的函數進行多次apply/call,this指向都會改變。
8. 箭頭函數
var n = 1;
var b = {
n: 2,
a: () => {
console.log(this.n);
}
}
b.a(); // 輸出1
b.a.call({n:3}); // 輸出1
b.a定義時的this和n
,b
所在的this一致
,默認情況下爲全局作用域
箭頭函數的this指向定義時所在的this,這個是明確的,但是如果定義時所在的是1個function,那麼this指向同上面7點。
說下我之前學JS遇到過的問題: ES5下function纔會有作用域隔離, {}這種玩意不會隔離作用域。
結尾
- 直接調用this指向全局作用域window,嚴格模式指向undefined
- 對象調用方式指向調用對象
- 嵌套對象調用方式指向最終調用對象(離function最近的那個)
-
構造函數方式調用指向對象實例
- 構造函數返回String/Number等簡單類型時this指向不變,返回null指向也不變
- 構造函數返回Object/Array等複雜對象時,new Person()的返回值爲return的對象
- bind可以更改function的this,一經綁定,永不改變。
但是並不執行函數
- apply/call可以更改沒有被bind過的this
- 箭頭函數的
this
指向爲定義
箭頭函數的this