掺合模式(Mixin)
Mixin是JavaScript中用的最普遍的模式,几乎所有流行类库都会有Mixin的实现。
Mixin是掺合,混合,糅合的意思,即可以就任意一个对象的全部或部分属性拷贝到另一个对象上。
从提供的接口来看,有的是对对象的操作,有的是对类的操作。对类的操作又称为掺元类(Mixin classes)
一、掺合对象 (Mixin object)
先看最简单的mixin实现
1 2 3 4 5 | function mixin(dest, src) { for ( var key in src) { dest[key] = src[key] } } |
使用下
1 2 3 4 | var person = {name: 'John' , age: 29} var obj = {} mixin(obj, person) console.log(obj) // {name: 'John', age: 29} |
可看到,已经将person的所有属性拷贝到obj上了。 有个缺点,如果obj上已经有了name: 'Jack',那么会被person覆盖。因此需要加个判断,如果dest上已经存在了,就不拷贝。
1 2 3 4 5 6 7 8 9 10 11 | function mixin(dest, src) { for ( var key in src) { if (!dest[key]) { dest[key] = src[key] } } } var person = {name: 'John' , age: 29} var obj = {name: 'Jack' } mixin(obj, person) console.log(obj) // Object { name="Jack", age=29} |
当然,你可以提供更强大,灵活的Mixin,比如可以将任意多个对象掺合到目标对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function mixin(dest /*, Any number of objects */ ) { var sources = Array.prototype.slice.call(arguments, 1) for ( var i=0; i<sources.length; i++) { var src = sources[i] for ( var key in src) { if (!dest[key]) { dest[key] = src[key] } } } } var person = {name: 'John' , age: 29, toString: function (){ return 'aa' }} var permission = {open: 1} var obj = {name: 'Jack' } mixin(obj, person, permission) console.log(obj) // Object { name="Jack", age=29, open=1} |
以下类库都是对对象的掺合
- jQuery的$.extend 操作对象,将其它对象的属性方法拷贝到目标对象。
- RequireJS的私有的mixin 操作对象,将其它对象的属性方法拷贝到目标对象。
- ExtJS的Ext.apply 也是操作对象,它还提供了一个defaults参数。
- Underscore.js 的 _.extend,把第二个参数起的所有对象都拷贝到第一个参数
二、掺和类(Mixin Classes)
有的翻译过来叫做掺元类,它是一种不需要用到严格的继承就可以复用代码的一种技术。如果多个类想用到某个类的某个方法,可以通过扩充这些类的原型已达到共享该方法。比如先创建一个包含各种通用方法的类,然后让其它类扩充于它。这个包含通用方法的类就叫掺元类。多数时候它不会直接实例化或调用,而是作为其它类的模板用于扩充。
先看最简单的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 工具方法,实现mixin function augment(destClass, srcClass) { var destProto = destClass.prototype var srcProto = srcClass.prototype for ( var method in srcProto) { if (!destProto[method]) { destProto[method] = srcProto[method] } } } function Person() {} // 具有两个方法的类,用于mixin Person.prototype.getName = function () {} Person.prototype.getAge = function () {} function Student() {} // 没有任何方法的类 augment(Student, Person) // 调用,拷贝 var s1 = new Student() console.log(s1) // Student { getName=function(), getAge=function()} |
工具函数augment接受两个参数,都是函数类型(类),第一个类会从第二个类的原型上继承其方法。即使用Person类扩充了Student类。
我们知道,某些语言如C++/Python允许子类继承多个父类,但在JavaScript中是不允许的,因为一个构造器只有一个原型对象,不过这可以通过多个掺元类的方式实现扩充,这实际是一种变相多继承的实现。和mixin方法类似,修改下augment方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 | function augment(destClass, /*, Any number of classes */ ) { var classes = Array.prototype.slice.call(arguments, 1) for ( var i=0; i<classes.length; i++) { var srcClass = classes[i] var srcProto = srcClass.prototype var destProto = destClass.prototype for ( var method in srcProto) { if (!destProto[method]) { destProto[method] = srcProto[method] } } } } |
这样就实现了多继承。
有时不想继承所有的方法,指向拷贝指定的方法,增加一个参数methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function augment(destClass, srcClass, methods) { var srcProto = srcClass.prototype var destProto = destClass.prototype for ( var i=0; i<methods.length; i++) { var method = methods[i] if (!destProto[method]) { destProto[method] = srcProto[method] } } } function Person() {} Person.prototype.getName = function () {} Person.prototype.setName = function () {} Person.prototype.getAge = function () {} Person.prototype.setAge = function () {} function Student() {} augment(Student, Person, [ 'getName' , 'setName' ]) var s1 = new Student() console.log(s1) // Student { getName=function(), setName=function()} |
Backbone是广泛使用掺元类的库
首先,Backbone库自身就采用Mixin classes方式组织,如Backbone.Events是最底层的掺元类,它的方法(on/off/trigger...)都被Backbone.Model/Backbone.Collection/Backbone.View等继承。代码片段如下
1 2 3 4 5 6 7 8 9 | _.extend(Model.prototype, Events, { ... }) _.extend(Collection.prototype, Events, { ... }) _.extend(View.prototype, Events, { ... }) |
它这里使用_.extend来扩充Model,Collection,View的原型,把Events的方法都拷贝到原型。即Event就是一个掺元类(虽然被实现为一个对象)
其次,我们使用Backbone开发时,你写的模型会用Backbone.Model去扩充,视图会用Backbone.View去扩充。如
1 2 3 4 5 6 7 8 9 10 11 | var MyModel = Backbone.Model.extend({ instanceProp: xx },{ classProp: yy }) var MyView = Backbone.Model.extend({ instanceProp: xx },{ classProp: yy }) |
这时,Backbone.Model/Backbone.View等就是掺元类了。当然,你还可以把underscore当做掺元对象,因为Backbone的很多类都继承了_.extend方法,如Backbone.Events/Backbone.Model等。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
2012-05-24 RequireJS入门(三)
2011-05-24 勿重复检测浏览器