资讯专栏INFORMATION COLUMN

Arale源码解析(3)——Base模块和Aspect模块

stdying / 471人阅读

摘要:本文同步自我的博客前言这个模块实际上才是模块系统中对外的模块,它包含了之前介绍的类和类,以及自己内部的模块和模块,因此模块是真正的基础类。这两个方法的作用就是针对类上的某个方法,给这个方法绑定先于其执行和后于其执行的回调函数。

本文同步自我的GitHub博客

前言

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模块的支持。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85373.html

相关文章

  • Arale源码解析(1)——Class

    摘要:先来看源码中,首先是做的是参数的处理工作,针对某些参数未传的情况作了调整,最后达到的效果是的值为传入的父类构造函数,如果没有,设为。下一个语句其作用是处理父类构造函数没有修改的属性值并且有方法的时候,在上调用方法。 本文同步自我的GitHub 概述 Arale是支付宝开发的一套基础类库,提供了一整套前端模块架构,基于CMD规范,所有模块均是以sea.js的标准进行开发。其开发过程借...

    _ivan 评论0 收藏0
  • js 支持 Aspect 切面编程

    摘要:在方法执行后,再执行函数函数在执行时,接收的参数第一个是的返回值,之后的参数和传给相同。的返回值源码定义两个出口定义一个可柯里化的函数,柯里化成函数指向基于生成的类的实例,如上例的如果该函数是第一次切面化绑定,则包装该函数。 系列文章:读 arale 源码之 class 篇 使用 Aspect,可以允许你在指定方法执行的前后插入特定函数 before object.before(me...

    zhaot 评论0 收藏0
  • Arale源码解析(2)——Events

    摘要:带注释源码用于分割事件名的正则,识别空格介绍使用方法,这个模块可以混入任何对象之中,实现对自定义事件的资瓷将空格分割的事件绑定给对象,事件名为的话,事件回调函数在任何事件被触发时都会调用。 带注释源码 // Regular expression used to split event strings // 用于分割事件名的正则,识别空格 var eventSplitter = /s+...

    adie 评论0 收藏0
  • arale 源码之 attribute 篇

    摘要:系列文章读源码之篇提供基本的属性添加获取移除等功能。判断是否为等对象特性检查闭包实现块作用域,不污染全局变量。找这个属性,若没有则返回空对象执行函数,返回被修改的值。 系列文章:读 arale 源码之 class 篇 attributes 提供基本的属性添加、获取、移除等功能。它是与实例相关的状态信息,可读可写,发生变化时,会自动触发相关事件 先来了解一下 Attribute 模块要实...

    Magicer 评论0 收藏0
  • minipack源码解析以及扩展

    摘要:的变化利用进行前后端通知。例如的副作用,资源只有资源等等,仔细剖析还有很多有趣的点扩展阅读创建热更新流程本文示例代码联系我 前置知识 首先可能你需要知道打包工具是什么存在 基本的模块化演变进程 对模块化bundle有一定了解 了解babel的一些常识 对node有一定常识 常见的一些打包工具 如今最常见的模块化构建工具 应该是webpack,rollup,fis,parcel等等各...

    tangr206 评论0 收藏0

发表评论

0条评论

stdying

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<