This is written by
Douglas Crockford
JavaScript is the world's most misunderstood programming language. Some believe that it lacks the property of
information hiding because objects cannot have private instance variables and methods. But this is a misunderstanding. JavaScript objects can have private members. Here's how.
Objects
JavaScript is fundamentally about
objects. Arrays are objects. Functions are objects. Objects are objects. So what are objects? Objects are collections of name-value pairs. The names are strings, and the values are strings, numbers, booleans, and objects (including arrays and functions). Objects are usually implemented as hashtables so values can be retrieved quickly.
If a value is a function, we can consider it a
method. When a method of an object is invoked, the
this variable is set to the object. The method can then access the instance variables through the
this variable.
Objects can be produced by
constructors, which are functions which initialize objects. Constructors provide the features that classes provide in other languages, including static variables and methods.
Public
The members of an object are all
public members. Any function can access, modify, or delete those members, or add new members. There are two main ways of putting members in a new object:
In the constructor
This technique is usually used to initialize public instance variables. The constructor's
this variable is used to add members to the object.
function Container(param) {
this.member = param;
}
So, if we construct a new object
var myContainer = new Container('abc');
then
myContainer.member contains
'abc'.
In the prototype
This technique is usually used to add public methods. When a member is sought and it isn't found in the object itself, then it is taken from the object's constructor's
prototype member. The prototype mechanism is used for inheritance. It also conserves memory. To add a method to all objects made by a constructor, add a function to the constructor's
prototype:
Container.prototype.stamp = function (string) {
return this.member + string;
}
So, we can invoke the method
myContainer.stamp('def')
which produces
'abcdef'.
Private
Private members are made by the constructor. Ordinary
vars and parameters of the constructor becomes the private members.
function Container(param) {
this.member = param;
var secret = 3;
var that = this;
}
This constructor makes three private instance variables:
param,
secret, and
that. They are attached to the object, but they are not accessible to the outside, nor are they accessible to the object's own public methods. They are accessible to private methods. Private methods are inner functions of the constructor.
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var that = this;
}
The private method
dec examines the
secret instance variable. If it is greater than zero, it decrements
secret and returns
true. Otherwise it returns
false. It can be used to make this object limited to three uses.
By convention, we make a private
that variable. This is used to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes
this to be set incorrectly for inner functions.
Private methods cannot be called by public methods. To make private methods useful, we need to introduce a privileged method.
Privileged
A
privileged method is able to access the private variables and methods, and is itself accessible to the public methods and the outside. It is possible to delete or replace a privileged method, but it is not possible to alter it, or to force it to give up its secrets.
Privileged methods are assigned with
this within the constructor.
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var that = this;
this.service = function () {
return dec() ? that.member : null;
};
}
service is a privileged method. Calling
myContainer.service() will return
'abc' the first three times it is called. After that, it will return
null.
servicecalls the private
dec method which accesses the private
secret variable.
service is available to other objects and methods, but it does not allow direct access to the private members.
Closures
This pattern of public, private, and privileged members is possible because JavaScript has
closures. What this means is that an inner function always has access to the vars and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language. There is no book currently available on JavaScript programming that shows how to exploit it. Most don't even mention it.
Private and privileged members can only be made when an object is constructed. Public members can be added at any time.
Patterns
Public
function Constructor(...) {
this.membername = value;
}
Constructor.prototype.membername = value;
Private
function Constructor(...) {
var that = this;
var membername = value;function membername(...) {...}
}
Note: The function statement
function membername(...) {...}
is shorthand for
var membername = function membername(...) {...};
Privileged
function Constructor(...) {
this.membername = function (...) {...};
}
More About Object
Numbers, strings, and booleans are object-like in that they have methods, but they are immutable.
Objects in JavaScript
are mutable keyed collections.
An object is a container of properties, where a property has a name and a value.
JavaScript’s fundamental datatype is the
object. An object is a composite value: it aggregates
multiple values (primitive values or other objects) and allows you to store and
retrieve those values by name. An object is an unordered collection of properties, each
of which has a name and a value. Property names are strings, so we can say that
objects
map strings to values. This string-to-value mapping goes by various names: you are
probably already familiar with the fundamental data structure under the name “hash,”
“hashtable,” “dictionary,” or “associative array.” An object is more than a simple string-to-value map, however. In addition to maintaining its own set of properties, a JavaScript
object also inherits the properties of another object, known as its “prototype.” The
methods of an object are typically inherited properties, and this “prototypal inheritance”
is a key feature of JavaScript.
JavaScript objects are dynamic—properties can usually be added and deleted—but
they can be used to simulate the static objects and “structs” of statically typed languages.
They can also be used (by ignoring the value part of the string-to-value mapping)
to represent sets of strings.
Any value in JavaScript that is not a string, a number,
true,
false,
null, or
undefined
is an object. And even though strings, numbers, and booleans are not objects, they
behave like
immutable objects .
Objects are
mutable and are manipulated by reference rather than
by value. If the variable x refers to an object, and the code var y = x; is executed, the
variable y holds a reference to the same object, not a copy of that object. Any modifications
made to the object through the variable y are also visible through the variable x.
The most common things to do with objects are create them and to set, query, delete,
test, and enumerate their properties.
A
property has a name and a value. A property name may be any string, including the
empty string, but no object may have two properties with the same name. The value
115 may be any JavaScript value, or (in ECMAScript 5) it may be a getter or a setter function
(or both). In addition to its name
and value, each property has associated values that we’ll call property attributes:
• The
writable attribute specifies whether the value of the property can be set.
• The
enumerable attribute specifies whether the property name is returned by a
for/in loop.
• The
configurable attribute specifies whether the property can be deleted and
whether its attributes can be altered.
Prior to ECMAScript 5, all properties in objects created by your code are writable,enumerable, and configurable. In ECMAScript 5, you can configure the attributes of your properties.
In addition to its properties, every object has three associated object attributes:
• An object’s
prototype is a reference to another object from which properties are
inherited.
• An object’s
class is a string that categorizes the type of an object.
• An object’s
extensible flag specifies (in ECMAScript 5) whether new properties may
be added to the object.
Three broad categories of JavaScript objects and two types of properties:
• A
native object is an object or class of objects defined by the ECMAScript specification.
Arrays, functions, dates, and regular expressions (for example) are native
objects.
• A
host object is an object defined by the host environment (such as a web browser)
within which the JavaScript interpreter is embedded. The HTMLElement objects
that represent the structure of a web page in client-side JavaScript are host objects.
Host objects may also be native objects, as when the host environment defines
methods that are normal JavaScript Function objects.
• A
user-defined object is any object created by the execution of JavaScript code.
• An
own property is a property defined directly on an object.
• An
inherited property is a property defined by an object’s prototype object.