Javascript基礎——this指向

Javascript的this指向問題,有些人可能覺得很簡單,有些人卻覺得撲朔迷離,看完本文之後相應會對this的掌握有一個直觀的判斷,而不是"開局全靠猜"。

敲黑板

  1. function函數this指向由調用方式確定,跟定義環境無關。
  2. 箭頭函數this指向由定義環境決定,與調用方式無關,也不可以bind(this)

嚴格模式

  1. 非嚴格模式下,全局作用域下的this指向window
  2. 嚴格模式下,全局作用域下的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纔會有作用域隔離, {}這種玩意不會隔離作用域。

結尾

  1. 直接調用this指向全局作用域window,嚴格模式指向undefined
  2. 對象調用方式指向調用對象
  3. 嵌套對象調用方式指向最終調用對象(離function最近的那個)
  4. 構造函數方式調用指向對象實例

    1. 構造函數返回String/Number等簡單類型時this指向不變,返回null指向也不變
    2. 構造函數返回Object/Array等複雜對象時,new Person()的返回值爲return的對象
  5. bind可以更改function的this,一經綁定,永不改變。但是並不執行函數
  6. apply/call可以更改沒有被bind過的this
  7. 箭頭函數的this指向爲定義箭頭函數的this
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章