Javascript and Sharing lexical state
Intro
In this article I want to look at ways to share lexically scoped variables (state) with public prototype functions and properties (as defined via defineProperty) and compare these to the more well-known pattern of using privileged functions.
Related articles
- http://ejohn.org/blog/ecmascript-5-objects-and-properties/
- explains
defineProperty
defineProperty
was introduced with ecmascript 5.
- explains
- http://en.wikipedia.org/wiki/ECMAScript#ECMAScript.2C_5th_Edition
- explains the increasingly convoluted history of javascript and where ecmascript 5 came from
- joy of javascript
- my article where I am a little critical of javascript prototypes
Encapsulation
Encapsulation gives you at least 2 things
- by minimizing what you expose to the outside world, you make it easier to change the implementation of an object (or closure) without unexpected side-effects and ramifications
- and the hiding of state and internals of an object to the outside allows/encourages you to define a clear interface, making it easier to reason about the behaviour of your object with others and by extension the overall project which is using your object.
You get encapsulation via "objects"...
Closures and objects
In the joy of javascript article I showed that you don't need the
new
keyword in javascript to do encapsulation. You can achieve it
simply enough using closures. This is a technique that has been
around probably since lexical scoping was introduced into languages
like scheme and dialects of lisp (including the precursors to Common
Lisp).
Closures can be thought of as objects:
function makeFoo() {
var me = {};
var secret = 'a';
me.method1 = function() {
... do something with 'secret' ...
}
return me;
}
var m = makeFoo();
m.method1();
This can be re-arranged to use javascript's new
keyword to
instantiate objects:
function Foo() {
var me = this;
var secret = 'a';
me.method1 = function() {
... do something with 'secret' ...
}
}
var m = new Foo();
m.method1();
Foo
behaves with lexical scope just likemakeFoo
, but by using thenew
keyword, we get an object that has access to javascript's prototype system.- To make use of the prototype system we need to set
the prototype property of the
Foo
constructor. - Prototype function definitions use the
this
keyword to refer to the instantiated object that they are being called on
Panning prototypes
In an earlier article I panned the javascript prototype system because it encouraged exposing private state to elements outside of the object.
Some points to note from this:
- prototype methods cannot access the lexical scope of an object
- using the above example,
Foo.prototype.*
cannot access the local variables defined with the braces that defineFoo
itself (iefunction Foo() {...}
)
- using the above example,
- this means that if you want to use prototype methods and
manipulate lexical state you need to expose it via the
this
keyword - some people may be happy with this and simply expose private
state where needed by adding it as a property to
this
egthis.privateVar
- others might adopt a convention where private variables
might be prefixed with '_'; so
this._privateVar
- personally, I think it is still a little too easy to use such state and I prefer the ability to encapsulate it inside a lexical closure
Is it possible to share lexical state of an object with a prototype?
Sharing lexical state with prototype functions and properties
In the example below, we share lexical state information (information that is encapsulated within a lexical closure) with an object's prototype or properties.
- The cost of doing this however is that each prototype function or property is defined on a per-instance basis
- I personally don't have trouble with this; but one reason
people use prototypes is that they can define prototype
functions once and share across all instances
- as mentioned, the cost you pay for doing this is that in order for
such functions to work with the state of the object,
it has to be exposed via the
this
keyword egthis.privateVar
- as mentioned, the cost you pay for doing this is that in order for
such functions to work with the state of the object,
it has to be exposed via the
var F,f,fa,makeF;
F = function() {
this.pubval = 'mess with me!'; // public value
var secret1 = 'secret-1'; // privileged function based
var secret3 = 'secret-3'; // defineProperty based
this.secret1 = function() { return secret1; }
Object.defineProperty(this,"secret3", {
get: function() { return secret3; },
});
}
makeF = function(secrets) {
F.prototype = {
secret2: function() { return secrets.secret2; },
};
Object.defineProperty(F.prototype,"secret4", {
get: function() { return secrets.secret4; },
});
return new F();
}
f = makeF({secret2:'secret-2',secret4:'secret-4',});
fa = makeF({secret2:'secret-2a',secret4:'secret-4a',});
f.secret1();
f.secret2();
fa.secret2();
f.secret4;
fa.secret4;
f.secret3;
Breaking this down:
- F is a constructor which we'll instantiate (
new F()
) - makeF provides a closure into which we can pass secret
state information;
makeF
is used to instantiate instances ofF
; we don't callnew F()
directly - secret1 is accessed using the standard privileged function
approach;
this.secret1
is a per-instance function - secret2 is accessed via a prototype function; note however
that the prototype for any instance of
F
will be different and will have access to a different set ofsecrets
(passed in tomakeF
); it is per-instance. - secret3 is similar to secret1 but uses relatively new
construct called javascript properties; this allows us
to access secret3 without having to explicitly call a function
- also note that since we only define
get
for this property it is readonly; any attempt to write to it will cause an error
- also note that since we only define
- secret4 is a property of
F.prototype
- also note:
- that we have to define our prototype for
F
within the lexical scope ofmakeF
. We can't define it elsewhere and instantiate it withinmakeF
. This could be a limiting factor for people who like to compose prototypes. - that we can readily define privileged property
secret3
inside F's lexical enclosure; we can't do this withF.prototype
.- update [27-Aug-2010]
- Actually we can in some instances. Some javascript implementations support
the
__proto__
property; this property is applied to instances of a constructor and points to its prototype.var F = function() { var secret5 = 'secret-5'; ... // Set F's prototype within lexical enclosure of F: this.__proto__ = { secret5: function() { return secret5; }, }; ... } var f = new F(); f.secret5();
- What's a little unusual in the above is that we seem to be implicitly setting
F.prototype
for a specific instance ofF
via__proto__
; (the resulting prototype is still obviously on a per-instance basis as previously noted). __proto__
is effectively giving us privileged prototypes- The above approach actually worked when I tested it on a recent version of Google Chrome and Firefox 3.6.
- Actually we can in some instances. Some javascript implementations support
the
- update [27-Aug-2010]
defineProperty
is not supported in Firefox 3.x; it may be available in firefox 4.
- that we have to define our prototype for
Shared prototype functions and properties that are also privileged
Is it possible to have prototype functions or properties that have privileged access to lexical state information but which are shared across all instances of F?
I have not found a satisfactory work-around.
Part of the problem is that the very nature of lexical scope itself forces any privileged functions to be defined and created along with the state for a given instance of an object. If they are defined outside of the object's lexical state, they will not have access to it and so are not privileged
Flawed example
In this example
- we sequence each instance of F with a unique id
- we store all private state for all instances of F in an
associative array
secrets
using theid
as key - One obvious flaw in this approach is that
secrets
object will simply keep growing. If an instance ofF
is garbage collected, its state will remain insecrets
. - We employ an anonymous closure [
(function(){...}())
] oversecrets
,F
andF.prototype
. We repeat this pattern as needed wherever we want to share secrets between these 3 elements. - I do not recommend the use of
secrets
here for storing state; I am just looking for work-arounds
var F,f;
(function(){
var id=0;
// This object will just keep growing as we
// create more instances of F(!!)
var secrets={};
var seqf = function(){return ++id;}
F = function() {
var id = seqf();
secrets[id] = {
secret1:'secret-1',
secret2:'secret-2',};
Object.defineProperty(this,'id',{
get: function() {return id;},
});
}
F.prototype = {
secret1: function() {
return secrets[this.id].secret1; },
};
Object.defineProperty(F.prototype,'secret2',{
get: function() {return secrets[this.id].secret2},
});
}());
f = new F();
f.secret1();
f.secret2;
Non-lexical (non-privileged), "dynamic" sharing of secret information
One way to share secret information between an Object and a non-privileged function is to get the object to call the function and pass it some private state. An explicit parameter would need to be passed.
var m = function(o,...) { ... do something with 'o' ... }
var F = function() {
...
var secrets = {...};
this.f = function() {
...
m(secrets);
...
}
}
var f = new F();
In the above o
is some sort of object that we pass to m
for m
to do
its work. F
defines secrets
which is used by the privileged function
when invoking m
.
This actually defers the problem. We still need a privileged function this.f
in order to have access to secrets
which we then pass to m
.
Javascript allows us to do this without having to specify o
directly.
We omit o
:
var m = function(...) { ... do something with 'this' ... }
Then we call m
using javascript's call
:
m.call(secrets,...);
where secrets
is some lexically bound object that we pass to m
.
The trouble with this is that there's no obvious indication that
we have to call
m
. If we don't, then the default this
will
be used based on the object that contains the function. This may
be acceptable, but remember that the thing I'm trying to investigate
here is sharing privileged or hidden state defined within the lexical
scope of an object not the object's public or privileged methods and fields.
Other languages:
- ruby provides ways to define and rebind a method so that it has access to instance variables within an existing object instance; however ruby makes a precise distinction between instance variables and local variables which isn't so much the case with javascript
- common lisp provides
- dynamic scope (usually referred to now as 'special scope') which might be one way to get around the strictures of lexical scope and allow access to variables bound dynamically within a function but which may lead to subtle bugs and interactions - this is one of the reasons lexical scope is now the standard scoping mechanism
- free variables; a technique I'm not familiar with but which (via lisp's macro system) might also be used to capture information
No comments:
Post a Comment