《JavaScript 模式》读书笔记(5)— 对象创建模式3
这一篇,我们主要来学习了解下沙箱模式以及静态成员的相关内容。
五、沙箱模式
沙箱模式(sandbox pattern)解决了命名空间模式的如下几个缺点:
- 对单个全局变量的依赖变成了对应用程序的全局变量的依赖。在命名空间模式中,是没办法使同一个应用程序或库的两个版本运行在同一个页面中,这是因为两者都需要同一个全局符号名,比如全局变量MYAPP,比如你所熟悉的“$”。
- 对这种以点分割的名字来说,需要输入更长的字符,并且在运行时需要解析更长的时间,比如MYAPP.utilities.array。
顾名思义,沙箱模式提供了一个可用于模拟运行的环境,且不会对其他模块和个人沙箱造成任何影响。
全局构造函数
在命名空间模式中,有一个全局对象,在沙箱模式中,则是一个全局构造函数,让我们称之为Sandbox()。可以使用该构造函数创建对象并且还可以传递回调函数,它变成了代码的隔离沙箱运行环境。
// 这样来使用沙箱
new Sandbox(function (box) {
// 你的代码写在这里
});
对象box与命名空间例子中的MYAPP是相似的,他有您所需要的所有库函数,能够使代码正常运行。
让我们向该模式添加两个新特性:
- 通过一些神奇特征(第三章中的强制new模式),可以假设在创建对象时不需要new操作符。
- Sandbox()构造函数可以接受一个或多个额外的配置参数,其中该参数制定了对象实例所需要的模块名。我们希望代码是模块化的,因此绝大部分Sandbox()提供的功能将被限制在模块中。
有了以上两个额外特征,让我们看一下实例化对象的代码看起来是什么样子的。
可以忽略new操作符,并且使用一些如下所示的虚构“ajax”和“event”模块来创建对象:
Sandbox(["ajax","event"],function(box){
//console.log(box);
});
// 下面例子与上面的例子类似,只不过模块名是以单个参数的形式传递的:
Sandbox("ajax","dom",function(box){
//console.log(box);
});
// 根据上面的例子的启示,使用通配符“*”来表示“使用所有的可用模块”如何?此外,为了方便起见,让我们假设当没有传递任何模块时,沙箱也会将其认定为“*”。
// 所以,我们还有以下两种方式来使用所有可用的模块:
Sandbox("*",function(box){
//console.log(box);
});
Sandbox(function(box){
//console.log(box);
}); // 此外,该模式的另外一个使用样例则演示了如何多次实例化沙箱对象的方法。甚至还可以将一个模块嵌入到另外一个模块中,并且这两者之间不会互相干扰。
Sandbox('dom','event',function(box) {
// 使用DOM和事件来运行
Sandbox('ajax',function (box) {
// 另一个沙箱化的“box”对象
// 这里的“box”对象与函数外部的“box”对象并不相同
// ...
// 这里用Ajax来处理
});
// 这里没有Ajax模块
});
从上面这些例子中可以看到,当使用本沙箱模式时,可以通过将代码包装到回调函数中从而保护全局命名空间。
如果需要,也可以利用函数就是对象这个事实,然后将数据存储为该Sandbox()构造函数的静态属性。
最后,可以根据所需要的模块类型创建不同的实例,并且这些实例互相独立运行。
题外话:上面的沙箱模式,是不是很像早期使用的Angular,或者一些模块化插件比如require.js等等的使用方式?
现在,我们来看看应该如何着手实现Sandbox()构造函数以及其模块,从而支持所有这些功能。
增加模块
在实现实际的构造函数之前,让我们看看如何才能够增加模块的功能。
Sandbox()构造函数也是一个对象,因此可以向它添加一个名为modules的静态属性。该属性是包含键值对的另一个对象,其中这些键是模块的名字,而值则是实现每个模块的对应函数。
Sandbox.modules = {}; Sandbox.modules.dom = function (box) {
box.getElement = function () {};
box.getStyle = function () {};
box.foo = "bar";
}; Sandbox.modules.event = function (box) {
// 如果需要,就访问Sandbox原型,如下语句:
// box.constructor.prototype.m = 'mmm';
box.attachEvent = function() {};
box.dettach = function () {};
}; Sandbox.modules.ajax = function (box) {
box.makeRequest = function () {};
box.getResponse = function () {};
};
在上面这个例子中,我们增加了DOM、event和ajax,这些都是在库或者复杂Web应用中常见的功能片段。
实现每个模块的函数可以接受当前实例box作为参数,并且可以向该实例中添加额外的属性和方法。
实现构造函数
最后,让我们来实现该Sandbox()构造函数(当然,可能希望重命名这种类型的构造函数,以便使得这些名字对于库或者应用程序来说更有字面意义):
function Sandbox() {
// 将参数转换成一个数组
var args = Array.prototype.slice.call(arguments),
// 最后一个参数是回调函数
callback = args.pop(),
// 模块可以作为一个数组传递,或作为单独的参数传递
modules = (args[0] && typeof args[0] === 'String') ? args : args[0],
i; // 确保该函数作为构造函数被调用
if(!(this instanceof Sandbox)) {
return new Sandbox(modules,callback);
} // 需要向this添加的属性
this.a = 1;
this.b = 2; // 向在向该核心“this”对象添加模块
// 不指定模块名称或指定“*” 都表示“使用所有模块”
if(!modules || modules === '*') {
modules = [];
for(i in Sandbox.modules) {
if(Sandbox.modules.hasOwnProperty(u)) {
modules.push(i);
}
}
} // 初始化所需的模块
for(i = 0; i < modules.length; i += 1) {
Sandbox.modules[modules[i]](this);
} // call the callback callback(this);
} // 需要的任何原型属性
Sandbox.prototype = {
name:"My Application",
version:"1.0",
getName: function () {
return this.name;
}
};
在以上代码实现中,其关键部分为:
- 存在一个类型检查语句,检查this是否为Sandbox的实例。如果为否(这表示在没有使用new操作符的情况下调用了Sandbox()),那么我们会再次以构造函数的形式调用该函数。
- 可以在构造函数中将一些属性添加到this中。此外,还可以将一些属性添加到构造函数的原型中。
- 所需的模块可以用模块名称数组的形式传递或以单个参数的形式传递,还可以通过通配符*或省略的形式传递,这表示我们应该咱如所有可用的模块。请注意,在这个实例中,我们并不关心从其他文件中加载所需的功能,但这也绝对是一个可选的实现功能,比如,YUI3库中就支持这种功能。可以仅加载最基本的模块(也称之为“种子”),并且根据与命名公约对应的模块名称,从外部文件中加载任何所需的模块。
- 当我们知道所需的模块时,便可以据此进行初始化,这表示可以调用实现每个模块的函数。
- 该构造函数的最后一个参数是一个回调函数。该回调函数将会在使用新创建的实例时最后被调用。这个回调函数实际上是用户的沙箱,它可以获得一个填充了所需功能的box对象。
六、静态成员
静态属性和方法也就是那些从一个实例到另一个实例都不会发生改变的属性和方法。
公有静态成员
JavaScript中并没有特殊的语法来表示静态成员。但是可以通过使用构造函数并且向其添加属性这种方式,从而获得与“类式”语言相同的语法,这种方式可以良好运行,这是因为构造函数与所有其它函数一样都是对象,并且它们都可以拥有属性。在前面章节中讨论的备忘模式也采用类相同的思想,即向函数中添加属性。
下面的例子定义了一个具有静态方法isShiny()的构造函数Gadget,以及一个普通的实例方法setPrice()。其中,isShiny()是一个静态方法,因为该方法并不需要特定的gadget对象就能够运行。另一方面,setPrice()方法则需要一个对象才能运行,因为gadget可被设为不同的定价:
// 构造函数
var Gadget = function () {}; // 静态方法
Gadget.isShiny = function () {
return 'you bet';
}; // 向该原型添加一个普通方法
Gadget.prototype.setPrice = function (price) {
this.price = price;
}; // 现在,让我们调用这些方法进行测试。构造函数中的静态isShiny()方法可以被直接调用,然而普通的方法则需要一个实例:
// 调用静态方法
console.log(Gadget.isShiny()); // 输出“you bet” // 创建一个实例并调用其方法
var iphone = new Gadget();
iphone.setPrice(500); // 试图以静态方法调用一个实例方法是无法正常运行的。同样,如果使用实例iphone对象调用静态方法也是无法正常运行的:
console.log(typeof Gadget.setPrice); // "undefined"
console.log(typeof iphone.isShiny); // "undefined" // 有时候也可以很方便地使用静态方法与实例一起工作。这种功能比较容易实现,只需要向原型中添加一个新的方法即可,其中该新方法作为一个指向原始静态方法的外观(Facade):
Gadget.prototype.isShiny = Gadget.isShiny;
iphone.isShiny();
在这种情况下,如果在静态方法内部使用this要特别注意。当执行Gadget.isShiny()时,那么isShiny()内部的this将会指向Gadget构造函数。如果执行iphone.isShiny(),那么this将会指向iphone。
最后一个例子向您展示了如何以静态或非静态方式调用同一个方法,而在这两种场景下依赖于调用模式的不同,其表现行为略有不同。下面的instanceof函数有助于确定方法是如何被调用的。
// 构造函数
var Gadget = function (price) {
this.price = price;
}; // 静态方法
Gadget.isShiny = function () {
// 这种方法总是可以运行
var msg = "you bet"; if(this instanceof Gadget) {
// this only works if called non-statically
msg += ", if cost $" + this.price + "!";
}
return msg;
}; // 向该原型添加一个普通搞得方法
Gadget.prototype.isShiny = function () {
return Gadget.isShiny.call(this);
}; // 测试静态方法调用
console.log(Gadget.isShiny());
// 测试实例,非静态调用
var a = new Gadget('499,99');
console.log(a.isShiny());
私有静态成员
到目前为止,本章所讨论的是公有静态方法,现在让我们来看看如何实现私有静态成员。就私有静态成员而言,指的是成员具有如下属性:
- 以同一个构造函数创建的所有对象共享该成员。
- 构造函数外部不可访问该成员。
下面我们看一个例子,其中counter是构造函数Gadget中的一个私有静态属性。在本章中以及存在有关私有属性的讨论,因此这一部分仍然是相同的。需要一个函数作为闭包并且包装私有成员。然后,让我们使同一个包装函数立即执行并返回一个新函数。返回的函数值分配给变量Gadget,并且成为了新的构造函数:
var Gadget = (function () {
// 静态变量/属性
var counter = 0; // 返回
// 该构造函数新的实现
return function () {
console.log(counter += 1);
}
}()); // 新的Gadget构造函数只是简单的递增和记录私有counter成员。使用以下几个实例进行测试,可以看到counter的确是在多个实例直接共享:
var g1 = new Gadget();
var g2 = new Gadget();
var g3 = new Gadget();
由于我们对每个对象都以1为单位递增counter,这个静态属性实际上成为了对象ID标识符,它唯一标识了以Gadget构造函数创建的每个对象这种唯一标识符可能是很有用的,因此为什么不通过特权方法将其公开?下面是基于前面示例基础上的一个例子,主要增加了一个特权方法getLastId()以访问静态私有属性:
var Gadget = (function () {
// 静态变量/属性
var counter = 0,
NewGadget; // 这将成为
// 该构造函数新的实现
NewGadget = function () {
counter += 1;
}; // 特权方法
NewGadget.prototype.getLastId = function () {
return counter;
}; // 覆盖该构造函数
return NewGadget;
}()); // 测试新的实现
var iphone = new Gadget();
console.log(iphone.getLastId());
var ipod = new Gadget();
console.log(ipod.getLastId());
var ipad = new Gadget();
console.log(ipad.getLastId());
静态属性(公有和私有)使用会带来很多便利。它们可以包含非实例相关的方法和数据,并且不会为每个实例重新创建静态属性。第7章中,当涉及单体模式时,可以看到一个使用静态属性以实现类似类的单体构造函数的例子。
《JavaScript 模式》读书笔记(5)— 对象创建模式3的更多相关文章
- 《Javascript高级程序设计》读书笔记之对象创建
<javascript高级程序设计>读过有两遍了,有些重要内容总是会忘记,写一下读书笔记备忘 创建对象 工厂模式 工厂模式优点:有了封装的概念,解决了创建多个相似对象的问题 缺点:没有解决 ...
- 设计模式---对象创建模式之原型模式(prototype)
一:概念 原型模式(Prototype Pattern) 实际上就是动态抽取当前对象运行时的状态 Prototype模式是一种对象创建型模式,它采取复制原型对象的方法来创建对象的实例.使用Protot ...
- C++设计模式 之 “对象创建”模式:Factory Method、Abstract Factory、Prototype、Builder
part 0 “对象创建”模式 通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 典型模式 Fact ...
- 《Javascript模式》之对象创建模式读书笔记
引言: 在javascript中创建对象是很容易的,可以使用对象字面量或者构造函数或者object.creat.在接下来的介绍中,我们将越过这些方法去寻求一些其他的对象创建模式. 我们知道js是一种简 ...
- 《JavaScript 模式》读书笔记(5)— 对象创建模式1
这又是一个新的开始,对象的重要性不言而喻.在JavaScript中创建对象是十分容易的,之前聊过的对象字面量和构造函数都可以达到目的.但是本篇中,我们越过那些方法,以寻求一些额外的对象创建模式. 本篇 ...
- 《JavaScript 模式》读书笔记(5)— 对象创建模式4
我们学完了大部分对象创建模式相关的内容,下面还有一些小而精的部分. 七.对象常量 JavaScript中没有常量的概念,虽然许多现代的编程环境可能为您提供了用以创建常量的const语句.作为一种变通方 ...
- 《JavaScript模式》第5章 对象创建模式
@by Ruth92(转载请注明出处) 第5章:对象创建模式 JavaScript 是一种简洁明了的语言,并没有其他语言中经常使用的一些特殊语法特征,如 命名空间.模块.包.私有属性 以及 静态成员 ...
- 深入理解JavaScript系列(47):对象创建模式(上篇)
介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度. ...
- 深入理解JavaScript系列(48):对象创建模式(下篇)
介绍 本篇主要是介绍创建对象方面的模式的下篇,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式6:函数语法糖 函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用pro ...
- 设计模式---对象创建模式之构建器模式(Builder)
一:概念 Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种.Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象 ...
随机推荐
- 基于RHEL 9 搭建 KVM 虚拟化环境
一.准备工作 1. 检查硬件虚拟化支持 KVM 要求处理器支持硬件虚拟化技术:Intel VT-x(虚拟化技术扩展)或 AMD-V(虚拟化技术扩展). 检查方法: 使用以下命令检查 CPU 是否支持虚 ...
- (系列五).net8 中使用Dapper搭建底层仓储连接数据库(附源码)
说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发). 该系统文章,我会尽量说的非常详细,做到不管新手.老手都能看懂. 说明:OverallAuth2 ...
- webapi action 参数
使用地址参数传递(queryString)数据:eg:http://localhost:5063/WeatherForecast?age=123 /// <summary> /// GET ...
- 防火墙NAT配置与DHCP下发
该实验如果有做的不足的地方请见谅 实验目标: 按要求划分区域,公司内部办公区为trust,服务器区为dmz,外部网络为untrust. PC1和PC2为公司内部办公区,需要从防火墙中的DHCP服务获取 ...
- 带你玩转nginx负载均衡
nginx跨多个应用程序实例的负载平衡是一种用于优化资源利用率,最大化吞吐量,减少延迟和确保容错配置的常用技术. 环境介绍 配置nginx负载均衡器因会用到多台服务器来进行,所以下面我会用到docke ...
- Linux下的网络抓包tcpdump
tcpdump [ -AdDefIJKlLnNOpqRStuUvxX ] [ -B buffer_size ] [ -c count ] [ -C file_size ] [ -G rotate_se ...
- Python 潮流周刊#74:创下吉尼斯世界记录的 Python 编程课(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- MySQL,你只需要看这一篇文章就够了!PART04--完结篇
MySQL--DAY04 索引 定义 索引是在数据库表的字段上添加的,是为了提高查询效率存在的一种机制. 一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引. 索引相当于一本书的目 ...
- c++11新增内容
记录一下c++11新特性方便以后回忆 1.nullptr (对标NULL) 2.auto ,decltype(根据表达式推断类型,表达式不执行) decltype(func()) sum = 5; / ...
- 7.Kubernetes集群YAML文件详解
Kubernetes集群YAML文件详解 概述 k8s 集群中对资源管理和资源对象编排部署都可以通过声明样式(YAML)文件来解决,也就是可以把需要对资源对象操作编辑到YAML 格式文件中,我们把这种 ...