簡體   English   中英

有沒有一種方法可以利用在JavaScript工廠函數中使用原型方法的性能優勢?

[英]Is there a way to exploit the performance advantages of using prototype methods in JavaScript factory functions?

我正在尋找以類似於Java類的方式編寫面向對象的JavaScript(JS)代碼的最佳方法。

工廠函數(FF)看起來是在JS中提供類功能的一種非常有前途的方式,到目前為止,我一直在像這樣構建它們:

function FF(constructorArg)
{
   var _privateName = constructorArg;

   var publicMessage = "Hello StackOverflow";

   function publicMethodGetName() {
      return _privateName;
   }

   return {
      publicMethodGetName: publicMethodGetName,
      publicMessage: publicMessage
   };
}

但是, 我最近發現,與原型方法不同,這種樣式的FF為每個FF實例重新創建每個方法,因此可能會降低性能

這個出色的話題的第二個答案中,埃里克·埃利奧特(Eric Elliot)談到了FF:

如果將原型存儲在父對象上,那么這可能是動態交換功能並為對象實例化啟用非常靈活的多態性的好方法。

我在網上找不到任何示例。 誰能向我解釋我如何使用上面的FF做到這一點?

如果我知道將從同一個FF創建許多對象,這是否意味着我可以將該FF切換為使用原型方法?

我正在尋找以類似於Java類的方式編寫面向對象的JavaScript(JS)代碼的最佳方法。

這是您的第一個錯誤。 Javascript是一種非常不同的語言,因此您不應該嘗試在Javascript中模仿其他語言。 當我來自C ++時,我做了類似的事情,這是一個很大的錯誤。 相反,您需要做的是學習Java腳本的優勢,以及在用Java腳本編寫時如何最好地以“ Java腳本方式”解決問題。 我知道,以其他語言已經知道的方式去做事情是一種自然的趨勢,但這是一個錯誤。 因此,與其嘗試用“ Java方式”做事,不如問Java腳本中解決某些特定問題的最佳方法是什么。

例如,對大量對象具有方法的最低內存方式是使用Javascript的原型。 您可以通過手動分配原型或使用較新的ES6 class語法來使用原型。 兩者都在原型對象上創建方法,然后在所有實例之間有效地共享它們。

例如,您可以將工廠函數與典型的原型一起使用,如下所示:

// constructor and factory function definition
function MyCntr(initialCnt) {
    if (!(this instanceof MyCntr)) {
        return new MyCntr(initialCnt);
    } else {
        // initialize properties
        this.cntr = initialCnt || 0;
    }
}

MyObject.prototype = {
    getCntr: function() {
        return this.cntr++;
    },
    resetCntr: function() {
        this.cntr = 0;
    }
};

然后,您可以使用傳統的new運算符創建一個對象,如下所示:

var m = new MyCntr(10);
console.log(m.getCntr());    // 10

或者,您可以將其用作工廠功能:

var m = MyCntr(10);
console.log(m.getCntr());    // 10

請記住,通過ES6(或編譯器),您也可以使用ES6類語法:

class MyCntr {
    constructor(initialCnt) {
        if (!(this instanceof MyCntr)) {
            return new MyCntr(initialCnt);
        } else {
            // initialize properties
            this.cntr = initialCnt || 0;
        }
    }

    getCntr() {
        return this.cntr++;
    }

    resetCntr() {
        this.cntr = 0;
    } 
}

var m = new MyCntr(10);
console.log(m.getCntr());    // 10

或者,您可以將其用作工廠功能:

var m = MyCntr(10);
console.log(m.getCntr());    // 10

這兩種語法都會創建完全相同的對象定義和原型。


綜上所述,不使用原型的內存消耗通常不是什么大問題,除非您同時擁有許多方法和大量對象,並且不使用原型具有一些明顯的優勢。 一個很大的問題是,在構造函數創建的閉包中可以真正擁有私有實例數據。 這是與以前的示例相同的實現方式,其中cntr實例變量是真正私有的。

// constructor and factory function definition
function MyCntr(initialCnt) {
    // truly private instance variable
    var cntr;

    if (!(this instanceof MyCntr)) {
        return new MyCntr(initialCnt);
    } else {
        // initialize properties
        cntr = initialCnt || 0;
    }

    this.getCntr = function() {
        return cntr++;
    }

    this.resetCntr = function() {
        cntr = 0;
    }
}

這確實會占用更多的內存,因為構造函數(包含cntr變量)創建了持久的閉包,並且每個函數都有新的實例來構成方法。 但是,這在內存上沒有太大的區別。 如果您沒有成千上萬的cntr對象,則內存消耗差異可能是無關緊要的。 道格·克勞福德(Doug Crawford)是這種Java編碼風格的擁護者之一。 你可以看到他早期writeups的一個關於這個問題在這里: http://javascript.crockford.com/private.html並有一定的克羅克福德的意見進行一些討論在這里 某處有一個Crockford視頻(我現在看不到),在那里他捍衛了非原型風格。


因此,邏輯上要問一下您是否可以結合兩者的優點。 不,不是。 為了能夠訪問構造函數閉包,必須在構造函數的詞法范圍內定義方法,而要做到這一點,它們就不在原型上。 嘗試將它們分配給構造函數內部的原型會造成混亂,該混亂會受到各種錯誤的影響,因此也不可行。

使用ES6的weakMap對象,可以在使用原型時制作私有實例數據,盡管我會說這樣做通常更麻煩,因為這樣做會使編寫代碼和訪問私有數據變得復雜,但這是可能的。 你可以看到使用私有變量的實現weakMap私有實例成員weakmaps在JavaScript隱藏與ECMAScript的6 WeakMaps實施細則


我提供我的意見是隱藏了你被莫名其妙地刪除了需要創建一個新的對象其實new是不是真的非常的Javascript狀或真的很OO-等。 當您創建新對象以及僅調用函數時,對於閱讀代碼的人來說應該是顯而易見的。 new與首字母大寫的構造函數一起使用在Javascript中非常明顯,我認為這是一件好事。 我不會故意避免在我的代碼中避免明顯的意圖。


如果將原型存儲在父對象上,那么這可能是動態交換功能並為對象實例化啟用非常靈活的多態性的好方法。

的確,如果使用原型,那么它是一個很好的封裝對象,其中包含該對象的所有方法,並且可以使某些多態性變得更容易。 但是,如果您不使用原型,也可以進行多態。

例如,假設您有三個不使用原型的對象,並且它們在其構造函數中分配了所有方法,並且您想創建這三個對象的混合組合。

您可以創建一個對象,然后調用其他構造函數,它們將自動將您的對象初始化為具有這三種行為的組合對象(假設實例數據屬性或方法名稱沒有沖突)。

function MyCombo() {
    ObjectA.call(this);
    ObjectB.call(this);
    ObjectC.call(this);
}

這將調用三個構造函數中的每一個,並且它們將分別初始化其方法和實例變量。 在某些方面,這比使用原型要簡單得多。

如果您有使用原型的對象,則可以執行以下操作:

function MyCombo() {
    ObjectA.call(this);
    ObjectB.call(this);
    ObjectC.call(this);
}

Object.assign(MyCombo.prototype, ObjectA.prototype, ObjectB.prototype, 
              ObjectC.prototype, {myComboMethod: function() {...}});

var x = new MyCombo();
x.methodA();
x.methodB();

如果我知道將從同一個FF創建許多對象,這是否意味着我可以將該FF切換為使用原型方法?

這取決於哪種權衡最適合您的代碼。 如果您有1000個方法並正在創建20,000個該類型的對象,那么我想您可能想使用原型,以便可以共享所有這些方法。 如果您沒有那么多方法,或者沒有創建大量此類對象,或者您有足夠的內存,那么您可能想要針對其他一些特性(例如私有數據)進行優化,而不使用原型。 這是一個權衡的空間。 沒有一個正確的答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM