天天看點

Mixin模式

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等。