GithubHelp home page GithubHelp logo

araledoc's Introduction

Hi, I'm Samuel 👨🏻‍💻

I'm a web developer and work as a frontend engineer at ByteDance. I enjoy working with VueJS, ReactJS, Node.js, Python.

  • 🌍  Based in Shanghai, China
  • ✉️  You can contact me at [email protected]
  • ⌚️  Web Developer, VIMer, Guitar player, Mystery lover, Cyberpunk enthusiast

Tools I use

Nerd Stats

araledoc's People

Contributors

classicemi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

pdap yangnuowei88

araledoc's Issues

03. Base01.md

前言

Base这个模块实际上才是Arale模块系统中对外的模块,它包含了之前介绍的Class类和Events类,以及自己内部的attribute模块和aspect模块,因此Base模块是真正的基础类。

由于Attribute模块的内容太多,而Aspect模块和它关系也不太大,因此,考虑到文章篇幅的平衡,将Base模块的解析分成两篇,Attribute模块的分析放在下一篇单独来写。

带注释源码

Base源码的开头是这样的:

var Class = require('arale-class');
var Events = require('arale-events');
var Aspect = require('./aspect');
var Attribute = require('./attribute');

可见,整个Base的实现是基于上面这四个模块的,前两个模块已经分析过了,下面来分析后面两个模块。首先是Aspect模块,这个模块实际上只提供了两个方法beforeafter

// `before`和`after`实际上是对`weave`方法的一次封装,提供易用的接口
// 在指定方法执行前,先执行 callback
exports.before = function(methodName, callback, context) {
  return weave.call(this, 'before', methodName, callback, context);
};

// 在指定方法执行后,再执行 callback
exports.after = function(methodName, callback, context) {
  return weave.call(this, 'after', methodName, callback, context);
};

// Helpers
// -------
// 事件分割
var eventSplitter = /\s+/;

/**
 * 控制callback的执行时序
 * @param  {String}   when       选择是`before`还是`after`
 * @param  {String}   methodName 方法名字符串
 * @param  {Function} callback   回调函数
 * @param  {Object}   context    上下文对象
 * @return {Object}              调用此方法的对象
 */
function weave(when, methodName, callback, context) {
  // 取得方法名数组
  var names = methodName.split(eventSplitter);
  var name, method;

  // 遍历方法名数组
  while (name = names.shift()) {
    // 取得方法函数
    method = getMethod(this, name);
    // 方法是否被改造过,如果没有则进行改造
    if (!method.__isAspected) {
      wrap.call(this, name);
    }
    // 绑定一下事件
    this.on(when + ':' + name, callback, context);
  }

  return this;
}

/**
 * 取得对应名称的方法
 * @param  {Object} host       调用对象
 * @param  {String} methodName 方法名称
 * @return {Function}            方法函数
 */
function getMethod(host, methodName) {
  // 取得对象上对应的方法函数
  var method = host[methodName];
  // 如果方法不存在则报错
  if (!method) {
    throw new Error('Invalid method name: ' + methodName);
  }
  return method;
}

/**
 * [wrap description]
 * @param  {[type]} methodName [description]
 * @return {[type]}            [description]
 */
function wrap(methodName) {
  // 取得对象上的方法
  var old = this[methodName];

  // 对方法进行改造封装
  // 改造过的方法执行时,会先触发'before:methodName'事件
  this[methodName] = function() {
    // 切分参数
    var args = Array.prototype.slice.call(arguments);
    // 在参数数组前添加一项'before:methodName'
    var beforeArgs = ['before:' + methodName].concat(args);

    // prevent if trigger return false
    // 先触发`before:methodName`事件,如果存在回调函数队列且执行后返回false,则阻止进一步往下执行
    if (this.trigger.apply(this, beforeArgs) === false) return;

    // 执行原方法,保存返回值
    var ret = old.apply(this, arguments);

    // 构造参数数组,执行`after:methodName`事件
    var afterArgs = ['after:' + methodName, ret].concat(args);
    this.trigger.apply(this, afterArgs);

    return ret;
  };

  // 修改方法是否被改造状态属性
  this[methodName].__isAspected = true;
}

然后是Base模块,它集成了Event, AspectAttribute模块的各种属性,实际上是Arale类库的一个入口模块:

var Class = require('arale-class');
var Events = require('arale-events');
var Aspect = require('./aspect');
var Attribute = require('./attribute');


module.exports = Class.create({
  // 混入Events, Aspect, Attribute模块的所有属性
  Implements: [Events, Aspect, Attribute],

  // 所有用`Base.extend()`构建的类在初始化时都会调用的方法
  initialize: function(config) {
    this.initAttrs(config);

    // 将`this._onChangeAttr`注册为`change:attr`事件的监听函数
    parseEventsFromInstance(this, this.attrs);
  },

  destroy: function() {
    // 卸载所有事件监听
    this.off();

    // 清除所有属性
    for (var p in this) {
      if (this.hasOwnProperty(p)) {
        delete this[p];
      }
    }

    // destroy一次后this都被清除了,再次调用会报错,因此生成一个空的destroy,该方法与主同在
    this.destroy = function() {};
  }
});

/**
 * 将`_onChangeAttr`方法注册为`change:attr`事件的监听函数
 * @param  {Class} host  调用对象
 * @param  {Object} attrs 包含所有要注册属性的对象
 */
function parseEventsFromInstance(host, attrs) {
  for (var attr in attrs) {
    if (attrs.hasOwnProperty(attr)) { // 检测attr是attrs的非继承属性
      var m = '_onChange' + ucfirst(attr);
      if (host[m]) {
        host.on('change:' + attr, host[m]);
      }
    }
  }
}

/**
 * 将首字母转变为大写
 * @param  {String} str 要处理的字符串
 * @return {String}     处理完的字符串
 */
function ucfirst(str) {
  return str.charAt(0).toUpperCase() + str.substring(1);
}

源码分析

Aspect

Aspect模块就是实现了两个方法,beforeafter。这两个方法的作用就是针对类上的某个方法,给这个方法绑定先于其执行和后于其执行的回调函数。

两个方法实际上调用的都是同一个方法weave,只是将before和after作为参数传入,在weaver方法中,对要进行beforeafter“伪事件”绑定的方法进行查找,找到后会检测这个方法上是否有__isAspected属性。这个属性的作用是标示出此方法有没有被进行过伪事件的“包装”。

上一段连续提到两次“伪事件”这个词,它是我编出来的,表示的意思为before:methodNameafter:methodName这样的事件并不能成为一个独立的事件,而是依附于methodName这个原方法的。原来的事件执行流程是这样的。

event.trigger(eventName)  +------------+
------------------------->| someMethod |----------->被触发执行
                          +------------+

一旦在someMethod上注册了afterbefore事件后,someMethod就会被封装成一个新的函数:

someMethod被封装后生成的新wrappedMethod:
                                      |trigger()
                  +-------------------------------------------------------+
                  |wrappedMethod:     |触发`before:method`事件             |
                  |                   |                                   |
                  |            +---------------+  return false +-----+    |
                  |            |  beforeMethod |-------------->| end |    |
                  |            +---------------+               +-----+    |
                  |                   |return true                        |
                  |                   |                                   |
                  |            +---------------+                          |
                  |            |    method     |                          |
                  |            +---------------+                          |
                  |                   |触发`after:method`事件              |
                  |                   |                                   |
                  |            +---------------+                          |
                  |            |  afterMethod  |                          |
                  |            +---------------+                          |
                  +-------------------------------------------------------+

整个模块的关键就在于wrap这个用来封装方法的函数了,当然实现这一功能的也需要功能完备的Event模块的支持。

02. Events

带注释源码

// Regular expression used to split event strings
// 用于分割事件名的正则,识别空格
var eventSplitter = /\s+/


// A module that can be mixed in to *any object* in order to provide it
// with custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
//     var object = new Events();
//     object.on('expand', function(){ alert('expanded'); });
//     object.trigger('expand');
//
// 介绍使用方法,这个模块可以混入任何对象之中,实现对自定义事件的资瓷~
function Events() {
}


// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
// 将空格分割的事件绑定给对象,事件名为all的话,事件回调函数在任何事件被触发时都会调用。
Events.prototype.on = function(events, callback, context) {
  var cache, event, list
  // 回调函数不存在,直接返回
  if (!callback) return this

  // 将对象的`__events`属性缓存,`__events`属性不存在则初始化为空对象
  cache = this.__events || (this.__events = {})
  // 将参数中的事件字符串进行分割,得到事件名数组
  events = events.split(eventSplitter)

  // 循环遍历`events`中的事件
  while (event = events.shift()) {
    // 查询cache中是否缓存了事件,如果有,取得这个事件的回调函数队列的引用,如果没有,初始化为空数组
    list = cache[event] || (cache[event] = [])
    // 将回调和上下文存入回调函数队列
    list.push(callback, context)
  }

  return this
}

// 绑定只执行一次就销毁的事件回调
Events.prototype.once = function(events, callback, context) {
  var that = this
  // 对传入的`callback`进行一次封装,`cb`内调用`off`方法,调用一次就解绑
  var cb = function() {
    that.off(events, cb)
    callback.apply(context || that, arguments)
  }
  // 将封装后的`cb`进行绑定
  return this.on(events, cb, 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 `events` is null, removes all bound callbacks for all events.
// 移除一个或多个回调,如果`context`为空,移除所有同名的回调。
// 如果`callback`为空,移除该事件上所有回调。
// 如果`events`为空,移除所有时间上绑定的所有回调函数。
Events.prototype.off = function(events, callback, context) {
  var cache, event, list, i

  // No events, or removing *all* events.
  // 如果没有任何已绑定事件,直接返回
  if (!(cache = this.__events)) return this
  // 如果三个参数都没传,则删除对象上的`__events`属性,并返回对象
  if (!(events || callback || context)) {
    delete this.__events
    return this
  }

  // 对传入的`events`进行分割处理,如果没有传入`events`,取得缓存中的所有事件
  events = events ? events.split(eventSplitter) : keys(cache)

  // Loop through the callback list, splicing where appropriate.
  // 循环遍历events
  while (event = events.shift()) {
    // 保存事件回调队列
    list = cache[event]
    // 如队列为空,跳过
    if (!list) continue

    // 如果`callback`和`context`都没传,则删除该事件队列
    if (!(callback || context)) {
      delete cache[event]
      continue
    }

    // 遍历回调队列,注意每个回调和其调用上下文是间隔排列的,步长为2
    // 和传入的`callback`以及`context`比较,都符合的则将回调和调用上下文从数组中移除
    for (i = list.length - 2; i >= 0; i -= 2) {
      if (!(callback && list[i] !== callback ||
          context && list[i + 1] !== context)) {
        list.splice(i, 2)
      }
    }
  }

  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).
Events.prototype.trigger = function(events) {
  var cache, event, all, list, i, len, rest = [], args, returned = true;
  // 如果没有绑定过任何事件,直接返回
  if (!(cache = this.__events)) return this

  // 分割
  events = events.split(eventSplitter)

  // Fill up `rest` with the callback arguments.  Since we're only copying
  // the tail of `arguments`, a loop is much faster than Array#slice.
  // 将除第一个参数`events`外的所有参数保存保存为数组存入`rest`
  for (i = 1, len = arguments.length; i < len; i++) {
    rest[i - 1] = arguments[i]
  }

  // For each event, walk through the list of callbacks twice, first to
  // trigger the event, then to trigger any `"all"` callbacks.
  // 对于每个事件,遍历两次回调队列,第一次是触发那个事件,第二次是触发任何`all`事件的回调
  while (event = events.shift()) {
    // Copy callback lists to prevent modification.
    // 如果缓存中存在all事件,将其回调队列分割存入all
    if (all = cache.all) all = all.slice()
    // 如果缓存中有当前遍历到的事件,将其回调队列分割存入list
    if (list = cache[event]) list = list.slice()

    // Execute event callbacks except one named "all"
    // 当遍历到的事件名不是all时,触发事件的所有回调,以this作为调用上下文
    if (event !== 'all') {
      returned = triggerEvents(list, rest, this) && returned
    }

    // Execute "all" callbacks.
    // 触发对应all事件的所有回调
    returned = triggerEvents(all, [event].concat(rest), this) && returned
  }

  // 返回值
  return returned
}

// trigger == emit
Events.prototype.emit = Events.prototype.trigger


// Helpers
// -------
// 保存对`Object.keys`方法的引用
var keys = Object.keys

// 不存在`Object.keys`方法时就自己实现
if (!keys) {
  // 接受一个对象,返回该对象所有自有属性
  keys = function(o) {
    var result = []

    for (var name in o) {
      if (o.hasOwnProperty(name)) {
        result.push(name)
      }
    }
    return result
  }
}

// Mix `Events` to object instance or Class function.
// 将`Events`混入任何一个Class类的实例
Events.mixTo = function(receiver) {
  // 保存`Events`的原型
  var proto = Events.prototype

  // 判断接收对象类型,是否为构造函数
  if (isFunction(receiver)) {
    // 遍历`Events`原型内方法
    for (var key in proto) {
      // 将自有方法进行复制
      if (proto.hasOwnProperty(key)) {
        receiver.prototype[key] = proto[key]
      }
    }
    // 经过调试这步和上步的作用是一样的,只是调用了ES5的API,不知是否和兼容性有关
    Object.keys(proto).forEach(function(key) {
      receiver.prototype[key] = proto[key]
    })
  }
  else { // 针对接收者不是构造函数而是实例的情况
    // 生成Event类的实例
    var event = new Events
    // 遍历,判断,复制
    for (var key in proto) {
      if (proto.hasOwnProperty(key)) {
        copyProto(key)
      }
    }
  }

  // 复制属性
  function copyProto(key) {
    receiver[key] = function() {
      // 由于receiver已经是一个对象而不是构造函数,所以将所有方法的执行上下文转换为一个Event类的实例
      proto[key].apply(event, Array.prototype.slice.call(arguments))
      return this
    }
  }
}

// Execute callbacks
/**
 * 执行回调的方法
 * @param  {Array} list    回调函数队列
 * @param  {Array} args    参数数组
 * @param  {Object} context 调用上下文
 * @return {Boolean} pass
 */
function triggerEvents(list, args, context) {
  var pass = true

  if (list) {
    var i = 0, l = list.length, a1 = args[0], a2 = args[1], a3 = args[2]
    // call is faster than apply, optimize less than 3 argu
    // http://blog.csdn.net/zhengyinhui100/article/details/7837127
    // 由于`call`方法要比`apply`快,因此针对参数数量少于等于3个的情况进行优化,调用`call`,参数数量大于3个时调用`apply`
    switch (args.length) {
      case 0: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context) !== false && pass} break;
      case 1: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1) !== false && pass} break;
      case 2: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2) !== false && pass} break;
      case 3: for (; i < l; i += 2) {pass = list[i].call(list[i + 1] || context, a1, a2, a3) !== false && pass} break;
      default: for (; i < l; i += 2) {pass = list[i].apply(list[i + 1] || context, args) !== false && pass} break;
    }
  }
  // trigger will return false if one of the callbacks return false
  // 有一个回调函数的返回值为false则pass值为false
  return pass;
}

// 判断是否为Function类型的工具函数
function isFunction(func) {
  return Object.prototype.toString.call(func) === '[object Function]'
}

module.exports = Events

分析

这个Events类的运行方式还是比较简单的,这里就把实现机制归纳一下,这个应该是学习的重点。具体代码层面的实现看源码和注释就行了。

事件的所有相关信息全部保存在对象的__events属性上,该属性值是一个对象,以k-v的形式保存事件名和回调队列的对应关系,结构就像这样:

{
  'click': [callback1, context1, callback2, context2, ...],
  'remove': [callback1, context1, callback2, context2, ...],
  ...
}

一旦触发了某个事件,比如click,那么它对应的回调队列中的所有回调函数就会依次被执行。值得一提的时每个回调函数都有各自的执行上下文对象,这个比较特别,回调和上下文在数组中是间隔排列的,因此触发事件和解除绑定时都会特别处理这种特殊的数据结构。我认为之所以选用数组这种结构主要还是为了保证所有回调的触发顺序可控,如果用对象的话,遍历时的顺序是不一定的。针对这个问题,在玉伯和一位开发者的讨论中也能得到答案。

另外值得一提的是all这个事件,在一个对象上触发任何事件,同时也一定会触发all事件,实现原理很简单,就是在trigger这个方法中,判断一下事件缓存中有没有all这个事件队列,如果有,那么不管触发哪个事件,最后都再触发一下all事件队列即可。

if (this.constructor === SubClass && this.initialize) 注释写的不对

// Only call initialize in self constructor.
// 当this.constructor为SubClass本身(即Parent的构造函数未修改constuctor属性值),
// 及父类构造函数中有initialize方法时,在this上调用自身的initialize方法
if (this.constructor === SubClass && this.initialize) {
this.initialize.apply(this, arguments)
}

这里的this其实就是SubClass的实例,this.constructor 就等于SubClass.prototype.constructor ,这个和Parent的构造函数有什么关系? 另外this.initialize 不是父类构造函数的方法,因该是判断是否有SubClass.prototype.initialize

关于attribute.js

attribute.js这个可以多多的分析下吗?我实在这个看不太明白!

01. Class

开篇明义

这是本系列的第一篇,对于Arale中每个模块的分析文章将采取同样的结构。前半部分是带注释源码,在模块源码中会添加尽可能详细的注释。后半部分则是分析,针对模块的运作方式进行具体分析。

带注释源码

// The base Class implementation.
// 基础Class类的实现,Class类作为返回的对象,本身也可接受对象参数
function Class(o) {
  // Convert existed function to Class.
  // 将现有的函数转换为Class类
  if (!(this instanceof Class) && isFunction(o)) {
    return classify(o)
  }
}

module.exports = Class


// Create a new Class.
//
//  var SuperPig = Class.create({
//    Extends: Animal,
//    Implements: Flyable,
//    initialize: function() {
//      SuperPig.superclass.initialize.apply(this, arguments)
//    },
//    Statics: {
//      COLOR: 'red'
//    }
// })
//
/**
 * 创建Class子类
 * @param  {Function} parent     要继承的父类的构造函数
 * @param  {Object} properties 包含要混入属性的对象
 * @return {Function} 生成子类的构造函数
 */
Class.create = function(parent, properties) {
  // 首先对第一个参数进行类型验证,是否为函数
  if (!isFunction(parent)) {
    // 如不是函数,将值赋给properties,再将parent设为null
    properties = parent
    parent = null
  }
  // 如properties是undefined或null等为false的值,将properties设为空对象
  properties || (properties = {})
  // 如parent为null,且properties有Extends属性,则将Extends属性的值赋给parent,
  // 如properties没有Extends属性,则将Class赋给parents,以Class为父类
  parent || (parent = properties.Extends || Class)
  // 将parents赋给properties,如原来properties无Extends属性,此时其Extends属性将为父类构造函数或Class
  properties.Extends = parent

  // The created class constructor
  // 用作生成子类的构造函数雏形
  function SubClass() {
    // Call the parent constructor.
    // 在this上调用父类构造函数
    parent.apply(this, arguments)

    // Only call initialize in self constructor.
    // 当this.constructor为SubClass本身(即Parent的构造函数未修改constuctor属性值),
    // 及父类构造函数中有initialize方法时,在this上调用自身的initialize方法
    if (this.constructor === SubClass && this.initialize) {
      this.initialize.apply(this, arguments)
    }
  }

  // Inherit class (static) properties from parent.
  // 从parent继承类的静态属性
  // 判断parent不是Class
  if (parent !== Class) {
    // 将parent的静态属性混入SubClass中,如果parent有StaticsWhiteList属性,则复制其指定的属性。
    mix(SubClass, parent, parent.StaticsWhiteList)
  }

  // Add instance properties to the subclass.
  // 调用implement方法,具体操作见implement函数注释
  implement.call(SubClass, properties)

  // Make subclass extendable.
  // 最后,对SubClass构造函数进行classify操作,在SubClass上添加extend和implement这两个Class类特有的方法,然后返回出去
  return classify(SubClass)
}

/**
 * 使子类混入属性或调用一些特殊的方法,这个方法只有在构建SubClass时的时候才会有用,所以没有挂载到Class上
 * @param  {Object} properties 包含某些属性的对象
 */
function implement(properties) {
  var key, value

  // 遍历properties中的属性
  for (key in properties) {
    // 暂存properties中属性对应的属性值
    value = properties[key]
    // 如果Class类的工具方法中有同名方法,则在this上调用该方法,暂存的value值作为参数
    if (Class.Mutators.hasOwnProperty(key)) {
      Class.Mutators[key].call(this, value)
    } else {
      // 如没有同名方法,则进行简单的赋值操作
      this.prototype[key] = value
    }
  }
}


// Create a sub Class based on `Class`.
// 以Class类或调用extend方法的类为父类,生成混入properties属性的子类
Class.extend = function(properties) {
  // 如不存在properties,给properties赋空对象作为默认值
  properties || (properties = {})
  // 将properties的Extends属性设为this,表示以this为父类
  properties.Extends = this

  // 调用create方法返回新的子类
  return Class.create(properties)
}

// 给cls添加`Class.extend`和`implement`方法
function classify(cls) {
  cls.extend = Class.extend
  cls.implement = implement
  return cls
}


// Mutators define special properties.
// Class类自有的一些方法,保存在Class的一些属性上,子类不会继承,只是作为构建子类时的工具函数使用
Class.Mutators = {
  /**
   * SubClass调用此方法,在原型上添加父类原型上的方法
   * @param  {Function} parent 要生成子类的父类构造函数
   */
  'Extends': function(parent) {
    // 保存this的原型对象
    var existed = this.prototype
    // 创建一个以parent.prototype为原型的空对象
    var proto = createProto(parent.prototype)

    // Keep existed properties.
    // 在proto这个空对象上混入this的原型对象上的属性
    mix(proto, existed)

    // Enforce the constructor to be what we expect.
    // proto的constructor指向this,为了构造正确的原型链
    proto.constructor = this

    // Set the prototype chain to inherit from `parent`.
    // 将proto赋给this的prototype对象,这样this的prototype上既有原有的属性,又有Extend的类的原型对象上的属性
    this.prototype = proto

    // Set a convenience property in case the parent's prototype is
    // needed later.
    // 将父类的prototye保存为this的superclass属性,可以通过superclass快速访问
    this.superclass = parent.prototype
  },

  /**
   * 从某些类中混入属性
   * @param  {Array|Function} items 包含提供属性的类的数组
   */
  'Implements': function(items) {
    // 检测参数类型,单个构造函数用数组包裹
    isArray(items) || (items = [items])
    // 保存子类的原型对象
    var proto = this.prototype, item

    // 循环遍历
    while (item = items.shift()) {
      // 将item原型对象中的属性混入子类原型对象中,如item没有原型对象,则item是包含需混入的属性的对象,直接mix即可
      mix(proto, item.prototype || item)
    }
  },

  // 将属性作为静态属性加入子类,这些属性不会被继续继承
  'Statics': function(staticProperties) {
    mix(this, staticProperties)
  }
}


// Shared empty constructor function to aid in prototype-chain creation.
// 无constructor的空函数,用于原型链的构造。
function Ctor() {
}

// See: http://jsperf.com/object-create-vs-new-ctor
// 工具函数,返回一个以proto为原型的空对象
var createProto = Object.__proto__ ?
    function(proto) {
      return { __proto__: proto }
    } :
    function(proto) {
      Ctor.prototype = proto
      return new Ctor()
    }


// Helpers
// 工具方法
// ------------

/**
 * 将s中的属性混入r
 * @param  {Object} r  接受复制对象
 * @param  {Object} s  被复制对象
 * @param  {Array} wl  白名单,用于特别指定要复制的属性
 */
function mix(r, s, wl) {
  // Copy "all" properties including inherited ones.
  // 将s对象的所有属性,包括继承的属性,全部复制到新的r对象中
  for (var p in s) {
    if (s.hasOwnProperty(p)) {
      if (wl && indexOf(wl, p) === -1) continue

      // 在 iPhone 1 代等设备的 Safari 中,prototype 也会被枚举出来,需排除
      if (p !== 'prototype') {
        r[p] = s[p]
      }
    }
  }
}

// 对Object.prototype.toString方法的引用
var toString = Object.prototype.toString

// 检测是否为数组方法
var isArray = Array.isArray || function(val) {
    return toString.call(val) === '[object Array]'
}

// 检测是否为函数方法
var isFunction = function(val) {
  return toString.call(val) === '[object Function]'
}

// 查询元素在数组中的索引值,如不存在则返回-1
var indexOf = Array.prototype.indexOf ?
    function(arr, item) {
      return arr.indexOf(item)
    } :
    function(arr, item) {
      for (var i = 0, len = arr.length; i < len; i++) {
        if (arr[i] === item) {
          return i
        }
      }
      return -1
    }

分析

Class类是整个Arale类库的基础,所有在Arale中使用到的类都是由Class构建的,因为其构建的所有类都包含特定的方法,有特殊性,是根据Arale的需要定制的。所有基于Arale的开发都要遵循Class类的规定,可以说这个类是Arale生态圈的基石。

既然有官方文档,具体使用方法就不用多说了,下面分析一下具体实现。

首先介绍一下模块中的工具函数,分别是:

mix() // 用于混入属性的方法
toString() // 转换为字符串类型的方法
isArray(), isFunction() 类型检测方法
indexOf() // 计算元素在数组中索引值的方法

具体实现见源码及注释即可。

首先是Class函数,这个函数是对外暴露的,所有方法都可以在它上面调用。可在Class上调用的方法只有两个,分别是Class.create()Class.extend()。先来看Class.create()

源码中,首先是做的是参数的处理工作,针对某些参数未传的情况作了调整,最后达到的效果是parent的值为传入的父类构造函数,如果没有,设为nullproperties为需要混入属性的对象,其中可能有些Arale规定的特殊的属性会进行特殊处理,这个后面会说。

下面一步,针对parentnull的情况,parentnull时,如properties中有Entends属性,则将该属性值赋给parent,如果没有Extends,则将Class赋给parent。意思就是,有Extends属性时,属性值作为子类的父类,如果没有,Class作为父类。然后将parent回头赋给properties.Extends,这是针对parentClass的情况。

再往后声明了子类的构造函数雏形——SubClass函数,在函数内首先在this上调用parent的构造函数。下一个if语句:

if (this.constructor === SubClass && this.initialize) {
  this.initialize.apply(this, arguments)
}

其作用是处理父类构造函数没有修改this的constructor属性值并且有initialize方法的时候,在this上调用initialize方法。这个多数情况下不会执行。下一步则是在parent不为Class时执行,将parent的静态属性赋给SubClass,可以通过StaticWhiteList参数特别指定要复制的属性。

接下来是关键一步,也是我认为整个Class类中技巧最高的一步。在SubClass上调用implement方法,该方法中,对properties进行遍历,将properties中的每个属性值和Class.Mutators中的属性值进行对比,Class.Mutators对象中保存的都是一些特殊的方法,这些方法可以以属性的方式写在properties参数中,当遇到特定名称的属性时,就会在SubClass上调用Class.Mutators中的同名方法,并且properties中对应的属性值会作为该方法的参数传入。而不存在于Class.Mutators中的属性,则会执行一般的赋值操作赋给SubClass。这种方法巧妙地将预设的方法和需要混入的属性通过同一种方式传入,降低了API的复杂性,提高了方法的灵活度。同样的技巧我在糖饼的artDialog源码中也看到过,不知道是不是受了Arale的启发。

最后返回“加工”后的SubClass,当然最后执行了一个classify()方法,作用就是在SubClass上加入extendimplement方法,让子类也可以拥有这些方法。

Class.Mutators中的方法具体实现就不说了,看注释即可,反正都是在SubClass上调用的。

至于Class.extend(每个子类都有的)方法,最后其实调用的还是Class.create,只是对properties做了一些处理,方便由子类直接调用再生成子类的一种简化API,免得再写一次类似Class.create(SubClass, properties)这么长的语句。

构造过程中,对原型链的处理是比较重要的一个环节,这是JavaScript的一大特色,注意一下就好。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.