JavaScript教程筆記(11)-this關鍵字
1 定義
this關鍵字是一個非常重要的語法點,不理解它的含義,大部分開發任務都很難完成。
無論什麼場合,this總是返回一個物件。簡單說,this就是屬性或方法“當前”所在的物件。
var person = {
name: 'mark',
say: function() {
return 'name: ' + this.name;
}
};
person.say() // "name: mark"
上面程式碼中,由於this.name是在say方法中呼叫的,而say方法所在的當前物件是person,因此this指向person,而this. name 就是 person. name。
由於物件的屬性可以賦給另一個物件,因此this的指向是可變的。
function f() {
return 'hello: ' + this.name;
}
var A = {
name: 'AAA',
desc: f
};
var B = {
name: 'BBB',
desc: f
};
A.desc() // "hello: AAA"
B.desc() // "hello: BBB"
上面程式碼中,函式f內部使用了this,隨著f所在物件的不同,this的指向也不同。
總結一下,JavaScript語言中一切皆物件,執行環境是物件,函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。這本來是很清晰的概念,但是因為JavaScript支援執行環境的動態切換,所以沒有辦法事先確定this到底指向哪個物件,這才是最容易讓人迷惑的地方。
2 實質
由於函式可以在不同的執行環境執行,需要有一種機制,能夠在函式體內部獲得當前的執行環境(context)。所以,this就出現了,它的設計目的就是在函式體內部,指向函式當前的執行環境。
var f = function() {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
f() // 1
obj.f() // 2
上面程式碼中,函式f在全域性環境裡執行,this.x指向全域性環境的x,輸出1;在obj環境裡執行,this.x指向obj.x,輸出2。
3 使用注意點
3.1 避免多層this
切勿在函式中包含多層this,因為this的指向不確定。
var obj = {
f1: function() {
console.log(this);
var f2 = function() {
console.log(this);
}();
}
}
obj.f1()
// Object
// Window
上面程式碼包含兩層this,實際執行後,第一層this指向物件obj,第二層this指向全域性物件。因為實際執行的類似於下面程式碼:
var temp = function() {
console.log(this);
};
var obj = {
f1: function() {
console.log(this);
var f2 = temp();
}
}
簡單的解決方法是用一個指向外層物件的變數來代替第二層this。
var obj = {
f1: function() {
console.log(this);
var self = this; // 增加一個變數
var f2 = function() {
console.log(self);
}();
}
}
obj.f1()
// Object
// Object
上面程式碼定義了變數self,固定指向外層的this,就不會發生指向的改變了。
通過一個變數固定this的值,然後內層函式使用這個變數,是非常常見的做法,請務必掌握。
3.2 避免陣列處理方法中的this
陣列的map和foreach方法,允許提供一個函式作為引數。這個函式內部不應該使用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
上面forEach方法的回撥函式中的this,跟上一段的多層this一樣,內層this不指向外部,而是指向頂層物件。
解決這個問題的一個簡單方法,就是前面提到的,使用中間變數固定this。
3.3 避免回撥函式中的this
回撥函式中的this往往會改變指向,最好避免使用。
為了解決這個問題,可以採用下面的一些方法對this進行繫結,使this固定指向某個物件,減小不確定性。
4 繫結this的方法
JavaScript提供了call、apply、bind三個方法,來切換/固定this的指向。
4.1 Function.prototype.call()
函式例項的call方法,可以指定this的執行環境(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。
call方法的引數,是一個物件。
var obj = {};
var f = function() {
return this;
};
f() === window // true
f.call(obj) === obj // true
上面程式碼中,直接執行函式f時,this指向全域性環境(window物件)。而call方法可以改變this指向物件obj,然後在物件obj的作用域中執行函式f。
如果call方法的引數為空、null和undefined,則預設傳入全域性物件。
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
call方法還可以接受多個引數,後面的引數是函式呼叫時所需的引數。
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
4.2 Function.prototype.apply()
apply方法的作用與call方法類似,也是改變this指向,然後再呼叫該函式。唯一的區別是,它接收一個數組作為函式執行的引數,使用格式如下:
func.apply(thisValue, [arg1, arg2, ...])
原函式的引數,在call方法中必須一個個新增,但是在apply方法中,必須以陣列形式新增。
function f(x, y) {
console.log(x + y);
}
f.call(null, 1, 2) // 3
f.apply(null, [1, 2]) // 3
5.3 Function.prototype.bind()
bind方法用於將函式體內的this繫結到某個物件,然後返回一個新函式。
var d = new Date();
d.getTime()
var print = d.getTime;
print() // Error
上面程式碼中,print報錯是因為getTime方法內部的this,綁定了Date物件的例項,賦值給變數print後,內部的this已經不指向Date物件的例項了。
bind方法可以解決這個問題
var print = d.getTime.bind(d);
print() // ok
上面程式碼中,bind將getTime方法內部的this繫結到d物件,因此就可以安全地將getTime賦值給其它變量了。
使用bind還可以將this繫結到其它物件。
var counter = {
count: 0,
inc: function() {
this.count++;
}
};
var obj = {
count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101
上面程式碼中,bind將inc內部的this繫結到obj物件,因此呼叫func後,遞增的就是obj內部的count屬性。
使用bind方法有一些注意事項。
(1)每一次返回一個新函式
bind方法每執行一次,就會返回一個新函式,這可能產生一些問題。
node.addEventListener('click', obj.m.bind(obj));
node.removeEventListener('click', obj.m.bind(obj)); // Error
上面程式碼無法取消繫結,因為後一個bind返回的是一個匿名函式,和前一個add事件並不是同一個函式。
正確的定法是下面這樣:
var listener = obj.m.bind(obj);
node.addEventListener('click', listener);
node.removeEventListener('click', listener);
(2)結合回撥函式使用
回撥函式是JavaScript最常用的模式之一,我們應用使用bind方法將目標函式固定到目標物件。
(3)結合call方法使用
以陣列的slice方法為例。
[1, 2, 3].slice(0, 1) // [1]
等同於
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
上面程式碼的兩種呼叫方法,得到同樣的結果。
注:本文適用於ES5規範,原始內容來自 JavaScript 教程,有修改。