Four ways to deal with private members in JavaScript

来源:互联网 发布:绝地求生淘宝不卖了 编辑:程序博客网 时间:2024/05/14 06:54

Author:
Tim Buschtöns

http://eclipsesource.com/blogs/2013/07/05/private-members-in-javascript/


I’ve been programming JavaScript for a few years now, and the one thing that definitely bothered me from the beginning was the lack of built-in support for private object members. The feeling that anyone using your code (including yourself) could by accident or intent mess with the internals of your objects isn’t great. Not just can anyone change the value of any property at any time, even methods can be overwritten from outside the instance. I’ve seen and toyed with a few different ways to solve this issues, and these are my preliminary findings for each method.

This is slightly advanced JavaScript, so I assume you know how prototypes, context,scopes and closures work. To keep things simple, this is only about creating raw JavaScript objects with private members (methods/properties) using the newoperator, ignoring most other aspects of OO-programming patterns, or any JavaScript libraries/utils supporting them.

This list does in no way claim to be comprehensive, so if you know any other approaches, I would be thrilled to hear from you in the comments. These methods are mostly non-exclusive and may even complement each other, but I wouldn’t go wild and combine them in endless combinations within the same project.

Method 1: “Imagined” privacy

Possibly the best known way to achieve some form of privacy in JavaScript is to simply prefix all “private” members with “_” or something similar. It’s very comfortable to use since it doesn’t add much (or any) complexity to your code, and requires no additional operations or memory at runtime. The obvious downside is that it doesn’t provide any real privacy at all, it only marks the members as “don’t touch that” for any developers working with the code. It’s what I do most of the time, though there is a minor variation that I have been trying out: Instead of prefixing the actual properties, I create a single property called “_” which is an object that holds all the other “private” properties. (Let’s call that Method 1b.)

Examples:

MyClass = function( a, b ) {  this._a = a;  this._b = b;};

Vs.

MyClass = function( a, b ) {  this._ = {    "a" : a,    "b" : b  };};

This at least groups all private properties together which I think looks cleaner in the constructor and debugger. However, you always have to type an additional character to access the property ( i.e. this._a, vs. this._.a). More importantly though, this way you can not inherit anything from your prototype, which is especially bad for functions, but they would not have access to the right “this” object anyway.

Method 2: Private “static” methods

Ignoring properties for a moment (you could use Method 1 for them), there is a relatively easy way to create actual private methods for your JavaScript “class” using closures:

(function(){  MyClass = function( a, b ) { [...] };  MyClass.prototype.sum = function() {    return add( this._a, this._b );  };  var add = function( a, b ) {    return a + b;  };}());

Now only methods defined within that outer function scope are able to access the “add” function. This is an excellent approach for small helper functions that don’t manipulate the instance, something that would be a private static method in Java. However, if you do need access to the instance, it’s a lot more complicated. This won’t work:

(function(){  MyClass = function( a, b ) { [...] };  MyClass.prototype.sum = function() {    return internalCalcSum();  };  var internalCalcSum = function() {    return this._a + this._b; // Wrong "this"!!  };}());

You can certainly make it work by giving the instance as a parameter…

  var internalCalcSum = function( myClass ) {    return myClass._a + myClass._b;  };

…or use call

  MyClass.prototype.sum = function() {    return internalCalcSum.call( this );  };

… which I like much better, but isn’t great either.

Method 3: Real private properties and methods

This is the method recommended by Douglas Crockford. If you want the real deal, this is it:

MyClass = function( a, b ) {   this.sum = function() {    return internalCalcSum();  };  var internalCalcSum = function() {    return a + b;  };};

“a”, “b” and “internalCalcSum” are private, while “sum” is public. Simply put, all public members have to be created in the constructor by attaching them to “this”, while all private members are just local variables hat can be accessed by closure. If a private method needs to access a public property or method (which I don’t think is good style) it would require an additional closure like “var that = this;“.

It sure is bulletproof, but I personally don’t like it because you can not use prototypes at all. (You can for methods that don’t require access to any private members, but those are usually rare). For one, I like to write my methods in one big object literal:

MyClass.prototype = {  myFunc1 : function() { [...] },  myFunc2 : function() { [...] },  myFunc3 : function() { [...] }};

This looks very clean to me. Without using the prototype, and assuming you also have some initialization code to execute, good luck organizing this one huge function in a clear manner. (Not saying it can’t be done, but it’s definitely not as simple.)

More importantly though, without using prototypes every instance of your “class” also creates new instances of Function for every single public and private method. This means time and memory. Not necessarily very much, considering modern PCs, but some. Lets look at the memory consumption of 5000 instances of a simple constructor with two private properties (numbers 10 and 20) and three public methods.

Using “imagined” privacy and a prototype:

using-prototype

Using “real” privacy:

not-using-prototype

Yeah, that’s 100KB vs 800KB for just three methods. No that great in my opinion. If you think I made a mistake, here’s the html file, I would love to be proven wrong.

Method 4: Just don’t

Basically the same as “imagined” privacy, but without the prefixes. As I learned Java first, I would not even have considered this a while ago, but why force a concept onto a language that isn’t designed to support it? If you create proper documentation for the code you are writing you can simply choose not to document your “private” members as public API. JsDoc Toolkit for example will ignore all undocumented members by default. This is especially appropriate, in my opinion, if you want your members to be “internal” API, i.e. use them from outside the object, but only within the same project/library/module. You could also document “private” properties – assuming they have remotely useful values – but warn against overwriting.

/** @constructor */MyClass = function( a, b ) {  this.a = a;  this.b = b;};MyClass.prototype = {  /**   * @returns {number} Sum of a and b   */  sum : function() {    return this.a + this.b;  }};

Doesn’t that look nice? The “a” and “b” properties would not have to appear in the generated documentation, or you could add “do not modify” to their description.

Conclusion

I’m still using Method 1 most of the time, but this is for internals of the RAP WebClient which only RAP framework developers have to deal with. It’s more a friendly reminder than anything else.

For objects that are part of any public API I have been using combinations of Methods 1b and 2, except for “static” objects that exist only once. Those can easily have “real” private members using clousers.

I would not recommend Method 3 if you have concerns regarding memory or performance, other than that it’s very much a matter of taste. If you are really afraid of developers using your code in the wrong way it’s the only option, but remember that unless you distribute your code only in an obfuscated form, JavaScript can always be hacked.

If I were to start a completely new project myself, I would use Method 4 in combination with Method 2 for some “real” private methods, and perhaps in some rare cases “_” prefixed members. (Again, “static” objects that can have all “real” privacy by clouser.) This works best if you are planning on well documenting and organizing all your code so that any “misuse” of your API is rare and obvious. I have to admit that I have little experience with this yet, but I did use it in some cases in the RAP WebClient and in my experimental “rap lite” project were it worked just fine.


0 0
原创粉丝点击