剛開始,代碼只是代碼而已。當代碼多了,咱們發明了函數,以便代碼能夠重用。到後來,咱們的函數也愈來愈多,因此咱們又發明了別的一些編程概念。可是當咱們沉迷於這些新概念的同時,咱們可能就錯過了一些近在眼前的美麗事物……git
類概念主要存在於面向對象編程中的,可能你們上學時都學過,好比Java、C#、Objective-C等面嚮對象語言。每一個實體都須要先要有一個類,這個類對於咱們來講是比較抽象的(不是說abstract class哦),而後子類還能夠繼承父類。程序員
這感受彷佛有點亞里士多德形而上學和分類法的味道,但簡單來講這就是面向對象的基礎,在寫出實際代碼以前,你得先寫出一些類、接口,而後類繼承類、類實現接口……github
實際上咱們人類並非很是擅長分類,打個比方,一個按鈕(Button),它應該是一個矩形(Rectangle),仍是一個控件(Control)呢?咱們可讓Button繼承Rectangle,讓Rectangle繼承Control……等等,是否是有什麼不對?express
因此,面向對象的概念可能會在項目剛開始時就把你壓垮,但這並非說面向對象就很差,只是有時候並不適合你的項目。編程
可能你們都知道,JavaScript是不支持class
關鍵字的,由於JavaScript自己就不做爲一個面嚮對象語言來設計,但並不妨礙你們想出各類招來實現class
。api
JavaScript是基於原型鏈(Prototype)繼承的,可是其詭異的寫法可能讓好多初學者望而卻步。微信
function Circle() { this.radius = 7; } Circle.prototype = { area: function () { return Math.PI * this.radius * this.radius; }, grow: function () { this.radius++; }, shrink: function () { this.radius--; } };
在ES5以後,咱們有了Object.create
方法,好理解了一些。閉包
var circle = Object.create({ area: function () { return Math.PI * this.radius * this.radius; }, grow: function () { this.radius++; }, shrink: function () { this.radius--; } }, { radius: { writable: true, configurable: true, value: 7 } });
只是第二參數用起來有點複雜,其實就是跟Object.defineProperties()
的參數同樣,可是咱們如今又有了ES6呀:app
class Cicle { constructor() { this.radius = 7; } area() { return Math.PI * this.radius * this.radius; } grow() { this.radius++; } shrink() { this.radius--; } }
這都是一些使用JavaScript實現class
的例子,實際上,JavaScript還能夠有另一種重用代碼的方式——Mixins。框架
這就是咱們今天的主題,近在眼前的美麗事物,但可不是什麼新概念,用過Ruby或Python的同窗,可能有所耳目,Mixins的字面意思就是把東西摻合在一塊兒。
在JavaScript咱們有call
和apply
,很容易切換上下文,將各類互不相干糅合,達到Mixins的目的。
假設咱們要作一個圓形按鈕(RoundButton),它有兩個特性:
是圓的
可點擊
咱們能夠把這兩個特性分別寫作2個函數:
// 是圓的 var withCircle = function () { this.area = function () { return Math.PI * this.radius * this.radius; }; this.grow = function () { this.radius++; }; this.shrink = function () { this.radius--; } } // 可點擊 var withClickable = function () { this.hover = function () { console.log('hovering'); }; this.press = function () { console.log('button pressed'); }; this.release = function () { console.log('button released'); }; this.fire = function () { this.action.fire(); }; }
這是咱們的圓形按鈕:
var RoundButton = function(radius, label, action) { this.radius = radius; this.label = label; this.action = action; };
如今咱們要讓給這個圓形按鈕附上那兩個特性:
withCircle.call(RoundButton.prototype); withClickable.call(RoundButton.prototype); var button = new RoundButton(4, 'yes!', function() { return 'you said yes!' }); button1.fire(); // 輸出 'you said yes!'
這樣寫,是否是瞬間顯得既簡潔又天然?讓人一眼看懂代碼在作什麼。
固然這些附加特性的函數用的多了,也就建立了許多函數,這裏能夠簡單的用一個當即執行函數(Immediately Invoked Function Expression)的閉包來對其進行優化一下,以withCircle
爲例:
var withCircle = (function () { function area() { return Math.PI * this.radius * this.radius; } function grow() { this.radius++; } function shrink() { this.radius--; } return function () { this.area = area; this.grow = grow; this.shrink = shrink; }; })();
這樣就不須要每次使用都新建函數了,從而節省更多的資源。
有時候,你沒法確保某些函數可能會覆蓋原有的功能,例如如下例子:
Button.prototype.press = function() { console.log('pressed'); }; // 這時再用咱們的 withClickable,就會覆蓋掉 press withClickable.call(Button.prototype);
這時候咱們應該採用Advice,Twitter的Flight框架已經提供了此功能:
var withClickable = function () { this.after('press', function () { console.log('press again.'); }); }; withAdvice.call(Button.prototype); withClickable.call(Button.prototype); var button = new Button(); button.press(); // 輸出 'pressed', 'press again.'
兩個press
並不會互相沖突,而是有前後順序的執行,就相似經過addEventListener
添加了多個事件而不是直接修改onclick
同樣,具體細節能夠參考Flight的API。
做爲一名程序員,咱們或許在上學時就被灌輸了面向對象的固有思想,畢竟面向對象從上世紀90年代到如今,經久不衰,自由它的優點。可是在JavaScript中,若是你並不善於面向對象的抽象思惟,何不嘗試一下Mixins呢?並且Mixins與類繼承相比,還能更好的解耦合,能夠用於任何Object之上,正好用上了JavaScript若類型的優點。
最近在讀《Beautiful JavaScript》這本書,有一些好的內容,正好能夠跟你們分享,但並非所有,有興趣的同窗也能夠本身讀一下,請支持正版。
原文連接:http://t.cn/RteECIF
微信號:程序員晉級之路『code-learning』