这一篇,我们主要来学习一下私有属性和方法以及模块模式。

三、私有属性和方法

  JavaScript并没有特殊的语法来表示私有、保护、或公共属性和方法,在这一点上与Java或其他语言是不同的。JavaScript中所有对象的成员是公共的:

var myobj = {
myprop:1,
getProp: function() {
return this.myprop;
}
};
console.log(myobj.myprop); // 'myprop'是共有可访问的
console.log(myobj.getProp()); //getProp()也是公有可访问的 // 当使用构造函数创建对象时也通用如此,即所有的成员仍然都是公共的:
function Gadget() {
this.name = 'iPod';
this.stretch = function () {
return 'iPad';
};
}
var toy = new Gadget();
console.log(toy.name); // 'name'是共有的
console.log(toy.stretch()); //stretch()是公有的

  

私有成员

  虽然JavaScript语言中并没有用于私有成员的特殊语法,但是可以使用闭包来实现这种功能。构造函数创建了一个闭包,而在闭包范围内部的任意变量都不会暴露给构造函数以外的代码。然而,这些私有变量仍然可以用于公共方法中:即定义在构造函数中,且作为返回对象的一个部分暴露给外部的方法。

  我们来看个例子,其中name是一个私有成员,在构造函数外部是不可访问的:

function Gadge() {
// 私有成员
var name = 'iPod';
// 公有函数
this.getName = function () {
return name;
};
}
var toy = new Gadge(); // 'name'是undefined的,它是私有的
console.log(toy.name);//undefined // 公有方法访问'name'
console.log(toy.getName());// 'iPod'

  正如所看到的,很容易在JavaScript中实现私有性。需要做的只是在函数中将需要保持为私有属性的数据包装起来,并确保它对函数来说是局部变量,这意味着外部函数不能访问它。

特权方法

  特权方法(Privileged Method)的概念并不涉及任何特殊语法,它只是指那些可以访问私有成员的公共方法(因此它拥有更多的特权)的一个名称而已。

  在前面的例子中,getName()就是一个特权方法,它具有访问私有属性name的“特殊”权限。

私有性失效

  当关注私有的时候就会出现一些边缘情况:

  • 旧版本浏览器的一些情况比如Firefox的eval()可以传递第二个上下文参数,比如Mozilla的__parent__属性也与此类似。但是这几乎都是在古代浏览器才存在,现代浏览器几乎已经不存在这种情况了。
  • 当直接从一个特权方法中返回一个私有变量,且该变量恰好是一个对象或者数组,那么外面的代码仍然可以访问该私有变量,这是因为它是通过引用传递的。

  我们来看下这种情况。以下Gadget的实现看起来就像是无意造成失效的:

function Gadget() {
// 私有成员
var specs = {
screen_width:320,
screen_height:480,
color:"white"
};
// 公有函数
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,POLA),其中规定了应该永远不要给予超过需要的特权。

  在这种情况下,如果Gadget的消费者仅关注该gadget组建是否与一个特定方框的尺寸相符合,那么它需要的仅是尺寸规格。因此,并不需要分发所有的数据,可以创建getDimensions()使其返回一个包含宽度和高度的新对象。此时,可能根本不需要实现getSpecs()。

  当需要传递所有的数据时,另外一种解决方法是使用一个通用性的对象克隆(object cloning)函数以创建specs对象副本。下一章提供了两个这样的函数,其中一个名为extend(),它可以针对给定对象创建一个浅复制(shallow copy)副本(仅复制顶级单数)。而另一个名为extendDeep()的函数,它可以通过递归复制所有的属性以及其嵌套属性而创建深度复制(deep copy)副本。

对象字面量以及私有性

  到目前为止,我们仅看到了使用构造函数获得私有性的例子。但是当使用对象字面量(object literal)来创建对象会是什么情况?他是否还有可能拥有私有成员?

  正如在前面所看到的,需要的只是一个能够包装私有数据的函数。因此,在使用对象字面量的情况下,可以使用一个额外的匿名即时函数(anonymous immediate function)创建闭包来实现私有性:

var myobj; //这将会是对象
(function() {
// 私有成员
var name = 'my,oh my'; // 实现公有部分
// 注意,没有'var'修饰符
myobj = {
// 特权方法
getName: function () {
return name;
}
};
}());
myobj.getName(); //'my, oh my'; // 下面的例子与上面的具有同样的思想,但是在实现上略有不同:
var myobj = (function () {
// 私有成员
var name = 'my,oh my'; // 实现公有部分
return {
getName:function () {
return name;
}
};
}());
// 这个例子也是模块模式的基础框架,后面会再聊。

原型和私有性

  当将私有成员与构造函数一起使用时,其中有一个缺点在于每次调用构造函数以创建对象时,这些私有成员都会被重新创建。构造函数中添加到this中的任何成员实际上都面临以上问题。为了避免复制工作以及节省内存,可以将重用属性和方法添加到构造函数的prototype属性中。这样,通过同一个构造函数创建的多个实例可以共享常见的部分数据。此外,还可以再多个实例中共享隐藏的私有成员。为了实现这一点,可以使用以下两个模式的组合:即构造函数中的私有属性以及对象字面了中的私有属性。由于prototype属性仅是一个对象,因此可以使用对象字面了创建该对象。

function Gadget() {
// 私有成员
var name = 'iPod';
// 公有函数
this.getName = function () {
return name;
};
} Gadget.prototype = (function () {
// 私有成员
var browser = "Mobile Webkit";
// 公有原型成员
return {
getBrowser: function() {
return browser;
}
};
}()); var toy = new Gadget();
console.log(toy.getName()); //自身特权方法
console.log(toy.getBrowser());// 原型特权方法

将私有方法揭示为公共方法

  揭示模式(revelation pattern)可用于将私有方法暴露成为公共方法。当为了对象的运转而将所有功能都放置在一个对象中,以及,想尽可能的保护该对象的时候,这种揭示模式就显得非常有用。不过,同时可能也想为其中的一些功能提供公共可访问的接口,因为那可能也是有用的。当这些私有方法暴露为公共方法时,也使他们变得更为脆弱。因为使用公共API的一些用户可能会修改原对象,甚至是无意的修改。在ES5中,可以选择将一个对象冻结,但是在前一版本的语言中是不具备该功能的。

  揭示模式的前提,是建立在对象字面量的私有成员之下的。

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()。
// 在匿名函数(immediate function)的最后,对象myarray中填充了认为适用于公共访问的功能。
// 在这种情况下,同一个私有函数indexOf()可以暴露为ES5风格的indexOf以及PHP范式的inArray。
myarray.isArray([1,2]); // true
myarray.isArray({0:1}); // false
myarray.indexOf(["a","b","z"],"z"); //
myarray.inArray(["a","b","z"],"z"); // // 现在,如果发生了意外的情况,例如公共indexOf()方法发生意外,但私有indexOf()方法仍然是安全的,因此inArray()将继续正常运行:
myarray.indexOf = null;
myarray.inArray(["a","b","z"],"z"); //

四、模块模式

  目前模块模式得到了广泛的应用,因为它提供了结构化的思想并且有助于组织日益增长的代码。与其他语言不同的是,JavaScript并没有(package)的特殊语法,但是模块模式提供了一种创建自包含非耦合(self-contained de-coupled)代码片段的有利工具,可以将它视为黑盒功能,并且可以根据您所编写软件的需求添加、替换或删除这些模块。

  模块模式是本系列中迄今为止介绍过的第一种多种模式组合的模式,也就是以下模式的组合:命名空间、即时函数、私有和特权成员、声明依赖。

  该模式的第一步时间里一个命名空间。让我们使用本章前面介绍的namespace()函数,并且启动可以提供有用数组方法的工具模块。

MYAPP.namespace('MYAPP.utilities.array');
// 下一步是定义该模块。对于需要保持私有性的情况,本模式使用了一个可以提供私有作用域的即时函数。
// 该即时函数返回了一个对象,即具有公共接口的实际模块,可以通过这些接口来使用这些模块。
MYAPP.utilities.array = (function () {
return {
// todo...
}
}()); // 接下来我们向该公共接口添加一些方法:
MYAPP.utilities.array = (function () {
return {
inArray:function(needle,haystack) {
// ...
},
isArray:function(a) {
// ...
}
};
}());

  通过使用由即时函数提供的私有作用域,可以根据需要声明一些私有属性和方法。在即时函数的顶部,正好也就是声明模块可能由任何依赖的为止。在变量声明之后,可以任意地放置有助于建立该模块的任何一次性的初始化代码。最终结果是一个由即时函数返回的对象,其中该对象包含了您模块的公共API:

MYAPP.namespace('MYAPP.utilities.array');

// 接下来我们向该公共接口添加一些方法:
MYAPP.utilities.array = (function () {
// 依赖
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang,
// 私有属性
array_string = "[Object Array]",
ops = Object.prototype.toString; // 私有方法
// ... // var 变量定义结束 // 可选的一次性初始化过程
// ... return {
inArray:function(needle,haystack) {
for(var i = 0;i < haystack.length; i += 1) {
if(haystack[i] === needle) {
return true;
}
}
},
isArray:function(a) {
return ops.call(a) === array_string;
}
// 更多方法和属性
};
}());

  模块模式得到了广泛的使用,并且强烈建议使用这种方式组织您的代码,尤其是当旧代码日益增长的时候。

揭示模块模式

  我们已经讨论了揭示模式,同时还考虑了私有模式。模块模式也可以组织成与之相似的方式,其中所有的方法都需要保持私有性,并且只能暴露那些最后决定设立API的那些方法。根据这些思想,代码是这样的:

MYAPP.namespace('MYAPP.utilities.array');

MYAPP.utilities.array = (function () {
// 私有属性
var array_string = "[Object Array]",
ops = Object.prototype.toString,
// 私有方法
inArray = function (needle,haystack) {
for(var i = 0;i < haystack.length; i += 1) {
if(haystack[i] === needle) {
return true;
}
}
return -1;
},
isArray = function(a) {
return ops.call(a) === array_string;
}
// var 变量定义结束 // 揭示公有API
return {
isArray:isArray,
indexOf:inArray
};
}());

创建构造函数的模块

  前面的例子中创建了一个对象MYAPP.utilities.array,但有时候使用构造函数创建对象更为方便。当然,可以仍然使用模块模式来执行创建对象的操作。它们之间唯一的区别在于包装了模块的即时函数最终将会返回一个函数,而不是返回一个对象。

  考虑以下使用模块模式的例子,在该例子中创建了一个构造函数MYAPP.utilities.Array:

MYAPP.namespace('MYAPP.utilities.Array');

MYAPP.utilities.Array = (function () {
// 依赖
var uobj = MYAPP.utilities.object,
ulang = MYAPP.utilities.lang, // 私有属性和方法
Constr; // var 变量定义结束 // 可选的一次性初始化过程
// ... // 公有API——构造函数
Constr = function(o) {
this.elements = this.toArray(o);
};
// 公有API——原型
Constr.prototype = {
constructor:MYAPP.utilities.Array,
version:"2.0",
toArray:function(obj) {
for(var i = 0,a = [],len = obj.length; i < len; i += 1) {
a[i] = obj[i]
}
return a;
}
}; // 返回要分配给新命名空间的构造函数
return Constr;
}()); // 这样使用
var arr = new MYAPP.utilities.Array(obj);

  

将全局变量导入到模块中

  在常见的变化模式中,可以将参数传递到包装了模块的即时函数中。可以传递任何值,但是通常这些都是对全局变量、甚至是全局对象本身的引用。导入全局变量有助于加速即时函数中的全局符号解析的速度,因为这些导入的变量成为了该函数的局部变量。

MYAPP.utilities.module = (function(app,global) {
// 引用全局对象
// 以及现在被转换成局部变量的全局应用程序命名空间对象
}(MYAPP,this));

  好了,这一篇就到这里了,上诉的代码,实用价值是很大的。希望大家可以仔细阅读,认真看看。嘿嘿。

《JavaScript 模式》读书笔记(5)— 对象创建模式2的更多相关文章

  1. 《Javascript高级程序设计》读书笔记之对象创建

    <javascript高级程序设计>读过有两遍了,有些重要内容总是会忘记,写一下读书笔记备忘 创建对象 工厂模式 工厂模式优点:有了封装的概念,解决了创建多个相似对象的问题 缺点:没有解决 ...

  2. 设计模式---对象创建模式之原型模式(prototype)

    一:概念 原型模式(Prototype Pattern) 实际上就是动态抽取当前对象运行时的状态 Prototype模式是一种对象创建型模式,它采取复制原型对象的方法来创建对象的实例.使用Protot ...

  3. C++设计模式 之 “对象创建”模式:Factory Method、Abstract Factory、Prototype、Builder

    part 0 “对象创建”模式 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 典型模式 Fact ...

  4. 《Javascript模式》之对象创建模式读书笔记

    引言: 在javascript中创建对象是很容易的,可以使用对象字面量或者构造函数或者object.creat.在接下来的介绍中,我们将越过这些方法去寻求一些其他的对象创建模式. 我们知道js是一种简 ...

  5. 《JavaScript 模式》读书笔记(5)— 对象创建模式1

    这又是一个新的开始,对象的重要性不言而喻.在JavaScript中创建对象是十分容易的,之前聊过的对象字面量和构造函数都可以达到目的.但是本篇中,我们越过那些方法,以寻求一些额外的对象创建模式. 本篇 ...

  6. 《JavaScript 模式》读书笔记(5)— 对象创建模式4

    我们学完了大部分对象创建模式相关的内容,下面还有一些小而精的部分. 七.对象常量 JavaScript中没有常量的概念,虽然许多现代的编程环境可能为您提供了用以创建常量的const语句.作为一种变通方 ...

  7. 《JavaScript模式》第5章 对象创建模式

    @by Ruth92(转载请注明出处) 第5章:对象创建模式 JavaScript 是一种简洁明了的语言,并没有其他语言中经常使用的一些特殊语法特征,如 命名空间.模块.包.私有属性 以及 静态成员 ...

  8. 深入理解JavaScript系列(47):对象创建模式(上篇)

    介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度. ...

  9. 深入理解JavaScript系列(48):对象创建模式(下篇)

    介绍 本篇主要是介绍创建对象方面的模式的下篇,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式6:函数语法糖 函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用pro ...

  10. 设计模式---对象创建模式之构建器模式(Builder)

    一:概念 Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种.Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象 ...

随机推荐

  1. HTML标签学习总结(2)

    点我:HTLM标签学习总结(1) 11. 在网页制作过程过中,可以把一些独立的逻辑部分划分出来,放在一个<div>标签中,这个<div>标签的作用就相当于一个容器. 语法: & ...

  2. 带你学习Javascript中的函数进阶(一)

    1. 函数的定义和调用 1.1 函数的定义方式 函数声明方式function关键字(命名函数) 函数表达式(匿名函数) new Function() var fn = new Function('参数 ...

  3. DataGirdView

    DataGridView知识点 简单示例 (1)代码 SqlDataAdapter da; DataSet ds; string sql ="select 列名 from 表名": ...

  4. 在WPF(core版本)中引用外部字体不可用问题说明

    这几天使用WPF写软件,想引用外部字体,于是下载了字体文件: 然后在App.xaml中添加了如下代码: <FontFamily x:Key="Digital-7 Mono"& ...

  5. JMeter-WebService接口的测试

    前言 JMeter3.2版本之后就没有SOAP/XML-RPC Request插件了,那么该如何进行webservice接口的测试呢? 今天我们来一起学习一下怎么在3.2以后版本的JMeter进行we ...

  6. javascript中你可能遇到的隐式调用

    前言 不知道用隐式调用来形容是否确切,其行为总是隐藏在背后,时不时出来露脸一下,作用貌似不大,但是了解一下还是有用处的,保不准在你的使用下大有作为.所谓的隐式调用简单来说就是自动调用一些方法,而这些方 ...

  7. 机器学习基础——简单易懂的K邻近算法,根据邻居“找自己”

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天的文章给大家分享机器学习领域非常简单的模型--KNN,也就是K Nearest Neighbours算法,翻译过来很简单,就是K最近邻居 ...

  8. vue项目打包后打开空白解决办法

    1.记得改一下config下面的index.js中bulid模块导出的路径.因为index.html里边的内容都是通过script标签引入的,而你的路径不对,打开肯定是空白的.先看一下默认的路径. a ...

  9. idea创建简单web项目分析Servlet的请求转发与重定向的区别

     注:如需转载,请附上原文链接,如有建议或意见,欢迎批评指正! 需求说明: // index.jsp页面 1 <% 2 String basePath = request.getScheme() ...

  10. ggplot2(9) 数据操作

    9.1 plyr包简介 ddply {plyr}: Split data frame, apply function, and return results in a data frame. ddpl ...