鍍金池/ 教程/ HTML/ 構造函數(shù)
arguments 對象
類型轉換
構造函數(shù)
instanceof 操作符
自動分號插入
為什么不要使用 eval
對象使用和屬性
作用域與命名空間
this 的工作原理
typeof 操作符
相等與比較
閉包和引用
數(shù)組遍歷與屬性
Array 構造函數(shù)
原型
hasOwnProperty 函數(shù)
undefined 和 null
函數(shù)聲明與表達式
setTimeout 和 setInterval
for in 循環(huán)

構造函數(shù)

JavaScript 中的構造函數(shù)和其它語言中的構造函數(shù)是不同的。 通過 new 關鍵字方式調用的函數(shù)都被認為是構造函數(shù)。

在構造函數(shù)內部 - 也就是被調用的函數(shù)內 - this 指向新創(chuàng)建的對象 Object。 這個新創(chuàng)建的對象的 prototype 被指向到構造函數(shù)的 prototype。

如果被調用的函數(shù)沒有顯式的 return 表達式,則隱式的會返回 this 對象 - 也就是新創(chuàng)建的對象。

    function Foo() {
        this.bla = 1;
    }

    Foo.prototype.test = function() {
        console.log(this.bla);
    };

    var test = new Foo();

上面代碼把 Foo 作為構造函數(shù)調用,并設置新創(chuàng)建對象的 prototypeFoo.prototype

顯式的 return 表達式將會影響返回結果,但僅限于返回的是一個對象。

    function Bar() {
        return 2;
    }
    new Bar(); // 返回新創(chuàng)建的對象

    function Test() {
        this.value = 2;

        return {
            foo: 1
        };
    }
    new Test(); // 返回的對象

譯者注new Bar() 返回的是新創(chuàng)建的對象,而不是數(shù)字的字面值 2。 因此 new Bar().constructor === Bar,但是如果返回的是數(shù)字對象,結果就不同了,如下所示

    function Bar() {
        return new Number(2);
    }
    new Bar().constructor === Number

譯者注這里得到的 new Test()是函數(shù)返回的對象,而不是通過new關鍵字新創(chuàng)建的對象,因此:

    (new Test()).value === undefined
    (new Test()).foo === 1

如果 new 被遺漏了,則函數(shù)不會返回新創(chuàng)建的對象。

    function Foo() {
        this.bla = 1; // 獲取設置全局參數(shù)
    }
    Foo(); // undefined

雖然上例在有些情況下也能正常運行,但是由于 JavaScript 中this的工作原理, 這里的 this 指向全局對象

工廠模式

為了不使用 new 關鍵字,構造函數(shù)必須顯式的返回一個值。

    function Bar() {
        var value = 1;
        return {
            method: function() {
                return value;
            }
        }
    }
    Bar.prototype = {
        foo: function() {}
    };

    new Bar();
    Bar();

上面兩種對 Bar 函數(shù)的調用返回的值完全相同,一個新創(chuàng)建的擁有 method 屬性的對象被返回, 其實這里創(chuàng)建了一個閉包。

還需要注意, new Bar()不會改變返回對象的原型(譯者注也就是返回對象的原型不會指向 Bar.prototype)。 因為構造函數(shù)的原型會被指向到剛剛創(chuàng)建的新對象,而這里的 Bar 沒有把這個新對象返回(譯者注:而是返回了一個包含 method 屬性的自定義對象)。

在上面的例子中,使用或者不使用 new 關鍵字沒有功能性的區(qū)別。

譯者注上面兩種方式創(chuàng)建的對象不能訪問 Bar 原型鏈上的屬性,如下所示:

    var bar1 = new Bar();
    typeof(bar1.method); // "function"
    typeof(bar1.foo); // "undefined"

    var bar2 = Bar();
    typeof(bar2.method); // "function"
    typeof(bar2.foo); // "undefined"

通過工廠模式創(chuàng)建新對象

我們常聽到的一條忠告是不要使用 new 關鍵字來調用函數(shù),因為如果忘記使用它就會導致錯誤。

為了創(chuàng)建新對象,我們可以創(chuàng)建一個工廠方法,并且在方法內構造一個新對象。

    function Foo() {
        var obj = {};
        obj.value = 'blub';

        var private = 2;
        obj.someMethod = function(value) {
            this.value = value;
        }

        obj.getPrivate = function() {
            return private;
        }
        return obj;
    }

雖然上面的方式比起 new 的調用方式不容易出錯,并且可以充分利用私有變量帶來的便利, 但是隨之而來的是一些不好的地方。

  1. 會占用更多的內存,因為新創(chuàng)建的對象不能共享原型上的方法。
  2. 為了實現(xiàn)繼承,工廠方法需要從另外一個對象拷貝所有屬性,或者把一個對象作為新創(chuàng)建對象的原型。
  3. 放棄原型鏈僅僅是因為防止遺漏 new 帶來的問題,這似乎和語言本身的思想相違背。

總結

雖然遺漏 new 關鍵字可能會導致問題,但這并不是放棄使用原型鏈的借口。 最終使用哪種方式取決于應用程序的需求,選擇一種代碼書寫風格并堅持下去才是最重要的。