JavaScript组件设计思想(二)

转载 2017年01月03日 16:07:09

目录(?)[+]

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
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

开发中,我们经常会遇到在用户登录成功后我们需要初始化“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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

二、伪类继承方式

Person是Event的一种?关系比较复杂,容易产生歧义,不易理解!

// 继承事件
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

三、混入方式

mixin(des, src)接受两个参数:接受者和提供者。函数的目的是将提供者所有的可枚举属性复制给接受者。

function mixin(des, src){
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            des[prop] = src[prop];
        }
    }
    return des;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ES6中新增方法Object.assign()可以达到同样的目的,其用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。会忽略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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

混入方式实现思路清晰,但需要将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;
    },
    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

四、项目中如何选择

上述描述的“传统方式”、“伪类继承”、“混入方式”三种实现在项目中,我们该如何抉择呢?个人认为,在项目中,我们至少需要两种事件:“总线事件”和“支线事件”。 
总线事件:处理各个模块之间的发布、订阅(全局)! 
支线事件:具体模块内部的发布、订阅(局部)!

// 模块间,通过globalEvent发布、订阅事件的机制
var globalEvent = new Event();
globalEvent.on("login", ...);
globalEvent.emit("login");
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
// 模块内,通过“混入”方式具备发布、订阅事件的机制
var HeaderSetting = function(){ ... }
Object.assign(HeaderSetting.prototype, Event.prototype);
var hs = new HeaderSetting();
hs.on("add", function(){
    ...
});
hs.addHeader = function(){
    this.emit("add");
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

JavaScript组件设计思想

将每个功能点最小颗粒化、然后将其封装成模块;创建数据中心,使各个模块不在互相调用嵌套,所有的依赖和调用全部通过数据中心(这里使用自定义事件实现的观察者模式);所有的网状的需求点,划点成线,最终形成操作...
  • ligang2585116
  • ligang2585116
  • 2016年03月19日 19:44
  • 1603

JavaScript组件设计思想(二)

2016年3月份曾写过一篇文章《JavaScript组件设计思想》其中描述了一些实现组件化的方式,以及降低各组件耦合度的说明。其中“事件机制”不失为好的选择!经过了更多实践给我带来了更多的思考。 事件...
  • ligang2585116
  • ligang2585116
  • 2017年01月02日 20:08
  • 2386

Android的面向组件思想

面向组件思想是在软件规模扩大,复杂度上升的背景下,以面向对象为基础而提出的一种软件设计思想。可以把它理解为一种更粗粒度的面向对象,其粒度一般大于对象,但具体要到什么程度,又可以根据实际情况来决定。这种...
  • luoxinwu123
  • luoxinwu123
  • 2012年09月26日 09:27
  • 2000

Android组件设计思想

内容来自罗升阳的PPT 从四个方面说起: 组件化背景 组件化设计 组件化支持 一、组件化背景 从PC客户端应用程序说起: 开发者角度 复杂,同时兼顾UI、交互和业务逻辑 运行载体是进程 进程只有一...
  • chenfuduo_loveit
  • chenfuduo_loveit
  • 2015年01月05日 14:09
  • 762

YARN的设计思想和功能组件简介

Yarn的设计思想A. Yarn(Yet Another Resource Negotiator) B. Yarn的基本思想: 将JobTracker啷个主要功能分离成单独的组件,一个全局的R...
  • s646575997
  • s646575997
  • 2016年07月01日 16:30
  • 839

面向对象的JavaScript(2): 对象就是二元组

什么是对象?这里有很多关于对象的定义:对象(object)是一件事、一个实体、一个名词,是可以获得的东西,是可以想象的能够有自己标识的任何东西。对象是类的实例化。对象是一些相关的变量和方法的软件集合。...
  • FinderCheng
  • FinderCheng
  • 2009年04月21日 20:42
  • 1312

设计思想二

我在项目中的分层是:Action->POJO/VO->service(接口+实现类)->DAO((接口+实现类)   Action:接收用户请求、获取用户输入的信息、调用Service、控制页面跳转等...
  • ldongwei
  • ldongwei
  • 2010年04月05日 20:21
  • 377

Bootstrap之CSS架构的设计思想

整体上,不同名的样式可以叠加到一起使用,同名的样式,后面的会覆盖前面的,从而到达组合应用的效果,整个CSS组件有8大类型的样式,然后根据每个组件的特性,来组装这些类型的特性,从而达到丰富多彩的配置效果...
  • u010874036
  • u010874036
  • 2016年03月13日 15:04
  • 738

JavaScript 设计思想

1.Javascript 有对象没有“类” Javascript里面都是对象,必须有一种机制,将所有对象联系起来。但是javascript并没有类的概念。。。 2.new关键字 java...
  • ve7ev
  • ve7ev
  • 2012年07月10日 23:12
  • 901

开发JavaScript组件(完整示例)

使用JavaScript,按照面向对象的思想来构建组件。 现以构建一个TAB组件为例。 从功能上讲,组件包括可视部分和逻辑控制部分;从代码结构上讲,组件包括代码部分和资源部分(样式、图片等)。 组件的...
  • hongweigg
  • hongweigg
  • 2015年01月15日 20:07
  • 6550
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JavaScript组件设计思想(二)
举报原因:
原因补充:

(最多只允许输入30个字)