Backbone继承系统、事件管理系统源码分析

Backbone继承系统

Backbone的继承机制使用es3规范,可以兼容到ie8。继承接口十分简洁。

var childClass = BaseClass.extend(protoProps /* 子类prototype属性 */, classProps /* 子类静态属性 */)
先看使用方法
// 继承基类
var extendBase = require('../utils/extendBase');

// 继承方法一:通过调用父类的构造方法
var parentModel1 = extendBase.extend({

    // 对象中的方法将被添加到子类的prototype上
    funA: function(options) {
        console.info('this is a function on prototype');
    }
}, {

    // 这个方法将被添加到子类的构造器上
    staticFun: function() {
        console.info('this is a static function');
    }
});

// 使用继承方法一
var p1 = new parentModel1();
p1.funA(); //  this is a function on prototype
parentModel1.staticFun(); // this is a static function

// 继承方法二:子类自己定义构造方法
var parentModel2 = extendBase.extend({

    // 定义子类的构造方法,如果参数中包含constructor,那么基类的构造器不会被调用
    constructor: function(){
        console.info('this is the constructor of parentModel2')
    },

    // 对象中的方法将被添加到子类的prototype上
    funA: function() {
        console.info('this is a function');
    }

    // 这个方法将被添加到子类的构造器上
    staticFun: function() {
        console.info('this is a static function');
    }
});

// 使用继承方法二
var p2 = new parentModel2(); // this is the constructor of parentModel2
p2.funA();  // this is a function
parentModel2.staticFun(); // this is a static function
Backbone继承的实现原理
var extend = function(protoProps, classProps) {
    return inherits(this, protoProps, classProps);
};

var inherits = function(parent, protoProps, staticProps) {
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent's constructor.

    // 调用子类构造方法
    if (protoProps && protoProps.hasOwnProperty('constructor')) {
        child = protoProps.constructor;
    } 
    // 调用父类构造方法
    else {
        child = function() {
            parent.apply(this, arguments);
        };
    }

    // Inherit class (static) properties from parent.

    // 父类静态方法继承
    _.extend(child, parent);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent`'s constructor function.

    // 最难理解的地方:这么做的好处是第一避免调用基类构造函数,第二消除了子类原型链上的实例属性
    var ctor = function() {}
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) _.extend(child.prototype, protoProps);

    // Add static properties to the constructor function, if supplied.
    if (staticProps) _.extend(child, staticProps);

    // Correctly set child's `prototype.constructor`.
    child.prototype.constructor = child;

    // Set a convenience property in case the parent's prototype is needed later.
    child.__super__ = parent.prototype;

    return child;
};

Backbone事件系统

Backbone的事件系统可以扩展的任何对象上,被扩展的对象具有on、off、trigger等方法,可以用来发送和监听事件。事件模块同样支持ie8。

使用方法
// 测试回调事件
var callback_listen_test = function(e) {
    console.info('callback_listen_test', e);
};
var callback_on_test = function(e) {
    console.info('callback_on_test', e);
};

// 定义模型对象和视图对象
var Model = _.extend({
    init: function() {
        // 监听Model对象触发的onTest事件
        this.on('onTest', callback_on_test);
    },
    print: function() {

        // Model对象触发listenToTest事件和onTest事件
        this.trigger('listenToTest', {arg:'test'});
        this.trigger('onTest', {arg:'test'});
    },

        // 卸载onTest事件的监听
        this.off('onTest', callback_on_test);
    }
}, Events);

var View = _.extend({
    init: function(m) {
        this.m = m;

        // 监听m对象发出的listenToTest事件
        this.listenTo(m, 'listenToTest', callback_listen_test);
    },
    stopListen: function() {
        var self = this;

        // 卸载对m对象的事件监听
        this.stopListening(self.m, 'listenToTest', callback_listen_test);
    }
}, Events);

Model.init();
View.init(Model);
// 事件触发
Model.print();
// 事件删除
View.stopListen();
Model.Off();
Model.print();
Backbone事件系统的实现原理
var Events = {

        // Bind an event to a `callback` function. Passing `"all"` will bind
        // the callback to all events fired.
        on: function(name, callback, context) {
          if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
          this._events || (this._events = {});
          var events = this._events[name] || (this._events[name] = []);
          events.push({callback: callback, context: context, ctx: context || this});
          return this;
        },

        // Bind an event to only be triggered a single time. After the first time
        // the callback is invoked, it will be removed.
        once: function(name, callback, context) {
          if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
          var self = this;
          var once = _.once(function() {
            self.off(name, once);
            callback.apply(this, arguments);
          });
          once._callback = callback;
          return this.on(name, once, context);
        },

        // Remove one or many callbacks. If `context` is null, removes all
        // callbacks with that function. If `callback` is null, removes all
        // callbacks for the event. If `name` is null, removes all bound
        // callbacks for all events.
        off: function(name, callback, context) {
          if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;

          // Remove all callbacks for all events.
          if (!name && !callback && !context) {
            this._events = void 0;
            return this;
          }

          var names = name ? [name] : _.keys(this._events);
          for (var i = 0, length = names.length; i < length; i++) {
            name = names[i];

            // Bail out if there are no events stored.
            var events = this._events[name];
            if (!events) continue;

            // Remove all callbacks for this event.
            if (!callback && !context) {
              delete this._events[name];
              continue;
            }

            // Find any remaining events.
            var remaining = [];
            for (var j = 0, k = events.length; j < k; j++) {
              var event = events[j];
              if (
                callback && callback !== event.callback &&
                callback !== event.callback._callback ||
                context && context !== event.context
              ) {
                remaining.push(event);
              }
            }

            // Replace events if there are any remaining.  Otherwise, clean up.
            if (remaining.length) {
              this._events[name] = remaining;
            } else {
              delete this._events[name];
            }
          }

          return this;
        },

        // Trigger one or many events, firing all bound callbacks. Callbacks are
        // passed the same arguments as `trigger` is, apart from the event name
        // (unless you're listening on `"all"`, which will cause your callback to
        // receive the true name of the event as the first argument).
        trigger: function(name) {
          if (!this._events) return this;
          var args = Array.prototype.slice.call(arguments, 1);
          if (!eventsApi(this, 'trigger', name, args)) return this;
          var events = this._events[name];
          var allEvents = this._events.all;
          if (events) triggerEvents(events, args);
          if (allEvents) triggerEvents(allEvents, arguments);
          return this;
        },

        // Inversion-of-control versions of `on` and `once`. Tell *this* object to
        // listen to an event in another object ... keeping track of what it's
        // listening to.
        listenTo: function(obj, name, callback) {
          var listeningTo = this._listeningTo || (this._listeningTo = {});
          var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
          listeningTo[id] = obj;
          if (!callback && typeof name === 'object') callback = this;
          obj.on(name, callback, this);
          return this;
        },

        listenToOnce: function(obj, name, callback) {
          if (typeof name === 'object') {
            for (var event in name) this.listenToOnce(obj, event, name[event]);
            return this;
          }
          if (eventSplitter.test(name)) {
            var names = name.split(eventSplitter);
            for (var i = 0, length = names.length; i < length; i++) {
              this.listenToOnce(obj, names[i], callback);
            }
            return this;
          }
          if (!callback) return this;
          var once = _.once(function() {
            this.stopListening(obj, name, once);
            callback.apply(this, arguments);
          });
          once._callback = callback;
          return this.listenTo(obj, name, once);
        },

        // Tell this object to stop listening to either specific events ... or
        // to every object it's currently listening to.
        stopListening: function(obj, name, callback) {
          var listeningTo = this._listeningTo;
          if (!listeningTo) return this;
          var remove = !name && !callback;
          if (!callback && typeof name === 'object') callback = this;
          if (obj) (listeningTo = {})[obj._listenId] = obj;
          for (var id in listeningTo) {
            obj = listeningTo[id];
            obj.off(name, callback, this);
            if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
          }
          return this;
        }

      };

      // Regular expression used to split event strings.
      var eventSplitter = /\s+/;

      // Implement fancy features of the Events API such as multiple event
      // names `"change blur"` and jQuery-style event maps `{change: action}`
      // in terms of the existing API.
      var eventsApi = function(obj, action, name, rest) {
        if (!name) return true;

        // Handle event maps.
        if (typeof name === 'object') {
          for (var key in name) {
            obj[action].apply(obj, [key, name[key]].concat(rest));
          }
          return false;
        }

        // Handle space separated event names.
        if (eventSplitter.test(name)) {
          var names = name.split(eventSplitter);
          for (var i = 0, length = names.length; i < length; i++) {
            obj[action].apply(obj, [names[i]].concat(rest));
          }
          return false;
        }

        return true;
      };

      // A difficult-to-believe, but optimized internal dispatch function for
      // triggering events. Tries to keep the usual cases speedy (most internal
      // Backbone events have 3 arguments).
      var triggerEvents = function(events, args) {
        var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
        switch (args.length) {
          case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
          case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
          case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
          case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
          default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
        }
      };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值