JavaScriptで(Qt|.NETのevent)っぽいSignalSlot作った
UIを実装するとObserverパターンおよびその派生は必須になるので、毎回車輪の再発明をしてるんだけど、個人的にこれでいいんじゃね、的なコードができたので晒しておく。
利用側のコードはこんな感じ。
var ObservableList = function () { Signals.define(this, 'onElementAdded', 'onElementRemoved'); this._list = []; } ObservableList.prototype.add = function (value) { this._list.push(value); this.onElementAdded(value); return value; } ObservableList.prototype.removeAt = function (index) { var removed = this._list.splice(index, 1); this.onElementRemoved(removed); return removed; } // connect signal var list = new ObservableList(); list.onElementAdded.connect(function (sender, elem) { alert('added:' + elem); }); list.onElementRemoved.connect(function (sender, elem) { alert('removed:' + elem); }); // emit list.add("fuga"); list.add("hoge"); list.removeAt(0);
定義したSignalがfunctionになっていて、.NETのeventのようにfireが書けるというのがこいつの大きな特徴ですね。emitって書かなくていいQtともいう。
以下、Signals(signals.js)のコード。例のごとく一応MITライセンスにしておく。
/** * signals.js * * @author terurou * @license MIT License */ (function (window, document) { if (!window.Signals) window.Signals = {}; Signals.define = define; if (typeof Array.prototype.forEach !== 'function') { Array.prototype.forEach = function (callback, thisObj) { for (var i = 0, len = this.length; i < len; ++i) { callback.call(thisObj, this[i], i, this); } } } function connect(slot, one) { var slots = this.__slots; slots[slots.length] = [slot, !!one]; } function disconnect(slot) { var slots = this.__slots; for (var i = slots.length - 1; i >= 0; --i) { if (slots[i][0] === slot) slots.splice(i, 1); break; } } function disconnectAll() { this.__slots = []; } function define(sender/* ... */) { Array.prototype.slice.call(arguments, 1).forEach(function (name) { var signal = sender[name] = function (/* ... */) { var slots = signal.__slots; var len = slots.length; if (len === 0) return; var args = [sender].concat(Array.prototype.slice.call(arguments)); var newSlots = []; for (var i = 0; i < len; ++i) { var slot = slots[i]; slot[0].apply(null, args); if (!slot[1]) newSlots.push(slot); } signal.__slots = newSlots; } signal.__slots = []; signal.isSignal = true; signal.connect = connect; signal.disconnect = disconnect; signal.disconnectAll = disconnectAll; }); } })(window, document);
SignalSlotらしくconnect/disconnectというメソッド名にしておいたけど、attach/detachとかadd/removeの方がいいかなぁ。.NETっぽく似せるか、Qtっぽく似せるかによるけど。