Pseudo Private Marker
Properties in objects are public, so are methods. Anyone who gets hold of an object is able to access its properties, methods, even its prototype's properties and methods all the way to the root prototype object (prototype is just another property after all). One approach to 'implement' private members is to make private members 'look' like private, and hope other developers will not access or modify them. Over time, programmers adopted a convention of putting an underscore in front of a property or method name. This underscore acts like a marker to say "Hey, this is private. Don't touch it!". This kind of convention should sound familiar to Python developers. Other flavors of the same convention include adding two underscores at the front or another underscore at the end, e.g. __firstName or _firstName_.
var helloKitty = { _meow: function() { // Private return 'Meow~~'; }, hello: function() { return this._meow(); } }; helloKitty.hello(); // 'Meow~~'
The pro of this approach is that it is really easy. No extra code is required. However, this approach puts a lot of trusts in the hands of your code users. This can be both good and bad. The upside is that when users know what they are dong and really want to access or extend your private members, they can easily do so. After all the underscore is just a marker which doesn't provide any constraint over how a member is accessed.
var helloKitty = { _meow: function() { return 'Meow~~'; }, hello: function() { return this._meow(); } }; // Extend helloKitty._meow which is private var superMeow = helloKitty._meow; helloKitty._meow = function() { return superMeow() + ' mew~~~'; }; helloKitty.hello(); // 'Meow~~ mew~~~'
However, when API authors really want to forbid access to private members, this approach cannot enforce such constraint. Lacking of real access control is not ideal to most OO purists.
Scope and Closure
The following JavaScript code defined one global variable (myName), and two global functions (sayHello and greet).
var myName = 'David'; var sayHello = function(name) { return 'Hello, ' + name; }; var greet = function() { return sayHello(myName); }; greet(); // 'Hello, David'
According to the JavaScript good practice, we should try to avoid creating globals whenever possible. In this example, the variable myName and function sayHello are mere implementation details of the greet function. We should make them private.
Attempt 1
var greet = function() { var myName = 'David'; var sayHello = function(name) { return 'Hello, ' + name; }; return sayHello(myName); }; greet(); // 'Hello, David'
Most JavaScript programmers will come up with this solution by moving private pieces into the function. Actually, in most cases, this solution should be good enough. However, it should be noticed that the local variables and functions will be created every time the function is invoked. For this simple example, this solution is fine. However, for functions which contain a lot of local variables, functions, or have massive preparation code in the functions, the overhead to re-create these locals will be more significant.
Attempt 2
With the help of self-executing function and closure, we created a scope where private variables and functions live inside:
var greet = (function() { var myName = 'David'; // Private variable var sayHello = function (name) { // Private function return 'Hello, ' + name; }; return function() { // Return a function return sayHello(myName); }; })(); // Notice the ending () greet(); // 'Hello, David'
The self-executing function creates a scope that hides variable name and sayHello from the outside world. Meanwhile, because of closure (one of JavaScript's most powerful features), the returned function is able to hold references to the private variable name and private function sayHello.
Please notice that the code to create the myName variable and sayHello function is executed only once. When the greet function is called, myName and sayHello are already there and won't be re-created again.
This solution works well for private variables which won't need to change for different function invocations. In our case, variable myName doesn't change when we call the greet function. In another world, we can think myName as a private constant.
Instance and Class Private Members
var Person = function() { var myName = 'David'; // Private variable var sayHello = function(name) { // Private function return 'Hello, ' + name; }; this.greet = function() { // Privileged method return sayHello(myName); }; }; var david = new Person(); david.hello(); // 'Hello, David'
This is a typical constructor function. JavaScript has no implementation of class. A constructor function might be the closest thing to a class. Here we defined a Person 'class' which has a private variable name, a private function sayHello, and a privileged method greet.
Variable myName and function sayHello are visible only in the constructor function Person. They are not accessible outside of the scope created by the constructor function.
Moreover, because of the closure, the function this.greet is able to access the private variable myName and private function sayHello. We call function this.greet a privileged method. It is exposed to the public, and it can see the class' internal secrets -- private members name and sayHello.
This approach is pretty an ideal implementation of private members, however, every time a constructor function is called to create an object, its local members (variables and functions) will be re-created. In our case, myName, sayHello, and this.greet will be re-created every time the Person constructor is invoked. This is not efficient, and wastes memories. It is recommended to have shared members especially reusable functions assigned to the prototype object outside of the constructor function. Here, we're going to do so to the greet function which is meant to be public and reusable.
var Person = function() { var myName = 'David'; // Private variable var sayHello = function(name) { // Private function return 'Hello, ' + name; }; }; Person.prototype.greet = function() { // Shared public function ... ... };
The greet public function is created only once, and it is shared by all instances created by the Person constructor. However, here comes a problem: how can we access the private members defined in the constructor from the greet function?
We can change myName and sayHello to this.myName and this.sayHello, and in the greet function we are able to access them by calling this.myName and this.sayHello. However, doing so made myName and sayHello public, which defeats our original purpose.
Our goal is to have myName and sayHello private but keep greet public, meanwhile, have greet shared by all instances created by the Person constructor.
To achieve this goal, we again borrowed the power from self-executing functions and closures. This time, the self-executing function returns a constructor function which keeps references to the myName variable and sayHello function through the closure which is created by the constructor function.
var Person = (function() { var myName = 'David'; var sayHello = function(name) { return 'Hello, ' + name; }; // Constructor fucntion var Constr = function() { }; // Public methods Constr.prototype.greet = function() { return sayHello(myName); }; return Constr; // Return the constructor function })(); // Don't forget the () var david = new Person(); david.greet(); // 'Hello, David'
Please notice that myName and sayHello are created only once. Once they are created, they are shared by all objects created by the constructor function, however, they are not accessible outside the constructor and the self-executing function.
myName and sayHello are not only private members, they are also class static members. Because these variable and function are bound to the constructor function (the closest thing to class in JavaScript) via closures, and thus shared by all instances created by the constructor.
No comments:
Post a Comment