本文深入解析 JavaScript 中私有成员的实现机制,澄清对象无真正私有变量的误解。通过构造器、原型链与闭包特性,详细演示如何创建公有、私有及特权成员,并探讨其内存管理与访问限制,帮助开发者构建更严谨的面向对象应用。
JavaScript 常被视为一门被严重误解的语言。一种常见观点认为,由于 JavaScript 对象缺乏真正的私有变量和方法,因此它不具备“信息隐藏”能力。然而,这其实是一种误解。JavaScript 对象完全可以拥有私有成员,接下来我们将探讨具体实现方式。(SharkUI.com 注:JavaScript 并非原生支持面向对象中的私有、公有等特性,本文所述的私有、公有及特权成员,实际上是借助 JavaScript 的其他特性(详见“闭包”章节)“模拟”实现的。感兴趣者可搜索相关文献深入了解,当然,若将其当作真正的面向对象特性使用也未尝不可。祝阅读愉快!)
对象
JavaScript 的核心建立在对象之上。数组(Array)、函数(Function)乃至对象(Objects)本身,皆属于对象范畴。那么,究竟什么是对象?对象本质上是一组“名称:值”对的集合。其中,名称必须是字符串,而值可以是字符串、数值、布尔值或另一个对象(包括数组和函数)。为了支持快速取值,对象通常基于哈希表实现。
当某个值是一个函数时,我们可以将其视为“方法”。在调用对象的方法时,变量 this 会被绑定为该对象本身。这使得方法能够通过 this 变量访问该对象的实例数据。
对象可以通过“构造器(constructor)”来创建。构造器是一个用于初始化对象的函数。它提供了类似其他编程语言中“类(class)”所具备的特性与功能,包括静态变量和方法的支持。
公有
对象的所有成员默认均为公有成员。任何函数都可以访问、修改或删除这些成员,甚至可以动态添加新成员。向对象添加成员主要有两种途径:
通过构造器
这种方法通常用于初始化对象实例的公有变量。我们利用构造器中的 this 变量来为对象添加成员:
function Container(param) {
this.member = param;
}
创建一个新对象:
var myContainer = new Container('abc');
此时,公有变量 myContainer.member 即被赋予值 'abc'。
通过原型(prototype)
此方法通常用于添加公有方法。当在对象本身中未找到指定成员时,JavaScript 会查找构造器的原型(prototype)成员。这种原型机制实现了面向对象概念中的“继承(inheritance)”,同时有效节省了内存。若需为同一构造器创建的所有对象添加一个方法,只需将该函数赋值给构造器的 prototype:
Container.prototype.stamp = function (string) {
return this.member + string;
}
随后,我们可以调用该方法:
myContainer.stamp('def')
该方法将返回 'abcdef'。
私有
私有(Private)成员由构造器内部创建。通常,在构造器中使用 var 声明的变量以及函数参数,均成为私有成员。
function Container(param) {
this.member = param;
var secret = 3;
var self = this;
}
该构造器创建了三个私有的实例变量:param、secret 和 self。
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var self = this;
}
私有方法 dec 用于检查实例变量 secret。若 secret 大于 0,则将其自减 1 并返回 true;若小于等于 0,则返回 false。由此实现了该构造器创建的对象中 dec 函数仅能被调用三次的功能。
按照惯例,我们创建了一个私有变量 self。私有方法可通过它来访问对象本身。但这仅是一种权宜之计,因为《ECMAScript 语言规范》中存在一个错误,导致内部函数的 this 变量被设置为错误的值。
公有方法(SharkUI.com 注:即上文所述通过 prototype 创建的方法)无法直接调用私有方法。因此,为了使用私有方法,我们需要引入特权方法(privileged method)。
特权
特权方法可以访问私有变量和方法,同时它本身也可被公有方法和外部代码访问。你可以删除或替换一个特权方法,但不能修改它,也不能强制它放弃其保密性(SharkUI.com 注:原文如此,可能指其特权属性,具体细节请资深开发者指正)。
特权方法是在构造器内部通过 this 创建的。
function Container(param) {
function dec() {
if (secret > 0) {
secret -= 1;
return true;
} else {
return false;
}
}
this.member = param;
var secret = 3;
var self = this;
this.service = function () {
if (dec()) {
return self.member;
} else {
return null;
}
};
}
service 是一个特权方法。前三次调用 myContainer.service() 将返回 'abc',之后将返回 null。service 通过调用私有方法 dec 来访问私有变量 secret。对于其他对象和方法而言,可以访问 service,但无法直接访问私有成员。
闭包
公有、私有和特权成员模式的存在,源于 JavaScript 的内在机制:闭包。这意味着内部函数永远可以访问其外部函数的变量和参数,即使外部函数已经返回。这是 JavaScript 语言非常强大的一个特性。目前尚无 JavaScript 编程书籍展示如何利用它,甚至许多书籍对此只字未提。
私有和特权成员只能在对象初始化时创建,而公有成员可以随时添加。
模式
公有
function Constructor(...) {
this.membername = value;
}
Constructor.prototype.membername = value;
私有
function Constructor(...) {
var self = this;
var membername = value;
function membername(...) {...}
}
注:这句代码:
function membername(...) {...}
事实上是以下代码的简略写法
var membername = function membername(...) {...};
特权
function Constructor(...) {
this.membername = function (...) {...};
}
后记
Douglas Crockford 的这篇文章为我们编写更优美的 JavaScript 程序奠定了基础,使得创建更合理的面向对象应用和框架成为可能。在本文翻译即将完成时,惊讶地发现作者网站上已出现本文中文版的链接。这是一件好事!越来越多的中国人开始关注这些“边边角角”的技术。虽然做了重复工作,但仍希望各位能从这篇文章中有所收获。同时也希望有更多人投身于前端技术文章的原创与翻译中,在多数人浮躁的时候,我们需要更多基础性工作。一周一篇不多,一年一篇不少,只要开始了就行!