JavaScript 複習之 this關鍵字

DreamTruth發表於2019-02-27

this關鍵字一直都是我們學習js中的難點。

this都有一個共同點:他總是返回一個物件。

簡單說,this就是屬性或方法當前所在的物件。

var person = {
  name: '張三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

person.describe()
// "姓名:張三"
複製程式碼

上面程式碼,nameperson的屬性,describe方法所在的物件是person。由於thisdescribe中呼叫,所以this指向persion

由於物件的屬性可以賦給另外一個物件,所以屬性所在的當前物件是可變的,即this的指向是可變的。

function f() {
  return '姓名:'+ this.name;
}

var A = {
  name: '張三',
  describe: f
};

var B = {
  name: '李四',
  describe: f
};

A.describe() // "姓名:張三"
B.describe() // "姓名:李四"
複製程式碼

只要函式被賦給另一個變數,this的指向就會變。

var A = {
  name: '張三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var name = '李四';
var f = A.describe;
f() // "姓名:李四"
複製程式碼

上面程式碼中,A.describe被賦值給了f,內部的this就會指向f所在物件(本例是頂層物件)。

總結一下,JavaScript 語言之中,一切皆物件,執行環境也是物件,所以函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。

實質

js 語言之所以有this的設計,跟記憶體裡面的資料結構有關係。

先看一個物件:

var obj = {color: red};
複製程式碼

程式碼將一個物件賦值給了變數obj,js 引擎首先會在記憶體中生成一個物件{color: red},然後被這個物件的記憶體地址賦值給變數obj。也就是說,變數obj是一個地址。讀取obj.color,引擎先從obj拿到地址,然後從該地址讀出原始的物件,返回它的color屬性。

原始物件以字典結構儲存,物件的每個屬性都對應一個屬性描述物件。以上面的color屬性來說:

{
    color:{
        [[value]]: red
        [[writable]]: true
        [[enumerable]]: true
        [[configurable]]: true
    }
}
複製程式碼

注意,color屬性的值儲存在value屬性裡面。我們知道,屬性後面也許是一個函式,而引擎會將函式儲存在記憶體中,所以此處的value也有可能是一個函式的地址。

由於函式是一個單獨的值,所以它可以在不同的環境(上下文)執行。js 允許函式體內部,引用當前環境的其他變數。那麼問題就來了,由於函式可以在不同的執行環境中執行,所以需要一種機制,能夠在函式體內部獲得當前的執行環境。所以,this就出現可,他設計的目的就是在函式體內部,指代函式當前的執行環境。

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 單獨執行
f() // 1

// obj 環境執行
obj.f() // 2
複製程式碼

使用場合

1、全域性環境

全域性環境使用 this,它指的就是頂層物件 window

this === window //true

function f() {
  console.log(this === window);
}
f() // true
複製程式碼

2、建構函式

建構函式種的this,指向例項物件。

var Obj = function (p) {
  this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
複製程式碼

3、物件的方法

如果物件的方法裡面包含thisthis指向的就是方法執行時所在的物件。該方法賦值給另外一個物件,就會改變this的指向。

但是,這條規則很不容易把握。

var obj ={
  foo: function () {
    console.log(this);
  }
};

obj.foo() // obj
複製程式碼

上面程式碼中,obj.foo方法執行時,它內部的this指向obj

但是,下面這幾種用法,都會改變this的指向。

// 情況一
(obj.foo = obj.foo)() // window
// 情況二
(false || obj.foo)() // window
// 情況三
(1, obj.foo)() // window
複製程式碼

上面程式碼中,obj.foo就是一個值。這個值真正呼叫的時候,執行環境已經不是obj了,而是全域性環境,所以this不再指向obj

可以這樣理解,JavaScript 引擎內部,objobj.foo儲存在兩個記憶體地址,稱為地址一和地址二。obj.foo()這樣呼叫時,是從地址一呼叫地址二,因此地址二的執行環境是地址一,this指向obj。但是,上面三種情況,都是直接取出地址二進行呼叫,這樣的話,執行環境就是全域性環境,因此this指向全域性環境。

如果this所在方法不在物件的第一層,這時this只是指向當前一層的物件,而不會繼承更上面的層。

使用注意點

  1. 避免多層this
  2. 避免陣列處理方法中的this
var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    });
  }
}

o.f()
// undefined a1
// undefined a2
複製程式碼

解決這個問題的一種方法,就是使用中間變數固定this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2
複製程式碼

另一種方法是將this當作foreach方法的第二個引數,固定它的執行環境。

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
}

o.f()
// hello a1
// hello a2
複製程式碼
  1. 避免回掉函式中的this

繫結this的方法

也就是常用的callapplybind三個方法。

三者都可以接受多個引數;但是第一個引數是this所要指向的那個物件。 區別如下:

  • applycallbind 三者第一個引數都是 this 要指向的物件,也就是想指定的上下文;
  • applycallbind 三者都可以利用後續引數傳參;
  • bind 是返回對應 函式,便於稍後呼叫;applycall 則是立即呼叫 。
  • 對於 applycall 二者而言,作用完全一樣,只是接受 引數 的方式不太一樣。call 是把引數按順序傳遞進去,而 apply 則是把引數放在陣列 裡。

注意點:

  • 方法的引數,應該是一個物件。如果引數為nullundefined,則預設傳入全域性物件。
  • 如果方法的第一個引數是一個原始值,那麼這個原始值會自動轉成對應的包裝物件,然後傳入方法。

相關文章