1. 程式人生 > 其它 >JS 深入淺出This指向(精簡)

JS 深入淺出This指向(精簡)

從原理出發

首先我們圍繞耳熟能詳的“this始終指向它的呼叫者”開始。這句結論雖然沒有什麼問題,但是說得過於籠統,還是得深入到背後的執行原理才能舉一反三解決問題。

舉個簡單的例子:

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

var foo = obj.foo;
var num = 3;

obj.foo();//2

foo();//3

obj.foo();是物件obj呼叫自身的屬性(方法)foothis指向呼叫者obj
②由var foo = obj.foo;獲取函式,並用foo();在全域性作用域中呼叫函式,所以this

指向瀏覽器全域性物件Window

記憶體資料結構的概述

JS之所以設計this,是因為其記憶體的資料結構的特徵。

var obj = { foo:  5 };

①上面的物件賦值給變數,其實質是JS引擎現在記憶體中生成{foo: 5},然後再把該物件的記憶體地址賦值給變數obj。也就是說onj.foo實質上是先從obj獲取物件的記憶體地址然後再從地址讀取原始物件,最後返回物件屬性foo的值。
②原始物件以詞典結構儲存,一個屬性名對應一個屬性描述物件。如下圖,foo屬性的描述物件就包含4個描述屬性,而最重要的值儲存在描述物件[[value]]中。

foo屬性值為函式時,JS引擎同樣會先將原始函式儲存在記憶體中,然後把該函式的記憶體地址存放於foo

屬性的描述物件中的[[value]]裡面。

從原理出發章節參考於JavaScript 的 this 原理——阮一峰的網路日誌

為何造就this

我們需要JS在函式體內部可以引用當前環境的其他內部變數,就像這樣:

var f = function () {- 
  console.log(this.x)//內部引用當前呼叫者變數x
};

var x = 1;//實質給全域性物件Window新增屬性 x 並賦值 1
var obj = {
  x: 2,//呼叫者內部變數
  f: f
}

f();//結果為1,執行於全域性環境,所以 this.x 指向 Window.x
boj.f();//結果為2,執行於 obj 內部環境,所以 this.x 指向 obj.x

這就說明了造就this的目的就是為函式內部的語句指定當前執行環境,而當前執行環境不一定就是該函式內部環境哈。

this的擴充套件延伸

建構函式與this

function name () {
  this.fne = "yulin"
};

var a = new name();
console.log(a.fne);//yulin

關於這個建構函式這裡先籠統提一些,new關鍵字會建立一個空物件name { },並且會將函式name()的保留在[[Prototype]]原型中而且會執行該函式並返回執行結果(這裡有幾種情況下文會提到),最後整個物件name { }賦值給變數aa即是物件例項。所以this.fne指向a.fne

thisreturn的問題

上文說到建構函式執行的返回結果的有幾種情況:

  1. 返回物件時,this會指向返回的物件,null除外,否則指向起始呼叫者。
function name () {
  this.fne = "yulin";
  return {}
};
var a = new name();
console.log(a.fne);//undefined,指向 {}

function name () {
  this.fne = "yulin";
  return function () {}
};
var a = new name();
console.log(a.fne);//undefined,指向 function () {}

function name () {
  this.fne = "yulin";
  return null
};
var a = new name();
console.log(a.fne);//yulin,指向 null,null是特殊的物件不會更改 this

function name () {
  this.fne = "yulin";
  return undefined
};
var a = new name();
console.log(a.fne);//yulin,指向 name {}

更靈活的this指向方法

  1. 當我們需要在a物件中呼叫b物件的方法時,我們就需要用到call()方法指定目的執行環境。
const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn
x.call(b, 2, 3);//yhh 5
  1. apply()方法與call()型別,但傳入apply的第二個引數必須是陣列,就像[1, 2, 3], [a , b, c]以及陣列變數。
const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn
x.apply(b, [2, 3]);//yhh 5

/*
以下程式碼效果一樣
let arr = [2, 3];
x.apply(b, arr);
*/

請注意:a.call(null)a.apply(null)this指向都是Window
3. bind()call(), apply()完全不同,bind()改變this指向的同時還返回被呼叫的屬性,方法。

const a = {
  name: 'yulin',
  fn: function (e, q) {
  console.log(this.name);
  console.log(e + q);
  }
};

const b = {
  name: 'yhh'
}

var x = a.fn

x.bind(b);
/*ƒ (e, q) {
  console.log(this.name);
  console.log(e + q);
}*/

let y = x.bind(b);
y(2, 3);//yhh 5