有没有办法利用JavaScript工厂函数中使用原型方法的性能优势?

use*_*297 1 javascript performance factory prototype factory-pattern

我正在寻找以类似于Java类的方式编写面向对象的JavaScript(JS)代码的最佳方法.

工厂函数(FF)看起来像是一种在JS中提供类类功能的非常有前途的方式,到目前为止,我一直在构建它们:

function FF(constructorArg)
{
   var _privateName = constructorArg;

   var publicMessage = "Hello StackOverflow";

   function publicMethodGetName() {
      return _privateName;
   }

   return {
      publicMethodGetName: publicMethodGetName,
      publicMessage: publicMessage
   };
}
Run Code Online (Sandbox Code Playgroud)

但是,我最近发现,与原型方法不同,这种样式的FF为每个FF实例重新创建每个方法,因此可能会降低性能.

这个优秀主题的第二个答案中,Eric Elliot谈到了FF:

如果将原型存储在父对象上,这可能是动态交换功能的好方法,并为对象实例化启用非常灵活的多态性.

我在网上找不到这个例子.任何人都可以向我解释我如何使用上面的FF来做到这一点?

如果我知道将从同一个FF创建许多对象,这是否意味着我可以将FF切换为使用原型方法?

jfr*_*d00 6

我正在寻找以类似于Java类的方式编写面向对象的JavaScript(JS)代码的最佳方法.

这是你的第一个错误.Javascript是一种非常不同的语言,您不应该尝试在Javascript中模拟其他语言.当我来自C++时,我做了类似的事情,这是一个很大的错误.你需要做的是学习Javascript的优势以及如何在用Javascript编写时最好地解决问题"Javascript方式".我知道,用其他语言你已经知道的方式做事情是一种自然倾向,但这是一个错误.因此,不要试图用"Java方式"做事,而是要问Javascript中解决某些特定问题的最佳方法是什么.

例如,在大量对象上拥有方法的最低内存方式是使用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;
    }
};
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用传统的new运算符创建一个对象,如下所示:

var m = new MyCntr(10);
console.log(m.getCntr());    // 10
Run Code Online (Sandbox Code Playgroud)

或者,您可以将其用作工厂功能:

var m = MyCntr(10);
console.log(m.getCntr());    // 10
Run Code Online (Sandbox Code Playgroud)

请记住,使用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
Run Code Online (Sandbox Code Playgroud)

或者,您可以将其用作工厂功能:

var m = MyCntr(10);
console.log(m.getCntr());    // 10
Run Code Online (Sandbox Code Playgroud)

这两种语法都创建了完全相同的对象定义和原型.


总而言之,不使用原型的内存消耗通常不是什么大问题,除非你有很多方法和很多对象,并且不使用原型有一些显着的优点.一个重要的一点是,您可以在构造函数创建的闭包中拥有真正的私有实例数据.以下是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这确实使用了更多的内存,因为构造函数(包含cntr变量)创建了持久闭包,并且构成方法的每个函数都有新的实例.但是,它在记忆方面并没有太大的不同.如果你没有数以万计的cntr对象,那么内存消耗差异可能是无关紧要的.Doug Crawford是这种Javascript编码风格的冠军之一.你可以看到他早期writeups的一个关于这个问题在这里:http://javascript.crockford.com/private.html并有一定的克罗克福德的意见进行一些讨论在这里.在某个地方有一个Crockford视频(我现在看不到它),他在那里捍卫非原型风格.


所以,问你是否可以结合两者的优点是合乎逻辑的.不,不是真的.为了访问构造函数闭包,必须在构造函数的词法范围中定义方法,为此,它们不在原型上.试图将它们分配给构造函数中的原型会造成一个混乱,这个混乱会受到各种错误的影响,所以这也是不可行的.

使用ES6 weakMap对象,可以在使用原型时创建私有实例数据,但我会说它通常更麻烦,因为它只是编写代码和访问私有数据变得复杂 - 但它是可能的.你可以看到使用私有变量的实现weakMap私有实例成员weakmaps在JavaScript隐藏与ECMAScript的6 WeakMaps实施细则.


我提出我的意见,隐藏你通过某种方式消除需要创建一个新对象的事实new并不是真的非常像Javascript或非常类似于OO.对于在创建新对象时以及刚刚调用函数时读取代码的人来说,这应该是显而易见的.使用new大写的构造函数使得在Javascript中非常明显,我认为这是一件好事.我不会故意在我的代码中避免这种明显的意图.


如果将原型存储在父对象上,这可能是动态交换功能的好方法,并为对象实例化启用非常灵活的多态性.

确实如果你使用原型,它是一个很好的封装对象,它包含了对象的所有方法,它可以使一些多态性更容易.但是,如果你不使用原型,你也可以做多态.

例如,假设您有三个不使用原型的对象,并且它们在构造函数中分配了所有方法,并且您希望创建这三个方法的mixin组合.

您可以创建一个对象,然后只调用其他构造函数,它们将自动初始化您的对象,使其成为具有所有三个行为的组合对象(假设实例数据属性或方法名称没有冲突).

function MyCombo() {
    ObjectA.call(this);
    ObjectB.call(this);
    ObjectC.call(this);
}
Run Code Online (Sandbox Code Playgroud)

这将调用三个构造函数中的每一个,并且它们将各自初始化它们的方法和实例变量.在某些方面,这比使用原型更简单.

如果您有使用原型的对象,那么您可以这样做:

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();
Run Code Online (Sandbox Code Playgroud)

如果我知道将从同一个FF创建许多对象,这是否意味着我可以将FF切换为使用原型方法?

这取决于哪些权衡最适合您的代码.如果您有1000个方法并且创建了20,000个该类型的对象,那么我会说您可能想要使用原型,以便您可以共享所有这些方法.如果您没有那么多方法或没有创建大量这些类型的对象或者您有足够的内存,那么您可能希望优化其他一些特性(如私有数据)而不使用原型.这是一个权衡空间.没有一个正确的答案.