Mixin是JavaScript中用的最普遍的模式,幾乎所有流行類庫都會有Mixin的實作。
Mixin是摻合,混合,糅合的意思,即可以就任意一個對象的全部或部分屬性拷貝到另一個對象上。
從提供的接口來看,有的是對對象的操作,有的是對類的操作。對類的操作又稱為摻元類(Mixin classes)
一、摻合對象 (Mixin object)
先看最簡單的mixin實作
1 function mixin(dest, src) {
2 for (var key in src) {
3 dest[key] = src[key]
4 }
5 }
使用下
1 var person = {name: 'John', age: 29}
2 var obj = {}
3 mixin(obj, person)
4 console.log(obj) // {name: 'John', age: 29}
可看到,已經将person的所有屬性拷貝到obj上了。 有個缺點,如果obj上已經有了name: 'Jack',那麼會被person覆寫。是以需要加個判斷,如果dest上已經存在了,就不拷貝。
1 function mixin(dest, src) {
2 for (var key in src) {
3 if (!dest[key]) {
4 dest[key] = src[key]
5 }
6 }
7 }
8 var person = {name: 'John', age: 29}
9 var obj = {name: 'Jack'}
10 mixin(obj, person)
11 console.log(obj) // Object { name="Jack", age=29}
當然,你可以提供更強大,靈活的Mixin,比如可以将任意多個對象摻合到目标對象
1 function mixin(dest /*, Any number of objects */) {
2 var sources = Array.prototype.slice.call(arguments, 1)
3 for (var i=0; i<sources.length; i++) {
4 var src = sources[i]
5 for (var key in src) {
6 if (!dest[key]) {
7 dest[key] = src[key]
8 }
9 }
10 }
11 }
12 var person = {name: 'John', age: 29, toString: function(){return 'aa'}}
13 var permission = {open: 1}
14 var obj = {name: 'Jack'}
15 mixin(obj, person, permission)
16 console.log(obj) // Object { name="Jack", age=29, open=1}
以下類庫都是對對象的摻合
- jQuery的$.extend 操作對象,将其它對象的屬性方法拷貝到目标對象。
- RequireJS的私有的mixin 操作對象,将其它對象的屬性方法拷貝到目标對象。
- ExtJS的Ext.apply 也是操作對象,它還提供了一個defaults參數。
- Underscore.js 的 _.extend,把第二個參數起的所有對象都拷貝到第一個參數
二、摻和類(Mixin Classes)
有的翻譯過來叫做摻元類,它是一種不需要用到嚴格的繼承就可以複用代碼的一種技術。如果多個類想用到某個類的某個方法,可以通過擴充這些類的原型已達到共享該方法。比如先建立一個包含各種通用方法的類,然後讓其它類擴充于它。這個包含通用方法的類就叫摻元類。多數時候它不會直接執行個體化或調用,而是作為其它類的模闆用于擴充。
先看最簡單的實作
1 // 工具方法,實作mixin
2 function augment(destClass, srcClass) {
3 var destProto = destClass.prototype
4 var srcProto = srcClass.prototype
5 for (var method in srcProto) {
6 if (!destProto[method]) {
7 destProto[method] = srcProto[method]
8 }
9 }
10 }
11
12 function Person() {} // 具有兩個方法的類,用于mixin
13 Person.prototype.getName = function() {}
14 Person.prototype.getAge = function() {}
15
16 function Student() {} // 沒有任何方法的類
17
18 augment(Student, Person) // 調用,拷貝
19
20 var s1 = new Student()
21 console.log(s1) // Student { getName=function(), getAge=function()}
工具函數augment接受兩個參數,都是函數類型(類),第一個類會從第二個類的原型上繼承其方法。即使用Person類擴充了Student類。
我們知道,某些語言如C++/Python允許子類繼承多個父類,但在JavaScript中是不允許的,因為一個構造器隻有一個原型對象,不過這可以通過多個摻元類的方式實作擴充,這實際是一種變相多繼承的實作。和mixin方法類似,修改下augment方法。
1 function augment(destClass, /*, Any number of classes */) {
2 var classes = Array.prototype.slice.call(arguments, 1)
3 for (var i=0; i<classes.length; i++) {
4 var srcClass = classes[i]
5 var srcProto = srcClass.prototype
6 var destProto = destClass.prototype
7 for (var method in srcProto) {
8 if (!destProto[method]) {
9 destProto[method] = srcProto[method]
10 }
11 }
12 }
13 }
這樣就實作了多繼承。
有時不想繼承所有的方法,指向拷貝指定的方法,增加一個參數methods
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
11 11
12 12
13 13
14 14
15 15
16 16
17 17
18 18
19 19
20 20
21 21
22 function augment(destClass, srcClass, methods) {
23 var srcProto = srcClass.prototype
24 var destProto = destClass.prototype
25 for (var i=0; i<methods.length; i++) {
26 var method = methods[i]
27 if (!destProto[method]) {
28 destProto[method] = srcProto[method]
29 }
30 }
31 }
32 function Person() {}
33 Person.prototype.getName = function() {}
34 Person.prototype.setName = function() {}
35 Person.prototype.getAge = function() {}
36 Person.prototype.setAge = function() {}
37
38 function Student() {}
39
40 augment(Student, Person, ['getName', 'setName'])
41 var s1 = new Student()
42 console.log(s1) // Student { getName=function(), setName=function()}
Backbone是廣泛使用摻元類的庫
首先,Backbone庫自身就采用Mixin classes方式組織,如Backbone.Events是最底層的摻元類,它的方法(on/off/trigger...)都被Backbone.Model/Backbone.Collection/Backbone.View等繼承。代碼片段如下
1 _.extend(Model.prototype, Events, {
2 ...
3 })
4 _.extend(Collection.prototype, Events, {
5 ...
6 })
7 _.extend(View.prototype, Events, {
8 ...
9 })
它這裡使用_.extend來擴充Model,Collection,View的原型,把Events的方法都拷貝到原型。即Event就是一個摻元類(雖然被實作為一個對象)
其次,我們使用Backbone開發時,你寫的模型會用Backbone.Model去擴充,視圖會用Backbone.View去擴充。如
1 var MyModel = Backbone.Model.extend({
2 instanceProp: xx
3 },{
4 classProp: yy
5 })
6
7 var MyView = Backbone.Model.extend({
8 instanceProp: xx
9 },{
10 classProp: yy
11 })
這時,Backbone.Model/Backbone.View等就是摻元類了。當然,你還可以把underscore當做摻元對象,因為Backbone的很多類都繼承了_.extend方法,如Backbone.Events/Backbone.Model等。