问题
I'm looking for the best way to write object-oriented JavaScript (JS) code in a way that is similar to Java classes.
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:
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.
In the second answer in this excellent thread, Eric Elliot says about FFs:
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?
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?
回答1:
I'm looking for the best way to write object-oriented JavaScript (JS) code in a way that is similar to Java classes.
This is your first mistake. Javascript is a very different language and you should not be trying to emulate some other language in Javascript. I kind of did something similar when I came from C++ and it was a big mistake. 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. 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.
For example, the lowest memory way to have methods on a large number of objects is by using Javascript's prototype. You can use the prototype either with manual assignments to the prototype or with the newer ES6 class
syntax. 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:
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:
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.
// 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. 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. Doug Crawford is one of the champions of such a Javascript coding style. 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. There's a Crockford video somewhere (I can't see to find it right now) where he defends the non-prototype style.
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. 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.
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. 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. 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?
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. 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.
来源:https://stackoverflow.com/questions/38426494/is-there-a-way-to-exploit-the-performance-advantages-of-using-prototype-methods