JavaScript没有特殊的语法来表示对象的私有属性和方法,默认的情况下,所有的属性和方法都是公有的。如下面用字面声明的对象:

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
又如下面用构造函数声明的对象:
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
1. 私有成员
虽 然JavaScript没有提供私有成员的语法表示,但通过局部代码段(closure)提供局部作用域来实现私有的成员。而JavaScript中唯一 提供局部作用域的方式是函数。比如,在构造函数内部使用var声明的变量,就是私有的变量。然后,我们可以通过公有方法来访问这些变量:效果上和Java 中的访问控制是一样的:
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中实现私有成员的方法,这些私有的属性对于构造函数,以及对构造的对象来说是局部的(私有的),但对外不可见。
2. 特权方法
所谓的特权方法其实也没有特别的语法表示,而是特指可以向外提供私有变量访问的方法而已。比如上面代码里的 getName() 方法就是一个特权方法,它提供对私有属性name的访问控制。
3. 对私有成员访问控制的失效
私有成员的访问控制也可能会失效,常见的情况有如下:
  • 早期版本的Firefox向eval()提供了第二个参数,使得函数中的私有成员可以被访问;
  • Mozilla Rhino提供__parent__属性,访问函数的私有成员;
  • 当特权方法直接返回私有属性的引用,而这个私有属性恰好又是一个对象(数组也是对象)时;外部代码就可以通过对象引用修改对象的内容。
上面的两种情况在现在的主流浏览器中都不能实现了,最需要防范的是第三种情况,如下面的代码:
function Gadget() {
// private member
var specs = {
screen_width: 320,
screen_height: 480,
color: "white"
};
// public function
this.getSpecs = function () {
return specs;
};
}
 getSpecs() 返回了specs对象的引用,这时外部代码就不仅得到了specs对象中存储的数值,还可以随时修改其中的数值:
var toy = new Gadget(),
specs = toy.getSpecs();
specs.color = "black";
specs.price = "free";
console.dir(toy.getSpecs());
可 以想象,上面的console.dir返回的结果是什么。要防止对象引用导致的私有成员访问控制的失效,就别把私有成员的引用直接返回。解决办法可以是返 回一个克隆的对象,或是只返回私有成员中需要返回的简单数据类型的数值。后者又被称为 Principle of Least Authority (POLA)。比如在上面的例子中,如果代码的使用者调用getSpecs()的目的是得到Gadget的尺寸,那么就可以提供一个 getDimensions()方法,返回一个新建的(new出来的)存有width和height的对象。如果非要返回specs对象,也可以通过另外 实现的克隆方法返回一个specs的拷贝。许多JavaScript库都提供如 extend()或 extendDeep()等API,实现了对象的浅 拷贝和深拷贝。
4. 字面声明的对象中的私有成员
上面介绍了使用构造函数时对象的私有成员,而使用字面声明法创建的对象也同样可以实现私有成员,做法在原理上相同:使用函数的局部作用域,提供局部的(私有的)变量。这时我们就要用到一个即时函数了:
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"
这种写法其实也是模块模式的最初实现,这里先提一下,后面再详细介绍。
5. Prototype和私有成员
使 用局部变量或方法来声明私有成员的一个不太完美的地方是,这些局部属性和方法在每次创建对象时都将创建一次。这一问题也同样出现在构造函数中使用this 为对象添加成员属性时。解决方法是把常用的成员(包括公有的和私有的)放到protoytpe里去声明,这样成员属性和方法就只创建一次了。下面的实现用 到两种模式:在构造函数中实现私有属性的模式和在字面声明的对象中实现私有属性。在字面声明方式声明的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
6. 揭露模式(Revelation Pattern):把私有方法变成公有
我们可以在任何实现私有方法的模式之上,把私有方法使用公有接口揭露出来。这种模式可以在保护私有方法的同时,向外提供私有方法的访问入口。下面是一种实现的方式:
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()都是私有方法,而字面声明中为indexOf()提供了两个公有接口:JavaScript风格的 indexOf()和PHP风格的inArray()。这两个公有的接口的使用方法完全相同:
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()接口,将它指向null,不会影响inArray()的使用:
myarray.indexOf = null;
myarray.inArray(["a", "b", "z"], "z"); // 2

JavaScript基础对象创建模式之私有属性和方法(024)的更多相关文章

  1. JavaScript基础对象创建模式之单体/单例模式(Singleton)

    首先,单例模式是对象的创建模式之一,此外还包括工厂模式.单例模式的三个特点: 1,该类只有一个实例 2,该类自行创建该实例(在该类内部创建自身的实例对象) 3,向整个系统公开这个实例接口 Java中大 ...

  2. JavaScript基础对象创建模式之模块模式(Module Pattern)(025)

    模块模式可以提供软件架构,为不断增长的代码提供组织形式.JavaScript没有提供package的语言表示,但我们可以通过模块模式来分解并组织 代码块,这些黑盒的代码块内的功能可以根据不断变化的软件 ...

  3. JavaScript基础对象创建模式之命名空间(Namespace)模式(022)

    JavaScript中的创建对象的基本方法有字面声明(Object Literal)和构造函数两种,但JavaScript并没有特别的语法来表示如命名空间.模块.包.私有属性.静态属性等等面向对象程序 ...

  4. JavaScript基础对象创建模式之静态成员(027)

    在支持“类”的面向对象语言中,静态成员指的是那些所有实例对象共有的类成员.静态成员实际是是“类”的成员,而非“对象”的成员.所以如果 MathUtils类中有个叫 max()的静态成员方法,那么调用这 ...

  5. JavaScript基础对象创建模式之对象的常量(028)

    虽然许多编程语言提供了const关键字来支持常量的声明,但JavaScript里没有表示常量的语义.我们可以用全大写的方式来声明变量,表明它实际上是个常量: Math.PI; // 3.1415926 ...

  6. JavaScript基础对象创建模式之沙盘模式(026)

    沙盘模式可以弥补命名空间模式中的两项不足之处: 使用唯一全局对象作为程序的全局变量入口,使得无法在同一程序中使用两个不同版本的API,因此它们使用的是同一个唯一的全局对象名,如MYAPP: 较长的嵌套 ...

  7. JavaScript基础对象创建模式之链式调用模式(Chaining Pattern)(029)

    链式调用模式允许一个接一个地调用对象的方法.这种模式不考虑保存函数的返回值,所以整个调用可以在同一行内完成: myobj.method1("hello").method2().me ...

  8. JavaScript基础对象创建模式之声明依赖模式(023)

    运用了命名空间(Namespace)模式后, 就可以使用一些JavaScript库了,比如YAHOO作用YUI2库的全局对象,可以通过 YAHOO.util.Dom 和 YAHOO.util.Even ...

  9. javascript的对象创建模式---命名空间模式

    javascript中对象的概念是很普遍的,对象是是对象,数组是对象,函数也是对象,字符串其实也是对象.常见的对象创建方法有对象字面量.构造函数创建.我们先来看看对象的创建还有哪些更高级的模式. 一. ...

随机推荐

  1. Java实现 LeetCode 833 字符串中的查找与替换(暴力模拟)

    833. 字符串中的查找与替换 对于某些字符串 S,我们将执行一些替换操作,用新的字母组替换原有的字母组(不一定大小相同). 每个替换操作具有 3 个参数:起始索引 i,源字 x 和目标字 y.规则是 ...

  2. Java实现 蓝桥杯VIP 算法训练 无权最长链

    试题 算法训练 无权最长链 问题描述 给定一个n节点m边的无圈且连通的图,求直径 输入格式 第一行两个数字n,m 接下来m行每行两个数字x,y,代表x,y之间有一条边 输出格式 要求用户的输出满足的格 ...

  3. Java实现 LeetCode 698 划分为k个相等的子集(递归)

    698. 划分为k个相等的子集 给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等. 示例 1: 输入: nums = [4, 3, 2, 3, ...

  4. Java实现 LeetCode 135 分发糖果

    135. 分发糖果 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分. 你需要按照以下要求,帮助老师给这些孩子分发糖果: 每个孩子至少分配到 1 个糖果. ...

  5. Java实现蓝桥杯 算法训练 大等于n的最小完全平方数

    试题 算法训练 大等于n的最小完全平方数 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 输出大等于n的最小的完全平方数. 若一个数能表示成某个自然数的平方的形式,则称这个数为完全平 ...

  6. Java实现 洛谷 P1421 小玉买文具

    import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = ...

  7. java实现第九届蓝桥杯最大乘积

    最大乘积 把 1~9 这9个数字分成两组,中间插入乘号, 有的时候,它们的乘积也只包含1~9这9个数字,而且每个数字只出现1次. 比如: 984672 * 351 = 345619872 98751 ...

  8. TZOJ 车辆拥挤相互往里走

    102路公交车是crq经常坐的,闲来无聊,他想知道最高峰时车上有多少人,他发现这辆车只留一个门上下人,于是他想到了一个办法,上车时先数一下车上人员数目(crq所上的站点总是人不太多),之后就坐在车门口 ...

  9. (易忘篇)java基本语法难点3

    本博客随笔主要记录本人学习过程中的知识,欢迎大家一同学习,有不对的地方很高兴读者能详细指出,感激不尽! JVM内存结构 编译完源程序以后,生成一个或多个字节码文件. 我们使用JVM中的类的加载器和解释 ...

  10. 免费 IP 代理池示例

    使用文档 import requests import re import random from concurrent.futures import ThreadPoolExecutor impor ...