简体   繁体   English

有没有一种方法可以利用在JavaScript工厂函数中使用原型方法的性能优势?

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

I'm looking for the best way to write object-oriented JavaScript (JS) code in a way that is similar to Java classes. 我正在寻找以类似于Java类的方式编写面向对象的JavaScript(JS)代码的最佳方法。

Factory functions (FFs) look like a very promising way of offering class-like functionality in JS and, so far, I've been building them like this: 工厂函数(FF)看起来是在JS中提供类功能的一种非常有前途的方式,到目前为止,我一直在像这样构建它们:

function FF(constructorArg)
{
   var _privateName = constructorArg;

   var publicMessage = "Hello StackOverflow";

   function publicMethodGetName() {
      return _privateName;
   }

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

However, I've recently discovered that, unlike prototype methods, this style of FF recreates each method for every FF instance and can thus disadvantage performance . 但是, 我最近发现,与原型方法不同,这种样式的FF为每个FF实例重新创建每个方法,因此可能会降低性能

In the second answer in this excellent thread , Eric Elliot says about FFs: 这个出色的话题的第二个答案中,埃里克·埃利奥特(Eric Elliot)谈到了FF:

If you store prototypes on the parent object, that can be a great way to dynamically swap out functionality, and enable very flexible polymorphism for your object instantiation. 如果将原型存储在父对象上,那么这可能是动态交换功能并为对象实例化启用非常灵活的多态性的好方法。

I can't find any examples of this online. 我在网上找不到任何示例。 Could anyone explain to me how I can do this using my FF above? 谁能向我解释我如何使用上面的FF做到这一点?

If I know that many objects are going to be created from the same FF does this mean that I could switch that FF to using prototype methods? 如果我知道将从同一个FF创建许多对象,这是否意味着我可以将该FF切换为使用原型方法?

I'm looking for the best way to write object-oriented JavaScript (JS) code in a way that is similar to Java classes. 我正在寻找以类似于Java类的方式编写面向对象的JavaScript(JS)代码的最佳方法。

This is your first mistake. 这是您的第一个错误。 Javascript is a very different language and you should not be trying to emulate some other language in Javascript. Javascript是一种非常不同的语言,因此您不应该尝试在Javascript中模仿其他语言。 I kind of did something similar when I came from C++ and it was a big mistake. 当我来自C ++时,我做了类似的事情,这是一个很大的错误。 What you need to do instead is learn the strengths of Javascript and how best to solve problems the "Javascript way" when writing in Javascript. 相反,您需要做的是学习Java脚本的优势,以及在用Java脚本编写时如何最好地以“ Java脚本方式”解决问题。 I know it's a natural tendency to look to do things the way you already know in some other language, but that is a mistake. 我知道,以其他语言已经知道的方式去做事情是一种自然的趋势,但这是一个错误。 So, instead of trying to do things the "Java way", ask what is the best way in Javascript to solve some specific problem. 因此,与其尝试用“ Java方式”做事,不如问Java脚本中解决某些特定问题的最佳方法是什么。

For example, the lowest memory way to have methods on a large number of objects is by using Javascript's prototype. 例如,对大量对象具有方法的最低内存方式是使用Javascript的原型。 You can use the prototype either with manual assignments to the prototype or with the newer ES6 class syntax. 您可以通过手动分配原型或使用较新的ES6 class语法来使用原型。 Both create methods on a prototype object that is then efficiently shared among all instances. 两者都在原型对象上创建方法,然后在所有实例之间有效地共享它们。

For example, you can use a factory function with a typical prototype like this: 例如,您可以将工厂函数与典型的原型一起使用,如下所示:

// 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;
    }
};

Then, you can create an object with the traditional new operator either like this: 然后,您可以使用传统的new运算符创建一个对象,如下所示:

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

Or, you can use it as a factory function: 或者,您可以将其用作工厂功能:

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

Keep in mind that with ES6 (or a transpiler), you can use ES6 class syntax too: 请记住,通过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

Or, you can use it as a factory function: 或者,您可以将其用作工厂功能:

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

Both of these syntaxes create the exact same object definition and prototype. 这两种语法都会创建完全相同的对象定义和原型。


All that said, the memory consumption of not using the prototype is usually not a big deal unless you have both a lot of methods and a lot of objects and there are some significant advantages to not using the prototype. 综上所述,不使用原型的内存消耗通常不是什么大问题,除非您同时拥有许多方法和大量对象,并且不使用原型具有一些明显的优势。 One big one is that you can have truly private instance data in a closure created by your constructor. 一个很大的问题是,在构造函数创建的闭包中可以真正拥有私有实例数据。 Here's the same previous example implemented that way where the cntr instance variable is truly private. 这是与以前的示例相同的实现方式,其中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;
    }
}

This does use more memory as there is both a lasting closure created by the constructor function (that contains the cntr variable) and there are new instances of each of the functions that make up the methods. 这确实会占用更多的内存,因为构造函数(包含cntr变量)创建了持久的闭包,并且每个函数都有新的实例来构成方法。 But, it's not a big difference in memory. 但是,这在内存上没有太大的区别。 If you don't have zillions of cntr objects, the memory consumption difference is likely inconsequential. 如果您没有成千上万的cntr对象,则内存消耗差异可能是无关紧要的。 Doug Crawford is one of the champions of such a Javascript coding style. 道格·克劳福德(Doug Crawford)是这种Java编码风格的拥护者之一。 You can see one of his early writeups on the subject here: http://javascript.crockford.com/private.html and there's some discussion of some of Crockford's views here . 你可以看到他早期writeups的一个关于这个问题在这里: http://javascript.crockford.com/private.html并有一定的克罗克福德的意见进行一些讨论在这里 There's a Crockford video somewhere (I can't see to find it right now) where he defends the non-prototype style. 某处有一个Crockford视频(我现在看不到),在那里他捍卫了非原型风格。


So, it is logical to ask if you can combine the best of both. 因此,逻辑上要问一下您是否可以结合两者的优点。 No, not really. 不,不是。 In order to get access to the constructor closure the methods must be defined in the lexical scope of the constructor and to do that, they are not on the prototype. 为了能够访问构造函数闭包,必须在构造函数的词法范围内定义方法,而要做到这一点,它们就不在原型上。 Trying to assign them to the prototype inside the constructor creates a mess that is subject to all sorts of bugs so that's not feasible either. 尝试将它们分配给构造函数内部的原型会造成混乱,该混乱会受到各种错误的影响,因此也不可行。

Using an ES6 weakMap object, it is possible to make private instance data while using the prototype, though I would say it is generally more trouble that it is worth as it complicates just writing code and accessing private data - but it is possible. 使用ES6的weakMap对象,可以在使用原型时制作私有实例数据,尽管我会说这样做通常更麻烦,因为这样做会使编写代码和访问私有数据变得复杂,但这是可能的。 You can see an implementation of the private variables using a weakMap in Private instance members with weakmaps in JavaScript and Hiding Implementation Details with ECMAScript 6 WeakMaps . 你可以看到使用私有变量的实现weakMap私有实例成员weakmaps在JavaScript隐藏与ECMAScript的6 WeakMaps实施细则


I'd offer my opinion that hiding the fact that you're creating a new object by somehow removing the need for new is not really very Javascript-like or really very OO-like. 我提供我的意见是隐藏了你被莫名其妙地删除了需要创建一个新的对象其实new是不是真的非常的Javascript状或真的很OO-等。 It should be obvious to someone reading the code when you are creating a new object and when you are just calling a function. 当您创建新对象以及仅调用函数时,对于阅读代码的人来说应该是显而易见的。 Using new with a capitalized constructor function makes that very obvious in Javascript and I consider that a good thing. new与首字母大写的构造函数一起使用在Javascript中非常明显,我认为这是一件好事。 I would not purposely look to avoid that obvious intent in my code. 我不会故意避免在我的代码中避免明显的意图。


If you store prototypes on the parent object, that can be a great way to dynamically swap out functionality, and enable very flexible polymorphism for your object instantiation. 如果将原型存储在父对象上,那么这可能是动态交换功能并为对象实例化启用非常灵活的多态性的好方法。

It is true that if you use the prototype, it's a nice encapsulated object that contains all the methods for the object and it can make some polymorphism things easier. 的确,如果使用原型,那么它是一个很好的封装对象,其中包含该对象的所有方法,并且可以使某些多态性变得更容易。 But, you can also do polymorphism if you aren't using the prototype. 但是,如果您不使用原型,也可以进行多态。

For example, suppose you have three objects that don't use the prototype and they assign all of their methods in their constructor and you want to create a mixin combination of all three. 例如,假设您有三个不使用原型的对象,并且它们在其构造函数中分配了所有方法,并且您想创建这三个对象的混合组合。

You can create an object and then just call the other constructors and they will automatically initialize your object to be a combined object with the behavior of all three (assuming no conflicts in instance data property or method names). 您可以创建一个对象,然后调用其他构造函数,它们将自动将您的对象初始化为具有这三种行为的组合对象(假设实例数据属性或方法名称没有冲突)。

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

This will call each of the three constructors and they will each initialize their methods and instance variables. 这将调用三个构造函数中的每一个,并且它们将分别初始化其方法和实例变量。 In some ways, this is event simpler than if using the prototype. 在某些方面,这比使用原型要简单得多。

If you had objects that were using the prototype, then you could do 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();

If I know that many objects are going to be created from the same FF does this mean that I could switch that FF to using prototype methods? 如果我知道将从同一个FF创建许多对象,这是否意味着我可以将该FF切换为使用原型方法?

It depends upon what tradeoffs work best for your code. 这取决于哪种权衡最适合您的代码。 If you had 1000 methods and were creating 20,000 objects of that type, then I'd say you probably want to use the prototype so you could share all those methods. 如果您有1000个方法并正在创建20,000个该类型的对象,那么我想您可能想使用原型,以便可以共享所有这些方法。 If you don't have that many methods or aren't creating a lot of those types of objects or you have plenty of memory, then you may want to optimize for some other characteristic (like private data) and not use the prototype. 如果您没有那么多方法,或者没有创建大量此类对象,或者您有足够的内存,那么您可能想要针对其他一些特性(例如私有数据)进行优化,而不使用原型。 It's a tradeoff space. 这是一个权衡的空间。 There is no single correct answer. 没有一个正确的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM