资讯专栏INFORMATION COLUMN

Arale源码解析(1)——Class

_ivan / 1462人阅读

摘要:先来看源码中,首先是做的是参数的处理工作,针对某些参数未传的情况作了调整,最后达到的效果是的值为传入的父类构造函数,如果没有,设为。下一个语句其作用是处理父类构造函数没有修改的属性值并且有方法的时候,在上调用方法。

本文同步自我的GitHub

概述

Arale是支付宝开发的一套基础类库,提供了一整套前端模块架构,基于CMD规范,所有模块均是以sea.js的标准进行开发。其开发过程借鉴了优秀的开源类库如jQuery, underscore等的经验,并融合发展,最后建立了一套自己的开发机制。

结构
Arale
  |--基础设施
  |    |-- Base
  |    |-- Class
  |    |-- Events
  |    `-- Widget
  |--工具
  |    |-- Cookie
  |    |-- Detector
  |    |-- Dnd
  |    |-- Easing
  |    |-- Iframe-Shim
  |    |-- Messenger
  |    |-- Name-Storage
  |    |-- Position
  |    |-- Qrcode
  |    |-- Sticky
  |    |-- Templatable
  |    `-- Upload
  `--UI组件
       |-- Autocomplete
       |-- Calendar
       |-- Dialog
       |-- Overlay
       |-- Popup
       |-- Switchable
       |-- Select
       |-- Tip
       `-- Validator
开篇明义

这是本系列的第一篇,对于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的一大特色,注意一下就好。

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

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

相关文章

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

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

    stdying 评论0 收藏0
  • arale 源码class

    摘要:拥有了和方法的三个变种属性这三个属性会做特殊处理继承的方法,只支持单继承建立原型链来实现继承强制改变构造函数提供语法糖,来调用父类属性混入属性,可以混入多个类的属性将参数变成数组无论参数是类,还是对象,都混入。 更新:读 arale 源码之 attribute 篇 arale 是阿里、开源社区明星人物--玉伯,开发的一套组件,代码相当优美,大赞玉伯的开源精神,我是您的粉丝。 这里分享下...

    firim 评论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
  • js 支持 Aspect 切面编程

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

    zhaot 评论0 收藏0

发表评论

0条评论

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