繁体   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