热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《JavaScript应用程序设计》一一3.9使用Stamps进行原型继承

本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第3章,第3.9节,作者:EricElliott更多章节内容可以访问

本节书摘来华章计算机出版社《Javascript应用程序设计》一书中的第3章,第3.9节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.9使用Stamps进行原型继承

对象在Javascript中,有各式各样灵活的特性,相比之下通过Object.create()方法所构建出的对象,感觉就像被“阉割”了一样。开发者总是需要自己去编写额外的代码来实现诸如让享元对象支持数据隐蔽特性这样的特性,所以当你需要将多个对象上的功能做组合时,一般情况下你会感觉非常无力。
现今多数Javascript类库中提供了一套与类继承概念相似的对象复用机制,基于原型继承的Javascript类库占比还是太少,所以就更别提什么业界标准了。既然少量语法糖就能够在Javascript中模拟类的概念,那么为什么不能构建一个囊括了Javascript所有强大特性的原型继承系统呢?
当聊起Javascript中的对象构建时,我们不禁首先会问自己,在Javascript中,对象所具有的最为重要的特性有哪些?
· 原型代理
· 实例状态
· 封装性
Stamps是一个工厂函数,它拥有一组公有属性与方法,这些属性方法用来定义所构建对象的原型代理、默认的实例属性以及为封装私有属性所需的函数。在构建对象方面,Stamps提供了3种不同的继承方式。
· 原型代理:原型继承
· 实例状态:属性混入
· 封装性:闭包式继承
Stampit(https://github.com/dilvie/stampit)是为本书而写的类库,用来向读者展示如何利用Javascript自带的语言特性来简化原型继承,它向外界仅暴露一个入口函数,其函数签名如下:
stampit(methods, state, enclose);
让我们看看如何使用Stampit来构建对象:

var testObj = stampit(
// methods
{delegateMethod: function delegateMethod() {return 'shared property';}
},// state
{instanceProp: 'instance property'
},// enclose
function () {var privateProp = 'private property';this.getPrivate = function getPrivate() {return privateProp;}
}).create();test('Stampit with params', function () {equal(testObj.delegateMethod(), 'shared property','delegate methods should be reachable');ok(Object.getPrototypeOf(testObj).delegateMethod,'delegate methods should be stored on the ' +'delegate prototype');equal(testObj.instanceProp, 'instance property','state should be reachable.');ok(testObj.hasOwnProperty('instanceProp'),'state should be instance safe.');equal(testObj.hasOwnProperty('privateProp'), false,'should hide private properties');equal(testObj.getPrivate(), 'private property','should support privileged methods');
});

在上例中,我们在stampit实例上调用create()方法,它返回testObj的实例。代码下方的测试用例显示,testObj实例已经拥有了诸多原型继承体系下的杀手级特性,你已经无需再花费大量精力去实现自己的工厂函数了。
stampit实例支持方法链式调用,上例中的对象也可以被这样创建:

var testObj = stampit().methods({delegateMethod: function delegateMethod() {return 'shared property';}}).state({instanceProp: 'instance property'}).enclose(function () {var privateProp = 'private property';this.getPrivate = function getPrivate() {return privateProp;}}).create();

由Object.create()构建出的对象会默认使用原型上定义的方法,这些方法被所有实例所共享,这样做在很大程度上节省了内存开销。同理,如果你在程序运行期间修改了原型中的方法,所有实例均会受到波及,如下例:

var stamp = stampit().methods({delegateMethod: function delegateMethod() {return 'shared property';}}),obj1 = stamp(),obj2 = stamp();Object.getPrototypeOf(obj1).delegateMethod = function () {return 'altered';};test('Prototype mutation', function () {equal(obj2.delegateMethod(), 'altered','Instances share the delegate prototype.');
});

state()方法采用了“属性混入”的方式,它将传入的对象视为实例的原型代理,并将该对象的每项属性拷贝后添加至新实例中。由于是拷贝而非直接引用,所以你可以放心大胆地对属性做后期修改。所有stampit实例在做对象构建时,均接受一个可选的字典对象,字典对象中的属性会被混入新实例中,这让对象实例化这一过程变得更为简单。

var person = stampit().state({name: ''}),jimi = person({name: 'Jimi Hendrix'});test('Initialization', function () {equal(jimi.name, 'Jimi Hendrix','Object should be initialized.');});

enclose()方法则采用了“闭包式继承”的方式, 你可以给它传入函数,这些函数彼此独立并且各自拥有闭包作用域,从而可以确保数据的隐蔽性与唯一性。此外,如果在函数中存在多个同名特权方法的定义,那么后一项始终会有限覆盖前一项。在stampit实例中enclose()方法可以被调用多次,所传入的函数会在对象构建时被“激活”。
在初始化一个对象时,有时并不需要将参数一次性全部传入,所以我一直建议将对象的实例化与初始化解耦,引入读写方法(Getter/Setter)可以解决这个问题。

var person = stampit().enclose(function () {var firstName = '',lastName = '';this.getName = function getName() {return firstName + ' ' + lastName;};this.setName = function setName(options) {firstName = options.firstName || '';lastName = options.lastName || '';return this;};
}),jimi = person().setName({firstName: 'Jimi',lastName: 'Hendrix'
});test('Init method', function () {equal(jimi.getName(), 'Jimi Hendrix','Object should be initialized.');});

前面我们介绍了用stampit进行对象构建,其实这仅仅是Javascript面向对象特性的冰山一角。接下来我们介绍的内容你很难在现有的Javascript流行类库中寻觅到,甚至连ES6规范中都没有相关定义。
首先,看如何使用闭包来封装私有数据:

var a = stampit().enclose(function () {var a = 'a';this.getA = function () {return a;};
});a().getA(); // 'a'

stampit实例使用函数作用域来封装私有数据,请注意读写方法(Getter/Setter)需定义在函数内部,才可以访问到闭包中的变量,这一规则同样适用于Javascript中的所有特权函数。
来看另一个例子:

var b = stampit().enclose(function () {var a = 'b';this.getB = function () {return a;};
});b().getB(); // 'b'

这个“拼写错误”是有意为之的,它是为了向你展示实例a与实例b各自封装的同名私有变量不会对彼此的使用造成影响,而且这么做的有趣之处在于:

var c = stampit.compose(a, b),foo = c();foo.getA(); // 'a'
foo.getB(); // 'b'

stampit的静态方法compose()让你能够从多个stampit实例中做原型继承。上述示例演示了使用compose()方法来实现多重继承,我们看到所继承的属性中甚至连私有数据都有囊括,而现今的类继承体系中是做不到这点的。
stampit实例有一个名为fixed的特别属性,它存储着所有methods(实例方法)、state(属性)与enclose(包裹函数)的对象原型。在实例化期间,state对象的所有属性被拷贝至实例中,确保了实例属性操作的安全性;methods对象则作为实例的原型代理来使用,从而使得多个实例间可以共享一套方法;enclose对象将所有函数当作闭包来使用,实现了对私有数据的访问控制。
compose()方法的用途与$.extend()方法很接近,不过它并不是像$.extend()方法那样使用外来对象的属性做扩展,而是借助了stampit实例的fixed属性,compose()方法先是将来自多个stampit实例下的fixed属性做合并,随后向外界返回一个新stampit实例。在对同名属性进行合并时,compose()会优先使用顺序靠后的属性,在这一点上,它与$.extend(), _.extend()等方法的合并策略是一致的。



推荐阅读
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文详细介绍了如何在 Windows 环境下使用 node-gyp 工具进行 Node.js 本地扩展的编译和配置,涵盖从环境搭建到代码实现的全过程。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • dotnet 通过 Elmish.WPF 使用 F# 编写 WPF 应用
    本文来安利大家一个有趣而且强大的库,通过F#和C#混合编程编写WPF应用,可以在WPF中使用到F#强大的数据处理能力在GitHub上完全开源Elmis ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Java多线程环境下,如何确保具有相同key值的线程能够互斥执行并按顺序输出结果。通过优化代码结构和使用线程安全的数据结构,我们解决了线程同步问题,并实现了预期的并发行为。 ... [详细]
  • 作为一名 Ember.js 新手,了解如何在路由和模型中正确加载 JSON 数据是至关重要的。本文将探讨两者之间的差异,并提供实用的建议。 ... [详细]
  • 2018年3月31日,CSDN、火星财经联合中关村区块链产业联盟等机构举办的2018区块链技术及应用峰会(BTA)核心分会场圆满举行。多位业内顶尖专家深入探讨了区块链的核心技术原理及其在实际业务中的应用。 ... [详细]
  • 本文详细介绍了C语言中的指针,包括其基本概念、应用场景以及使用时的优缺点。同时,通过实例解析了指针在内存管理、数组操作、函数调用等方面的具体应用,并探讨了指针的安全性问题。 ... [详细]
  • 自 Node.js 6.3 版本起,调试功能已内置在核心模块中,无需额外安装 node-inspector 等工具。通过简单的命令即可启动调试模式,并利用 Chrome 浏览器进行高效的代码调试。 ... [详细]
  • 本文详细介绍了 org.apache.commons.io.IOCase 类中的 checkCompareTo() 方法,通过多个代码示例展示其在不同场景下的使用方法。 ... [详细]
  • 使用GDI的一些AIP函数我们可以轻易的绘制出简 ... [详细]
  • 本文将详细探讨Linux pinctrl子系统的各个关键数据结构,帮助读者深入了解其内部机制。通过分析这些数据结构及其相互关系,我们将进一步理解pinctrl子系统的工作原理和设计思路。 ... [详细]
  • 在现代Web应用中,当用户滚动到页面底部时,自动加载更多内容的功能变得越来越普遍。这种无刷新加载技术不仅提升了用户体验,还优化了页面性能。本文将探讨如何实现这一功能,并介绍一些实际应用案例。 ... [详细]
author-avatar
月光魔术师2702935955
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有