私有的属性和方法(Private Properties and Methods)
JavaScript中没有特殊的语法去声明private,protected和public的属性和方法,不像Java和其它语言。所有的对象成员都是public的:
var myobj = {
myprop: 1,
getProp: function () {
return this.myprop;
}
};
console.log(myobj.myprop); // `myprop` is publicly accessible
console.log(myobj.getProp()); // getProp() is public too
当你使用构造函数创建对象也是一样,所有的成员都是public的:function Gadget() {
this.name = 'iPod';
this.stretch = function () {
return 'iPad';
};
}
var toy = new Gadget();
console.log(toy.name); // `name` is public
console.log(toy.stretch()); // stretch() is public
私有的成员(Private Members)
虽然语言没有特殊的语法去声明私有成员,但你可以使用闭包(closure)去实现这个功能。
你的构造函数创建一个闭包,并且在闭包范围内的任何变量都不会暴露在构造函数之外。然而这些私有变量可以被public方法访问。
让我们看一个例子,name是一个私有成员,在构造函数之外无法被访问到:
function Gadget() {
// private member
var name = 'iPod';
// public function
this.getName = function () {
return name;
};
}
var toy = new Gadget();
// `name` is undefined, it's private
console.log(toy.name); // undefined
// public method has access to `name
console.log(toy.getName()); // "iPod"
正如你看到的,在JavaScript中实现私有是非常简单的。你所需要做的就是将你想私有的数据包裹在一个函数中让它成为函数的局部变量,意味着在这个函数外不可被访问。特权方法(Privileged methods)
特权方法的概念不包含任何特殊的语法,就是能给能访问私有成员的pubic方法起的名字。
在前面的例子中getName()就是一个特权方法,因为他可以特殊的权利去访问私有的属性name
私有化的局限(Privacy Failures)
当你涉及到私有化时,有一些边界情况
- 当你从特权方法直接返回一个私有变量并且这个变量恰巧是一个对象或数组时,那么外面的代码就可以修改这个私有的变量因为它们是通过引用传递的
让我们通过第二个例子说明的更具体些,下面这个Gadget实现看起来是没有问题的:
function Gadget() {
// private member
var specs = {
screen_width: 320,
screen_height: 480,
color: "white"
};
// public function
this.getSpecs = function () {
return specs;
};
}
这里的问题出在 getSpecs()返回的是指向specs对象的引用。这可以让Gadget的使用者修改看起来似乎是隐藏并私有的specs:var toy = new Gadget(),
specs = toy.getSpecs();
specs.color = "black";
specs.price = "free";
console.dir(toy.getSpecs());
解决这个意想不到的行为的方法就是小心不要传递你想私有化对象或数值的引用。一种方法去实现私有化这个目的是让getSpecs()返回一个新的对象,这个对象仅仅包含了一些对象使用者感兴趣的数据。这个也被成为最小权限原则(Principle of Least Authority),这表明你永远不应该提供比实际需要的多。
这个例子中,如果Gadget对象使用者关心的是它是否能被装的某个盒子中,它仅仅需要的是尺寸。
那么不需要提供所有的数据,你可以创建一个getDimensions(),它返回一个只包含width和height的新对象。你可能根本不需要提供getDimensions()。
另一种方法就是,当你需要传递所有的数据是,可以创建一个specs对象的副本(copy),通过一个通用的对象克隆函数。
另一种方法就是,当你需要传递所有的数据是,可以创建一个specs对象的副本(copy),通过一个通用的对象克隆函数。
后面我们会通过两个这样的函数,一个叫做extend()做一个浅复制(只复制顶层的参数),另一个叫做extendDeep()做一个深度复制,递归复制所以的属性和它们属性的属性。
对象字面量和私有化(Object Literals and Privacy)
到目前为止我们仅仅看到了使用构造函数去实现私有化的例子,但当你的对象是使用对象字面量创建会出现什么情况呢?它仍然能够拥有私有成员吗?
正如你前面看到的,你所需要的就是用一个函数去包裹私有数据。所以在对象字面量的情况下,你可以使用一个额外的匿名的立即执行函数创建闭包,像下面这样:
var myobj; // this will be the object
(function () {
// private members
var name = "my, oh my";
// implement the public part
// note -- no `var`
myobj = {
// privileged method
getName: function () {
return name;
}
};
}());
myobj.getName(); // "my, oh my"
相同的主意,但是实现有点不同的实现:var myobj = (function () {
// private members
var name = "my, oh my";
// implement the public part
return {
getName: function () {
return name;
}
};
}());
myobj.getName(); // "my, oh my"
这个例子也是模块模式的本质。
原型和私有化(Prototypes and Privacy)
使用构造方法实现私有化的缺点就是这些私有成员在构造方法每次被调用创建新的对象时都会被重新创建一次。
在构造方法中你添加到this的任何成员都会存在这个问题,为了防止这种重复工作并且节约内存,你可以添加公共的属性和方法给构造方法的prototype属性。
通过这样方式,公共的部分在相同构造方法创建的所有实例间进行共享。
你也可以在所有实例之间共享隐藏的私有成员,为了实现这样的效果你可以使用两种模式的组合:构造方法的私有属性和对象字面量的私有属性。
因为prototype也就是一个对象,它也可以通过对象字面量创建。
举例:
function Gadget() {
// private member
var name = 'iPod';
// public function
this.getName = function () {
return name;
};
}
Gadget.prototype = (function () {
// private member
var browser = "Mobile Webkit";
// public prototype members
return {
getBrowser: function () {
return browser;
}
};
}());
var toy = new Gadget();
console.log(toy.getName()); // privileged "own" method
console.log(toy.getBrowser()); // privileged prototype method
暴露一个私有方法作为一个公开方法(Revealing Private Functions As Public Methods)
暴露模式是想将一个私有方法暴露为一个公共方法。这是非常有用的,当一个对象所有的函数对对象的工作方式是很关键的并且你想尽可能的保护它们。
但同时你也想将一些函数提供公共的访问权限,因为这会非常有用。但当你公然地暴露一个方法,你会使它可以被修改,你的公共API用户可能会修改它,即使是无心的。
在ECMAScript 5中你有选项去冻结一个对象,但在之前的版本中是不可以的。
我们来举个例子,首先创建一个私有化模式--对象字面量的私有属性:
var myarray;
(function () {
var astr = "[object Array]",
toString = Object.prototype.toString;
function isArray(a) {
return toString.call(a) === astr;
}
function indexOf(haystack, needle) {
var i = 0,
max = haystack.length;
for (; i < max; i += 1) {
if (haystack[i] === needle) {
return i;
}
}
return −1;
}
myarray = {
isArray: isArray,
indexOf: indexOf,
inArray: indexOf
};
}());
这里我们拥有两个私有变量和两个私有方法,isArray()和indexOf(),在立即执行函数的结尾,myarray对象由你想要的暴露的函数组成。
myarray.isArray([1,2]); // true
myarray.isArray({0: 1}); // false
myarray.indexOf(["a", "b", "z"], "z"); // 2
myarray.inArray(["a", "b", "z"], "z"); // 2
现在如果公共indexOf()发生了不在预期之内的事情,私有的indexOf()仍然是安全的并且inArray() 仍然能继续工作。myarray.indexOf = null;
myarray.inArray(["a", "b", "z"], "z"); // 2