this
關鍵字一直都是我們學習js中的難點。
this
都有一個共同點:他總是返回一個物件。
簡單說,this
就是屬性或方法當前所在的物件。
var person = {
name: '張三',
describe: function () {
return '姓名:'+ this.name;
}
};
person.describe()
// "姓名:張三"
複製程式碼
上面程式碼,name
是person
的屬性,describe
方法所在的物件是person
。由於this
在describe
中呼叫,所以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、物件的方法
如果物件的方法裡面包含this
,this
指向的就是方法執行時所在的物件。該方法賦值給另外一個物件,就會改變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 引擎內部,obj
和obj.foo
儲存在兩個記憶體地址,稱為地址一和地址二。obj.foo()
這樣呼叫時,是從地址一呼叫地址二,因此地址二的執行環境是地址一,this
指向obj
。但是,上面三種情況,都是直接取出地址二進行呼叫,這樣的話,執行環境就是全域性環境,因此this
指向全域性環境。
如果this
所在方法不在物件的第一層,這時this
只是指向當前一層的物件,而不會繼承更上面的層。
使用注意點
- 避免多層
this
- 避免陣列處理方法中的
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
複製程式碼
- 避免回掉函式中的
this
繫結this
的方法
也就是常用的call
, apply
, bind
三個方法。
三者都可以接受多個引數;但是第一個引數是this
所要指向的那個物件。
區別如下:
apply
、call
、bind
三者第一個引數都是this
要指向的物件,也就是想指定的上下文;apply
、call
、bind
三者都可以利用後續引數傳參;bind
是返回對應 函式,便於稍後呼叫;apply
、call
則是立即呼叫 。- 對於
apply
、call
二者而言,作用完全一樣,只是接受 引數 的方式不太一樣。call
是把引數按順序傳遞進去,而apply
則是把引數放在陣列 裡。
注意點:
- 方法的引數,應該是一個物件。如果引數為
空
、null
和undefined
,則預設傳入全域性物件。 - 如果方法的第一個引數是一個原始值,那麼這個原始值會自動轉成對應的包裝物件,然後傳入方法。