1. 程式人生 > >《javascript設計模式與開發實踐》閱讀筆記(三)

《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