JavaScriptコンポーネント設計思想(二)

13064 ワード

2016年3月に「JavaScriptコンポーネント設計思想」という文章を書きましたが、コンポーネント化の実現方法と各コンポーネントの結合度を低減する説明があります.その中でも「事件のメカニズム」は良い選択です.もっと多くの実践を通して、私にもっと多くの思考をもたらしました.
イベントの実現には、日常開発において、私たちは常にこのようなイベントを必要としています.
function Event() {
    this._events = Object.create(null);  //       
}
Event.prototype = {
    constructor: Event,
    //      key:    ,listener:      (              )
    on: function (event, fn) {
        var eventTarget = this;
        (eventTarget._events[event] || (eventTarget._events[event] = [])).push(fn);
        return eventTarget;
    },
    //      
    once: function (event, fn) {
        var eventTarget = this;
        function on() {
            eventTarget.off(event, on);
            fn.apply(eventTarget, arguments);
        }
        on.fn = fn;
        eventTarget.on(event, on);
        return eventTarget
    },
    //         
    off: function (event, fn) {
        var eventTarget = this;
        //       
        if (!arguments.length) {
            eventTarget._events = Object.create(null);
            return eventTarget
        }
        var cbs = eventTarget._events[event];
        if (!cbs) {
            return eventTarget
        }
        //         
        if (arguments.length === 1) {
            eventTarget._events[event] = null;
            return eventTarget
        }
        //            
        var cb;
        var i = cbs.length;
        while (i--) {
            cb = cbs[i];
            if (cb === fn || cb.fn === fn) {
                cbs.splice(i, 1);
                break
            }
        }
        return eventTarget
    },
    //          
    emit: function (event) {
        var eventTarget = this;
        var cbs = eventTarget._events[event];
        if (cbs) {
            cbs = cbs.length > 1 ? Array.prototype.slice.call(cbs) : cbs;
            var args = Array.prototype.slice.call(arguments, 1);
            for (var i = 0, l = cbs.length; i < l; i++) {
                cbs[i].apply(eventTarget, args);
            }
        }
        return eventTarget
    }
};
開発において、ユーザー登録が成功したら、私たちは「header」、「toolbar」、「menu」を初期化する必要があります.通常のやり方は登録成功のコールバックで対応モジュールの初期化関数を呼び出すことですが、このように各モジュール間の結合度が高すぎて、開発仕様に合わないです.だから、よく事件を借りて実現します.しかし、ある状態を「toolbar」で修正しました.他のモジュールにも対応した操作をするように通知します.この時には、「toolbar」にも事件行動を持たせる必要があります.今はPersonクラスを持っています.イベント行動を備えてほしいです.下記はいくつかの実施形態である.
一、伝統的なイベント方式
共通の“公共”の事件、実現は簡単で、多くのモジュールは同時に事件を備える時比較的に維持しにくいです!
//   Event  
var event = new Event();

function Person(name){
    this.name = name;
}
Person.prototype.sayName = function(){
    console.log(this.name);
    //     (Event  )
    event.emit("Person.sayName", this,name);
};
//     (Event  )
event.on("Person.sayName", function(args){
    console.log(args.name + " emit Person.sayName event!");
});

var p = new Person("ligang");
p.sayName();
二、疑似類の継承方式
Personはイベントの一種ですか?関係が複雑で、曖昧さが生じやすく、理解しにくいです.
//     
Person.prototype = new Event(); // Object.create(Event.prototype);
Person.prototype.constructor = Person;
Person.prototype.sayName = function(){
    console.log(this.name);
    //     (  Person  )
    this.emit("Person.sayName", this,name);
};
var p = new Person("ligang");
//     (  Person  )
p.on("Person.sayName", function(args){
    console.log(args.name + " emit Person.sayName event!");
});
p.sayName();
// person  Event    
console.log(p instanceof Person); // true
console.log(p instanceof Event);  // true
三、混入方式
mixin(des,src)は二つのパラメータを受け取ります.関数の目的は、プロバイダが持つエニュメレート・プロパティーを受信者にコピーすることです.
function mixin(des, src){
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            des[prop] = src[prop];
        }
    }
    return des;
}
ES 6に追加された方法Object.assign()は、オブジェクトのマージのために、ソースオブジェクトのエニュメレート・属性のすべてを対象オブジェクトにコピーする同じ目的を達成することができる.enumerableがfalseの属性であることを無視して、オブジェクト自体のエミュレート可能な属性のみをコピーします.なお、mixinとObject.assignは、深くコピーするのではなく、浅いコピーを実行しています.つまり、ソースオブジェクトのある属性の値が対象であれば、対象のオブジェクトがコピーされるのはこのオブジェクトの参照です.
var src = {a: {b: 1}, c: 1};
var des = {};
Object.assign(des, src);
src.a.b = 2;
src.c = 2;
console.log(des.a.b);   // 2
console.log(des.c);     // 1
混入方式は考え方がはっきりしていますが、Event構造関数を修正する必要があります.それは自分の属性と方法だけを拡張することができます.
function Event() {}
Event.prototype = {
    constructor: Event,
    //      key:    ,listener:      (              )
    on: function (event, fn) {
        var eventTarget = this;
        //         on   
        if(!this._events){
            this._events = Object.create(null);  //       
        }
        (eventTarget._events[event] || (eventTarget._events[event] = [])).push(fn);
        return eventTarget;
    },
    ...
function Person(name){
   this.name = name;
}
// mixin(Person.prototype, Event.prototype);
Object.assign(Person.prototype, Event.prototype);
mixin(Person.prototype, {
    constructor: Person,
    sayName: function() {
        console.log(this.name);
        this.emit("Person.sayName", this, name);
    }
});
var p = new Person("ligang");
p.on("Person.sayName", function(args){
    console.log(args.name + " emit Person.sayName event!");
});
p.sayName();

console.log(p instanceof Person); // true
console.log(p instanceof Event);  // false
四、プロジェクトの中でどうやって選択しますか?
上記で述べた「伝統的な方式」、「疑似的な継承」、「混入方式」の3つがプロジェクトの中で実現されていますが、どうやって選択すればいいですか?個人的には、プロジェクトでは、少なくとも2つのイベントが必要です.「バスイベント」と「支線イベント」.バスイベント:各モジュール間のリリース、購読(グローバル)を処理します.サブイベント:具体的なモジュール内部のリリース、購読(ローカル)!
//    ,  globalEvent  、       
var globalEvent = new Event();
globalEvent.on("login", ...);
globalEvent.emit("login");
//    ,  “  ”      、       
var HeaderSetting = function(){ ... }
Object.assign(HeaderSetting.prototype, Event.prototype);
var hs = new HeaderSetting();
hs.on("add", function(){
    ...
});
hs.addHeader = function(){
    this.emit("add");
    ...
}