第2章this、call和apply
跟別的語言大相徑庭的是,javascript的this總是指向一個對象,而具體指向哪個對象是運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境。
this的指向
除去不常用的with和eval的情況,具體到實際應用中,this的指向大致可以分爲以下4種
o 作爲對象的方法調用
o 作爲普通函數調用
o 構造器調用
o Function.prototype.call或Function.prototype.apply調用。
1. 作爲對象方法的調用
當作爲對象方法調用時,this指向該對象:
var obj = {
a:1,
getA:function(){
alert(this==obj);
alert(this.a);
}
}
obj.getA();
2. 作爲普通函數調用
當函數不作爲對象的屬性被調用時,也就是我們常說的普通函數方式,此時的this總是指向全局對象。在瀏覽器js裏,這個全局對象是window對象
如:
window.name = ‘globalName’;
var getName = function(e){
return this.name;
}
console.log(getName()); //輸出:globalName
或者
window.name = ‘globalName’;
var myObject = {
name:’sven’,
getName:function(){
return this.name;
}
}
var getName = myObject.getName;
console.log(getName()); //輸出globalName
有時候我們會遇到一些困擾,比如在div節點的事件函數內部,有一個局部的callback方法,callback方法作爲普通方法調用內部的this指向window,但我們往往是想讓它指向div節點。
如下:
<html>
<body>
<div id=”div1”>我是一個div</div>
</body>
<script>
window.id = “window”;
document.getElementById(“xxx”).onclick = function(){
alert(this.id); //輸出:div
var callback = function(){
alert(this.id); //輸出:window
};
callback();
}
</script>
</html>
此時有一種簡單的解決方案,就是用一個變量保存div節點的引用
document.getElementById(“xxx”).onclick = function(){
var that = this;
alert(this.id); //輸出:div1
var callback = function(){
alert(that.id); //輸出:div1
};
callback();
}
在ECMAScript5的strict模式下,這種情況下的this已經被規定爲不會指向全局對象,而是undefined。
function func(){
“use strict”
alert(this);//undefined
}
3. 構造器調用
javascript中沒有類,但是可以從構造器中創建對象,同是也提供了new運算符,使得構造器看起來更像一個類
除了宿主提供的一些內置函數,大部分javascript函數都可以當作構造器使用。構造器的外表跟普通函數一模一樣,它們的區別在於被調用的方式。當用new運算符調用函數時該函數會返回一個對象。
var MyClass = function(){
this.name = ‘sven’;
}
var obj = new MyClass();
alert(obj.name); //輸出:sven
注意:如果new 調用構造器時還要注意一個問題,如果構造器顯式地返回一個object類型的對象,那麼此次運算結果最終會返回這個對象(參見之前的總結)
var MyClass = function(){
this.name = ‘sven’;
return {
name:’anne’
}
}
var obj = new MyClass();
alert(obj.name); //輸出:anne
注意如果構造器不顯式返回的不是一個對象類型的數據就不會造成上述問題。
如:
var MyClass = function(){
this.name = ‘sven’;
return “question”;
}
var obj = new MyClass();
alert(obj.name); //輸出:sven
4. Functon.prototype.call或Function.prototype.apply調用
動態改變傳入函數的this
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 變成普通函數調用啦
//正如document.getElementById();我們來簡化這個寫法的時候是這樣做的
var getId = function(id){
return document.getElementById(id);
}
而不是簡單的這樣
var getId = document.getElementById; //普通函數調用啦,上下文指向的不是document而是window對象啦
call和apply的區別就是前者的參數對應傳遞是單個的,後者以數組的形式傳遞函數的參數。
借用其他對象的方法
借用方法的第一種場景就是借用構造函數,通過這種技術,可以以實現類似繼承的效果:
var A = function(name){
this.name = name;
}
var B = function(){
A.apply(this,arguments);
}
B.prototype.getName = function(){
return this.name;
}
var b = new B(‘sven’);
console.log(b.getName());<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
第二種運用場景跟我們的關係更加密切:
函數的參數列表arguments是一個類數組對象,雖然它也有“下標”,但它並非真正的數組,所以也不能像數組一樣,進行排序操作或者往集合裏添加一個新的元素。這種情況下我們常常借用Array.prototype對象上的方法。
如
(function(){
Array.prototype.push.call(arguments,3);
console.log(arguments); //[1,2,3]
})(1,2)
這種數組方法借用是可以在絕大多數瀏覽器中順利執行,但由於引擎的內部實現存在差異,如果在低版本的IE中執行,必須顯式地給對象a設置屬性length。
所以這種數組方法的借用要滿足:
o 對象本身要可以存取屬性
o 對象的length屬性可讀寫