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
呼叫自身的屬性(方法)foo
,this
指向呼叫者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 { }
賦值給變數a
,a
即是物件例項。所以this.fne
指向a.fne
。
this
與return
的問題
上文說到建構函式執行的返回結果的有幾種情況:
- 返回物件時,
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
指向方法
- 當我們需要在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
-
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