2

Probably many of you tried to achieve encapsulation in JavaScript. The two methods known to me are:

a bit more common I guess:

var myClass(){
  var prv //and all private stuff here
  //and we don't use protoype, everything is created inside scope
  return {publicFunc:sth};
}

and second one:

var myClass2(){
  var prv={private stuff here}
  Object.defineProperty(this,'prv',{value:prv})
  return {publicFunc:this.someFunc.bind(this)};
}
myClass2.prototype={
  get prv(){throw 'class must be created using new keyword'},
  someFunc:function(){
    console.log(this.prv);
  }
}
Object.freeze(myClass)
Object.freeze(myClass.prototype)

So, as second option is WAY more convenient to me (specifically in my case as it visually separates construction from workflow) the question is - are there any serious disadvantages / leaks in this case? I know it allows external code to access arguments of someFunc by

myClass.protoype.someFunc.arguments

but only in case of sloppily executed callbacks (synchronously inside caller chain). Calling them with setTimeout(cb,0) breaks chain and disallows to get arguments as well as just returning value synchronously. At least as far as i know.

Did I miss anything? It's kind of important as code will be used by external, untrusted user provided code.

Lapsio
  • 6,384
  • 4
  • 20
  • 26
  • `It's kind of important as code will be used by external, untrusted user provided code.` - What exactly do you mean? Are you going to `eval` user-provided code? If you do, realize that any modern debugger can view/edit "private" methods/variables. – soktinpk Jun 30 '14 at 15:18
  • i don't care about accessing them inside debugger. I only don't want to make user code able to access them programatically. – Lapsio Jun 30 '14 at 15:19

3 Answers3

4

I like to wrap my prototypes in a module which returns the object, this way you can use the module's scope for any private variables, protecting consumers of your object from accidentally messing with your private properties.

var MyObject = (function (dependency) {

  // private (static) variables
  var priv1, priv2;

  // constructor
  var module = function () {
    // ...
  };

  // public interfaces
  module.prototype.publicInterface1 = function () {
  };

  module.prototype.publicInterface2 = function () {
  };

  // return the object definition
  return module;

})(dependency);

Then in some other file you can use it like normal:

obj = new MyObject();

Any more 'protecting' of your object is a little overkill for JavaScript imo. If someone wants to extend your object then they probably know what they're doing and you should let them!

As redbmk points out if you need private instance variables you could use a map with some unique identifier of the object as the key.

Daniel Imms
  • 47,944
  • 19
  • 150
  • 166
  • thanks, yes I'm also using something similar when I'm creating dynamically fake Window objects to make them behave and look like real window, however I'd like to avoid huge functions and access private vars by this – Lapsio Jun 30 '14 at 15:22
  • yeah - the point is I'm afraid users may know what they're doing even too well - I'm going to execute few user provided codes and the point is to control access between them. I don't care that location.reload cannot be blocked programatically, or whole location class. I'm just going to isolate whole DOM and user provided javascript from browser. Snadbox atm spawns about 2.5 seconds rewriting whole window with monitored hooks. i know iFrames would be 1000 times easier but I'm going to allow sharing huge binary arrays with native performance. Basically it's for sandbox with permissions – Lapsio Jun 30 '14 at 15:27
  • The problem with using "private" variables in this manor is that all instances of your module use the same private variables. They are not instance variables anymore. – Greg Burghardt Jun 30 '14 at 16:45
  • You could, however, give each new instance a unique id, then have a private, static hash to store private variables unique to that instance. Then they wouldn't be accessible from outside of the instance, if that's important. Look it up using something like `priv1[this.instanceId]`. – redbmk Jun 30 '14 at 17:02
  • these modules will be created only once and constructor gets special root object. Eventually user will be able to spawn own module inside this module but then it'll have different root so it won't be the same. All these modules will be created before loading external code – Lapsio Jun 30 '14 at 18:50
1

So, as second option is WAY more convenient to me (specifically in my case as it visually separates construction from workflow) the question is - are there any serious disadvantages / leaks in this case?

Hm, it doesn't really use the prototype. There's no reason to "encapsulate" anything here, as the prototype methods will only be able to use public properties - just like your untrusted code can access them. A simple

function myClass2(){
  var prv = // private stuff here
  Object.defineProperty(this, 'prv', {value:prv})
  // optionally bind the publicFunc if you need to
}
myClass2.prototype.publicFunc = function(){
  console.log(this.prv);
};

should suffice. Or you use the factory pattern, without any prototypes:

function myClass2(){
  var prv = // private stuff here
  return {
    prv: prv,
    publicFunc: function(){
      console.log(this.prv); // or even just `prv`?
    }
  };
}

I know it allows external code to access arguments of someFunc by myClass.protoype.someFunc.arguments

Simply use strict mode, this "feature" is disallowed there.

It's kind of important as code will be used by external, untrusted user provided code.

They will always get your secrets if the code is running in the same environment. Always. You might want to try WebWorkers instead, but notice that they're still CORS-privileged.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I'm not going to use strict. It disables too many useful features like eg. with (){}. How could they access my secrets? I know javascript doesn't really allow any Sandboxing and it's impossible to isolate global window, so instead I decided to close important things inside sandbox, destroying whole environment, removing all window properties and storing in local secret. Then freezing whole window recursively and providing Window objects containing only allowed features/wrappers to clients. To achieve max compability they keep original API, these are just simple hooks altering arguments in fly. – Lapsio Jun 30 '14 at 18:34
  • kind of difficult for debugging as it damages dev tools in all browsers except ff. But I can't see any way to steal secrets including hardcore ways like altering Function.protoype.call etc. Everything is frozen and can't be altered. This code is called always before loading external code so it can destroy all connections. Monkey patching is bad practice so noone should complain about freezing all original prototypes – Lapsio Jun 30 '14 at 18:38
  • CORS is not an issue - after all user will be able to load client from ANYWHERE. This system will support external repositories. It's difficult to compare to anything. Maybe closest to what I'm going to achieve is(was) iGoogle. Website managing multiple "internal" websites. But solutions like WebWorkers or iFrames don't provide enough performance as it's multimedia app. For example we already have eg. FLAC and other unsupported formats decoding made in pure JS so it needs native performance as well as drawing visualizations using WebGL. DOM tree will be broken by altering document object. – Lapsio Jun 30 '14 at 18:46
  • The easiest thing I could think of would be `myClass2.toString()`. Or even loading your code via XHR. Just saying that you too easily overlook a thing. Notice there's a similar question: [Making WebWorkers a safe environment](http://stackoverflow.com/q/10653809/1048572) – Bergi Jun 30 '14 at 18:46
  • For better advise though, you should ask a new question in which you state your requirements, what kind of app you want to construct, what the untrusted user code should do, and especially which data/APIs you want to hide and how you want to expose (parts of) it. – Bergi Jun 30 '14 at 18:50
  • nothing will be hardcoded. private "key" is object pointer here, pointer cannot be stolen easily. It's only about creating two classes (one class is one client, second class is another client) and to allow _native_ communication between them but only if both will agree for that. Yes I know there is A LOT of things to overlook. That's why I'm really careful. Also it'll be open source so I hope all flaws will be detected possibly soon. It'll implement secure fallbacks like allowing only trusted repositories in case of detected intrusion. Sure, i can post another topic about this sandbox. – Lapsio Jun 30 '14 at 18:57
0

To enforcing encapsulation in a language that doesn't properly support private, protected and public class members I say "Meh."

I like the cleanliness of the Foo.prototype = { ... }; syntax. Making methods public also allows you to unit test all the methods in your "class". On top of that, I just simply don't trust JavaScript from a security standpoint. Always have security measures on the server protecting your system.

Go for "ease of programming and testing" and "cleanliness of code." Make it easy to write and maintain, so whichever you feel is easier to write and maintain is the answer.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92