《javascript設計模式與開發實踐》閱讀筆記(三)
this,call和apply
2.1 this
this指標的用法,相信在很多場合都看到過,這裡也總結了幾點:
- 作為物件的方法呼叫
- 作為普通函式呼叫
- 構造器呼叫
- Function.prototype.call或Function.prototype.apply呼叫
2.1.1 作為物件的方法呼叫
var obj = {
a: 1,
getA: function() {
console.log(this === obj);
console.log(this.a);
}
};
obj.getA();
2.1.1 作為普通函式呼叫
當函式不作為物件的時候,也就是我們說得普通函式方式,此時的this總是指向全域性物件。在瀏覽器的Javascript中,這個全域性物件是指window物件。
window.name='globalName';
var getName=function(){
return this.name; //'globalName'
};
console.log(getName());
在有些情況下,比如下面的例子,在div節點的事件函式內部,有一個區域性的callback方法,callback被作為普通函式呼叫時,callback內部的this指向了window,但我們其實想讓它指向的時div節點:
<body>
<div id="div1">我是一個div</div>
</body>
<script type="text/javascript">
window.id="window";
document.getElementById('div1').onclick=function () {
alert(this.id); //'div1'
var callback=function(){
alert(this.id); //"window"
}
callback();
}
解決辦法是儲存div的this,即that還是指向的div1
<body>
<div id="div1">我是一個div</div>
</body>
<script type="text/javascript">
window.id="window";
document.getElementById('div1').onclick=function () {
alert(this.id); //'div1'
var that=this;
var callback=function(){
alert(that.id); //'div1'
}
callback();
}
注:在ECMAScript5的strict模式下,這種情況下的this已經被規定為不會指向全域性物件,而是undefined。
window.name='globalName';
var getName=function(){
"use strict"
alert(this)
return this.name;
};
getName();//undefined
2.1.3 構造器呼叫
javascript中沒有類,但是可以從構造器中建立物件,同時也提供了new運算子,使得構造器看起來更像是一個類。
除了宿主提供的一些內建函式,大部分Javascript函式可以當作構造器使用。構造器的外表跟普通函式一模一樣,它們的區別在於被呼叫的方式。當用new運算子呼叫函式時,該函式總會返回一個物件,通常情況下,構造器裡的this就指向返回的這個物件,如下程式碼:
var myClass = function() {
this.name = 'ave';
}
var obj = new myClass();
//myClass 中的this指向obj
console.log(obj.name);
但用new呼叫構造器時,還要注意一個問題,如果構造器顯式的返回了一個object物件,那麼此次運算結果最終會返回這個物件,而不是我們期待的this了,如:
var myClass = function() {
this.name = 'svem';
return {
name: 'jack'
}
}
var obj = new myClass();
console.log(obj.name);//jack
感覺這個經常會用到在單例模式中。
如果構造器不顯式地返回任何資料,或者是返回一個非物件型別的資料,就不會造成上述問題:
var myClass = function() {
this.name = 'svem';
return 'jack' //返回string型別
}
var obj = new myClass();
console.log(obj.name);//svem,因為這裡的jack沒有顯式的命名為name
2.1.4 Function.prototype.call或Function.prototype.apply呼叫
跟普通的函式呼叫相比,用Function.prototype.call或Function.prototype.apply可以動態地改變傳入函式的this。
var obj1 = {
name: 'sven',
getName: function() {
return this.name;
}
}
var obj2 = {
name: 'ane'
};
console.log(obj1.getName());//'sven'
console.log(obj1.getName.call(obj2)); //'ane' 將this指標指向obj2
2.1 丟失的this
這是一個經常會遇到的問題,比如下面的程式碼:
var obj = {
myName: 'sven',
getName: function() {
return this.myName
}
};
console.log(obj.getName());//'sven'
var getName2 = obj.getName;
console.log(getName2());//undefined
呼叫obj.getName,getName方法時作為obj物件的屬性呼叫的,因此getName中的this指向obj物件。
而getName2 = obj.getName這種方式,是普通函式呼叫方式,此時的this是指向全域性window的。
下面的一段程式碼很有意思,原本是這樣的:
var getId=function(id){
return document.getElementById(id) //作為document的物件呼叫
}
console.log(getId("div1"));
就想把上面的getId直接通過
var getId=document.getElenmentById
但是卻是實現不了的,如下面的程式碼:
<body>
<div id="div1">我是一個div</div>
</body>
<script type="text/javascript">
var getId=document.getElementById; //作為普通函式呼叫
getId('div1')
</script>
</html>
丟擲了一個錯誤:Uncaught TypeError: Illegal invocation(…)
原因在於:許多引擎的document.getElenmetById方法內部實現中需要用到this。這個this本來被期望指向document,當getElementById方法作為document物件的屬性被呼叫時,方法內部的this確實是指向document的。
但當getId來引用document.getElementById之後,再呼叫getId,此時就成了普通函式呼叫,此時的this指向了window,而不是原來的document。
這種情況可以通過apply修改:
document.getElementById=(function(func){
return function(){
return func.apply(document,arguments);
}
})(document.getElementById);
var getId=document.getElementById;
var div=getId('div1');
console.log(div);
通過document當作this傳入getId函式中,幫助修正this