Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zepto源码分析-zepto模块 #10

Open
mominger opened this issue Mar 31, 2015 · 0 comments
Open

Zepto源码分析-zepto模块 #10

mominger opened this issue Mar 31, 2015 · 0 comments

Comments

@mominger
Copy link
Owner

源码注释

//     Zepto.js
//     (c) 2010-2015 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

var Zepto = (function() {
    //定义局部变量    concat = emptyArray.concat 缩短作用域链
  var undefined, key, $, classList, emptyArray = [], concat = emptyArray.concat, filter = emptyArray.filter, slice = emptyArray.slice,



    document = window.document,

      //缓存元素的默认display属性
    elementDisplay = {},

      //缓存匹配class正则表达式hasClass判断用到classCache = {},

   //设置CSS时不用加px单位的属性
    cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
   //匹配HTML代码
    fragmentRE = /^\s*<(\w+|!)[^>]*>/,
      //TODO 匹配单个HTML标签
    singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
  //TODO 匹配自闭合标签
    tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
   //匹配根节点
    rootNodeRE = /^(?:body|html)$/i,
     //匹配A-Z
    capitalRE = /([A-Z])/g,

    // special attributes that should be get/set via method calls
  //需要提供get和set的方法名
    methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
   //相邻DOM的操作
    adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
    table = document.createElement('table'),
    tableRow = document.createElement('tr'),

  //这里的用途是当需要给tr,tbody,thead,tfoot,td,th设置innerHTMl的时候需要用其父元素作为容器来装载HTML字符串
    containers = {
      'tr': document.createElement('tbody'),
      'tbody': table, 'thead': table, 'tfoot': table,
      'td': tableRow, 'th': tableRow,
      '*': document.createElement('div')
    },
  //当DOM ready的时候document会有以下三种状态的一种
    readyRE = /complete|loaded|interactive/,
    simpleSelectorRE = /^[\w-]*$/,
      //缓存对象类型用于类型判断 如object
    class2type = {},
    toString = class2type.toString,
    zepto = {},
    camelize, uniq,
    tempParent = document.createElement('div'),
    propMap = {
      'tabindex': 'tabIndex',
      'readonly': 'readOnly',
      'for': 'htmlFor',
      'class': 'className',
      'maxlength': 'maxLength',
      'cellspacing': 'cellSpacing',
      'cellpadding': 'cellPadding',
      'rowspan': 'rowSpan',
      'colspan': 'colSpan',
      'usemap': 'useMap',
      'frameborder': 'frameBorder',
      'contenteditable': 'contentEditable'
    },

    isArray = Array.isArray ||
      function(object){ return object instanceof Array }

    /**
     * 元素是否匹配选择器
     * @param element
     * @param selector
     * @returns {*}
     */
  zepto.matches = function(element, selector) {
     //没参数非元素直接返回
      if (!selector || !element || element.nodeType !== 1) return false

      //如果浏览器支持MatchesSelector  直接调用
    var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
                          element.oMatchesSelector || element.matchesSelector
    if (matchesSelector) return matchesSelector.call(element, selector)

      //浏览器不支持MatchesSelector
    var match, parent = element.parentNode, temp = !parent

      //元素没有父元素存入到临时的div tempParent
    if (temp) (parent = tempParent).appendChild(element)

      //再通过父元素来搜索此表达式找不到-1  找到有索引从0开始
      //注意 ~取反位运算符  作用是将值取负数再减1   -1变成0  0变成-1
    match = ~zepto.qsa(parent, selector).indexOf(element)

    //清理临时父节点
    temp && tempParent.removeChild(element)

      //返回匹配
    return match
  }

    /**
     * 获取对象类型
     * @param obj
     * @returns {*}
     */
  function type(obj) {
    return obj == null ? String(obj) :
      class2type[toString.call(obj)] || "object"
  }

  function isFunction(value) { return type(value) == "function" }
  function isWindow(obj)     { return obj != null && obj == obj.window }
  function isDocument(obj)   { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
  function isObject(obj)     {
      return type(obj) == "object"
  }
    /**
     * 是否是纯粹对象   JSON/new Object
     * @param obj
     * @returns {*|boolean|boolean}
     */
  function isPlainObject(obj) {
        //是对象  非window  非new时需要传参的
    return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
  }

    /**
     * 伪数组/数组判断
     * @param obj
     * @returns {boolean}
     */
  function likeArray(obj) { return typeof obj.length == 'number' }

    /**
     * 清掉数组中的null/undefined
     * @param array
     * @returns {*}
     */
  function compact(array) { return filter.call(array, function(item){ return item != null }) }

    /**
     * 返回一个数组副本
     * 利用空数组$.fn.concat.apply([], array) 合并新的数组返回副本
     * @param array
     * @returns {*|Function|Function|Function|Function|Function|Zepto.fn.concat|Zepto.fn.concat|Zepto.fn.concat|Array|string}
     */
  function flatten(array) {
      return array.length > 0 ? $.fn.concat.apply([], array) : array
  }

    /**
     * '-'字符串转成驼峰格式
     * @param str
     * @returns {*|void}
     */
  camelize = function(str){
      return str.replace(/-+(.)?/g, function(match, chr){
          //匹配到-字符后的字母转换为大写返回
          return chr ? chr.toUpperCase() : ''
      })
  }

    /**
     * 字符串转换成浏览器可识别的 -拼接形式如background-color
     *
     * @param str
     * @returns {string}
     */
  function dasherize(str) {
    return str.replace(/::/g, '/') //::替换成/
           .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') //在大小写字符之间插入_,大写在前比如AAAbb,得到AA_Abb
           .replace(/([a-z\d])([A-Z])/g, '$1_$2')  //在大小写字符之间插入_,小写或数字在前比如bbbAaa,得到bbb_Aaa
           .replace(/_/g, '-')  //将_替换成-
           .toLowerCase()   //转成小写
  }
    //数组去重如果该条数据在数组中的位置与循环的索引值不相同则说明数组中有与其相同的值
  uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }

    /**
     * 将参数变为正则表达式
     * @param name
     * @returns {*}
     */
  function classRE(name) {
      //classCache,缓存正则
        //TODO 缓存可以理解但应该在重复使用第二次时再缓存吧直接缓存return name in classCache ?
      classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
  }

    /**
     *  除了cssNumber指定的不需要加单位的默认加上px
     * @param name
     * @param value
     * @returns {string}
     */
  function maybeAddPx(name, value) {
    return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
  }

    /**
     * 获取元素的默认display属性
     * 是为了兼容什么* @param nodeName
     * @returns {*}
     */
  function defaultDisplay(nodeName) {
    var element, display
    if (!elementDisplay[nodeName]) {  //缓存里没有

      element = document.createElement(nodeName)
      document.body.appendChild(element)
      display = getComputedStyle(element, '').getPropertyValue("display")
      element.parentNode.removeChild(element)

        // display == "none"设置成blaock即隐藏-显示
      display == "none" && (display = "block")

      elementDisplay[nodeName] = display //TODO:缓存元素的默认display属性缓存干嘛?
    }
    return elementDisplay[nodeName]
  }

    /**
     * 获取元素的子节集
     * 原理原生方法children  老的火狐不支持的遍历childNodes
     * @param element
     * @returns {*}
     */
  function children(element) {
    return 'children' in element ?
      slice.call(element.children) :
      $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
  }

    /**
     * 构造器
     * @param dom
     * @param selector
     * @constructor
     */
  function Z(dom, selector) {
    var i, len = dom ? dom.length : 0
    for (i = 0; i < len; i++) this[i] = dom[i]
    this.length = len
    this.selector = selector || ''
  }

  // `$.zepto.fragment` takes a html string and an optional tag name
  // to generate DOM nodes nodes from the given html string.
  // The generated DOM nodes are returned as an array.
  // This function can be overriden in plugins for example to make
  // it compatible with browsers that don't support the DOM fully.
    /**
     *  内部函数 HTML 转换成 DOM
     *  原理是 创建父元素innerHTML转换
     * @param html  html片段
     * @param name  容器标签名
     * @param propertie  附加的属性对象
     * @returns {*}
     */
  zepto.fragment = function(html, name, properties) {
    var dom, nodes, container

    // A special case optimization for a single tag
      //如果是单个元素创建dom
      //TODO
      // RegExp 是javascript中的一个内置对象为正则表达式//      RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串以此类推RegExp.$2RegExp.$3,..RegExp.$99总共可以有99个匹配
    if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

    if (!dom) {
        //修正自闭合标签 <div />转换成<div></div>
      if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
        //给name取元素名
      if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
        //设置容器名如果不是tr,tbody,thead,tfoot,td,th则容器名为div
        //为什么设置容器是严格按照HTML语法虽然tr td th浏览器会会自动添加tbody
      if (!(name in containers)) name = '*'

      container = containers[name]    //创建容器
      container.innerHTML = '' + html       //生成DOM
       //取容器的子节点TODO:子节点集会返回
      dom = $.each(slice.call(container.childNodes), function(){
        container.removeChild(this)   //把创建的子节点逐个删除
      })
    }
    //如果properties是对象遍历它将它设置成DOM的属性
    if (isPlainObject(properties)) {
        //转换成Zepto Obj,方便调用Zepto的方法
      nodes = $(dom)
        //遍历对象设置属性
      $.each(properties, function(key, value) {
          //优先获取属性修正对象通过修正对象读写值
          // methodAttributes包含'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'//TODO: 奇怪的属性
        if (methodAttributes.indexOf(key) > -1) nodes[key](value)
        else nodes.attr(key, value)
      })
    }

    //返回dom数组  [div,div]
    return dom
  }

  // `$.zepto.Z` swaps out the prototype of the given `dom` array
  // of nodes with `$.fn` and thus supplying all the Zepto functions
  // to the array. This method can be overriden in plugins.
    //入口函数zepto.Z = function(dom, selector) {
    return new Z(dom, selector)
  }

  // `$.zepto.isZ` should return `true` if the given object is a Zepto
  // collection. This method can be overriden in plugins.
    //判断给定的参数是否是Zepto集
  zepto.isZ = function(object) {
    return object instanceof zepto.Z
  }

  // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
  // takes a CSS selector and an optional context (and handles various
  // special cases).
  // This method can be overriden in plugins.
  zepto.init = function(selector, context) {
    var dom
    // If nothing given, return an empty Zepto collection
    //未传参undefined进行boolean转换返回空Zepto对象
    if (!selector) return zepto.Z()
    // Optimize for string selectors

    //selector是字符串即css表达式
    else if (typeof selector == 'string') {
       //去前后空格
      selector = selector.trim()
      // If it's a html fragment, create nodes from it
      // Note: In both Chrome 21 and Firefox 15, DOM error 12
      // is thrown if the fragment doesn't begin with <
      //如果是<开头 >结尾  基本的HTML代码时
      if (selector[0] == '<' && fragmentRE.test(selector))
        //调用片段生成dom
        dom = zepto.fragment(selector, RegExp.$1, context), selector = null
      // If there's a context, create a collection on that context first, and select
      // nodes from there
      //如果传递了上下文在上下文中查找元素
      else if (context !== undefined) return $(context).find(selector)
      // If it's a CSS selector, use it to select nodes.
      //通过css表达式查找元素
      else dom = zepto.qsa(document, selector)
    }
    // If a function is given, call it when the DOM is ready
    //如果selector是函数则在DOM ready的时候执行它
    else if (isFunction(selector)) return $(document).ready(selector)
    // If a Zepto collection is given, just return it
    //如果selector是一个Zepto对象返回它自己
    else if (zepto.isZ(selector)) return selector
    else {
      // normalize array if an array of nodes is given
        //如果selector是数组过滤null,undefined
      if (isArray(selector)) dom = compact(selector)
      // Wrap DOM nodes.
      //如果selector是对象,TODO://转换为数组它应是DOM; 注意DOM节点的typeof值也是object所以在里面还要再进行一次判断
      else if (isObject(selector))
        dom = [selector], selector = null
      // If it's a html fragment, create nodes from it
      //如果selector是复杂的HTML代码调用片段换成DOM节点
      else if (fragmentRE.test(selector))
        dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
      // If there's a context, create a collection on that context first, and select
      // nodes from there
      //如果存在上下文context仍在上下文中查找selector
      else if (context !== undefined) return $(context).find(selector)
      // And last but no least, if it's a CSS selector, use it to select nodes.
      //如果没有给定上下文在document中查找selector
      else dom = zepto.qsa(document, selector)
    }
    // create a new Zepto collection from the nodes found
    //将查询结果转换成Zepto对象
    return zepto.Z(dom, selector)
  }

  // `$` will be the base `Zepto` object. When calling this
  // function just call `$.zepto.init, which makes the implementation
  // details of selecting nodes and creating Zepto collections
  // patchable in plugins.
  $ = function(selector, context){
    return zepto.init(selector, context)
  }

    /**
     * 内部方法用户合并一个或多个对象到第一个对象
     * @param target 目标对象  对象都合并到target里
     * @param source 合并对象
     * @param deep 是否执行深度合并
     */
  function extend(target, source, deep) {
    for (key in source)
        //如果深度合并
      if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
          //如果要合并的属性是对象但target对应的key非对象
        if (isPlainObject(source[key]) && !isPlainObject(target[key]))
          target[key] = {}
          //如果要合并的属性是数组但target对应的key非数组
        if (isArray(source[key]) && !isArray(target[key]))
          target[key] = []

         //执行递归合并
        extend(target[key], source[key], deep)
      }
      //不是深度合并直接覆盖
      //TODO: 合并不显得太简单了else if (source[key] !== undefined) target[key] = source[key]
  }

  // Copy all but undefined properties from one or more
  // objects to the `target` object.
    /**
     * 对外方法
     * 合并
     * @param target
     * @returns {*}
     */
  $.extend = function(target){
    var deep,    //是否执行深度合并
        args = slice.call(arguments, 1)//arguments[0]是target被合并对象或为deep
    if (typeof target == 'boolean') {
        //第一个参数为boolean值时表示是否深度合并
      deep = target
      target = args.shift()   //target取第二个参数
    }
    //遍历后面的参数都合并到target上
    args.forEach(function(arg){ extend(target, arg, deep) })
    return target
  }

  // `$.zepto.qsa` is Zepto's CSS selector implementation which
  // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
  // This method can be overriden in plugins.
    /**
     *  通过选择器表达式查找DOM
     *  原理  判断下选择器的类型id/class/标签/表达式*  使用对应方法getElementById getElementsByClassName getElementsByTagName querySelectorAll 查找
     * @param element
     * @param selector
     * @returns {Array}
     */
  zepto.qsa = function(element, selector){
    var found,
        maybeID = selector[0] == '#',//ID标识
        maybeClass = !maybeID && selector[0] == '.',//class 标识
        //是id/class,就取'#/.'后的字符串#test’取‘test'
        nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
        isSimple = simpleSelectorRE.test(nameOnly)  //TODO:是否为单个选择器  没有空格
    return (element.getElementById && isSimple && maybeID) ? // Safari DocumentFragment doesn't have getElementById
        //通过getElementById查找DOM找到返回[dom],找不到返回[]
      ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
        //当element不为元素节点或document fragment时返回空
        //元素element   1   属性attr   2   文本text   3   注释comments   8   文档document   9   片段 fragment 11
      (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
      slice.call(
          //如果是class通过getElementsByClassName查找DOMisSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName
          maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
          element.getElementsByTagName(selector) : // Or a tag //如果是标签名,调用getElementsByTagName
            //最后调用querySelectorAll
          element.querySelectorAll(selector) // Or it's not simple, and we need to query all
      )
  }

    /**
     * 在元素集中过滤某些元素
     * @param nodes
     * @param selector
     * @returns {*|HTMLElement}
     */
  function filtered(nodes, selector) {
    return selector == null ? $(nodes) : $(nodes).filter(selector)
  }

    /**
     * 父元素是否包含子元素
     * @type {Function}
     */
  $.contains = document.documentElement.contains ?
    function(parent, node) {
        //父元素
      return parent !== node && parent.contains(node)
    } :
    function(parent, node) {
      while (node && (node = node.parentNode))
        if (node === parent) return true
      return false
    }

    /**
     * 处理 arg为函数/
     * 为函数返回函数返回值
     * 为值返回值
     * @param context
     * @param arg
     * @param idx
     * @param payload
     * @returns {*}
     */
  function funcArg(context, arg, idx, payload) {
    return isFunction(arg) ? arg.call(context, idx, payload) : arg
  }

    /**
     * 设置属性
     * @param node
     * @param name
     * @param value
     */
  function setAttribute(node, name, value) {
      //value为null/undefined,处理成删除否则设值
    value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
  }

  // access className property while respecting SVGAnimatedString
    /**
     * 对SVGAnimatedString的兼容* @param node
     * @param value
     * @returns {*}
     */
  function className(node, value){
    var klass = node.className || '',
        svg   = klass && klass.baseVal !== undefined

    if (value === undefined) return svg ? klass.baseVal : klass
    svg ? (klass.baseVal = value) : (node.className = value) //class设值
  }

  // "true"  => true
  // "false" => false
  // "null"  => null
  // "42"    => 42
  // "42.5"  => 42.5
  // "08"    => "08"
  // JSON    => parse if valid
  // String  => self
    /**
     * 序列化值  把自定义数据读出来时做应该的转换,$.data()方法使用
     * @param value
     * @returns {*}
     */
  function deserializeValue(value) {
    try {
      return value ?
        value == "true" ||
        ( value == "false" ? false :
          value == "null" ? null :
          +value + "" == value ? +value :
          /^[\[\{]/.test(value) ? $.parseJSON(value) :
          value )
        : value
    } catch(e) {
      return value
    }
  }

  $.type = type
  $.isFunction = isFunction
  $.isWindow = isWindow
  $.isArray = isArray
  $.isPlainObject = isPlainObject

    /**
     * 空对象
     * @param obj
     * @returns {boolean}
     */
  $.isEmptyObject = function(obj) {
    var name
    for (name in obj) return false
    return true
  }

    /**
     * 获取在数组中的索引
     * @param elem
     * @param array
     * @param i
     * @returns {number}
     */
  $.inArray = function(elem, array, i){
      //i从第几个开始搜索
    return emptyArray.indexOf.call(array, elem, i)
  }

    //将字符串转成驼峰格式
  $.camelCase = camelize
    //去字符串头尾空格
  $.trim = function(str) {
    return str == null ? "" : String.prototype.trim.call(str)
  }

  // plugin compatibility
  $.uuid = 0
  $.support = { }
  $.expr = { }
  $.noop = function() {}


    /**
     * 内部方法
     * 遍历对象/数组 在每个元素上执行回调将回调的返回值放入一个新的数组返回
     * @param elements
     * @param callback
     * @returns {*}
     */
  $.map = function(elements, callback){
    var value, values = [], i, key
      //如果被遍历的数据是数组或者Zepto(伪数组if (likeArray(elements))
      for (i = 0; i < elements.length; i++) {
        value = callback(elements[i], i)
        if (value != null) values.push(value)
      }
    else
    //如果是对象
      for (key in elements) {
        value = callback(elements[key], key)
        if (value != null) values.push(value)
      }
    return flatten(values)
  }

    /**
     * 以集合每一个元素作为上下文来执行回调函数
     * @param elements
     * @param callback
     * @returns {*}
     */
  $.each = function(elements, callback){
    var i, key
    if (likeArray(elements)) {        //数组伪数组
      for (i = 0; i < elements.length; i++)
        if (callback.call(elements[i], i, elements[i]) === false) return elements
    } else {
      for (key in elements)       //对象
        if (callback.call(elements[key], key, elements[key]) === false) return elements
    }

    return elements
  }

    /**
     * 查找数组满足过滤函数的元素
     * @param elements
     * @param callback
     * @returns {*}
     */
  $.grep = function(elements, callback){
    return filter.call(elements, callback)
  }

  if (window.JSON) $.parseJSON = JSON.parse

    //填充class2type的值
  $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase()
  })

  // Define methods that will be available on all
  // Zepto collections
    //针对DOM的一些操作
  $.fn = {
    constructor: zepto.Z,
    length: 0,

    // Because a collection acts like an array
    // copy over these useful array functions.
    forEach: emptyArray.forEach,
    reduce: emptyArray.reduce,
    push: emptyArray.push,
    sort: emptyArray.sort,
    splice: emptyArray.splice,
    indexOf: emptyArray.indexOf,
      /**
       * 合并多个数组
       * @returns {*}
       */
    concat: function(){
      var i, value, args = []
      for (i = 0; i < arguments.length; i++) {
        value = arguments[i]
        args[i] = zepto.isZ(value) ? value.toArray() : value
      }
      return concat.apply(zepto.isZ(this) ? this.toArray() : this, args)
    },

    // `map` and `slice` in the jQuery API work differently
    // from their array counterparts
      /**
       * 遍历对象/数组 在每个元素上执行回调将回调的返回值放入一个新的Zepto返回
       * @param fn
       * @returns {*|HTMLElement}
       */
    map: function(fn){
      return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
    },
      /**
       * slice包装成Zepto
       * @returns {*|HTMLElement}
       */
    slice: function(){
      return $(slice.apply(this, arguments))
    },

      /**
       * 当DOM载入就绪时绑定回调
       *   $(function(){}) $(document).ready(function(){
  // 在这里写你的代码
       * @param callback
       * @returns {*}
       */
    ready: function(callback){
      // need to check if document.body exists for IE as that browser reports
      // document ready when it hasn't yet created the body element

        //如果已经ready
      if (readyRE.test(document.readyState) && document.body) callback($)

      //监听DOM已渲染完毕事件
      else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
      return this
    },
      /**
       * 取Zepto中指定索引的值
       * @param idx    可选不传时将Zetpo转换成数组
       * @returns {*}
       */
    get: function(idx){
      return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
    },
      /**
       * 将Zepto(伪数组)转换成数组
       * 原理是 伪数组转换成数组oa = {0:'a',length:1};Array.prototype.slice.call(oa);
       * 数组转换伪数组  var obj = {}, push = Array.prototype.push; push.apply(obj,[1,2]);
       * @returns {*}
       */
    toArray: function(){
        return this.get()
    },
      //获取集合长度
    size: function(){
      return this.length
    },
      /**
       * 删除元素集
       * 原理   parentNode.removeChild
       * @returns {*}
       */
    remove: function(){
          //遍历到其父元素   removeChild
      return this.each(function(){
        if (this.parentNode != null)
          this.parentNode.removeChild(this)
      })
    },
      //遍历集合将集合中的每一项放入callback中进行处理去掉结果为false的项注意这里的callback如果明确返回false
      //那么就会停止循环了
      /**
       *  遍历Zepto在每个元素上执行回调函数
       * @param callback
       * @returns {*}
       */
    each: function(callback){

      emptyArray.every.call(this, function(el, idx){
          //el元素idx下标 传递给callback(idx,el)
        return callback.call(el, idx, el) !== false
      })
      return this
    },

      /**
       *  过滤返回处理结果为true的记录
       * @param selector
       * @returns {*}
       */
    filter: function(selector){
        //this.not(selector)取到需要排除的集合第二次再取反(这个时候this.not的参数就是一个集合了),得到想要的集合
      if (isFunction(selector)) return this.not(this.not(selector))

        //filter收集返回结果为true的记录
      return $(filter.call(this, function(element){
          //当element与selector匹配则收集
        return zepto.matches(element, selector)
      }))
    },
      //将由selector获取到的结果追加到当前集合中
    add: function(selector,context){
        //追加并去重
      return $(uniq(this.concat($(selector,context))))
    },
      //返回集合中的第1条记录是否与selector匹配
    is: function(selector){
      return this.length > 0 && zepto.matches(this[0], selector)
    },
      //排除集合里满足条件的记录接收参数为css选择器function, dom ,nodeList
    not: function(selector){
      var nodes=[]
        //当selector为函数时safari下的typeof odeList也是function所以这里需要再加一个判断selector.call !== undefined
      if (isFunction(selector) && selector.call !== undefined)
        this.each(function(idx){
            //注意这里收集的是selector.call(this,idx)返回结果为false的时候记录
          if (!selector.call(this,idx)) nodes.push(this)
        })
      else {
          //当selector为字符串的时候对集合进行筛选也就是筛选出集合中满足selector的记录
        var excludes = typeof selector == 'string' ? this.filter(selector) :
            //当selector为nodeList时执行slice.call(selector),注意这里的isFunction(selector.item)是为了排除selector为数组的情况
            //当selector为css选择器执行$(selector)
          (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
        this.forEach(function(el){
            //筛选出不在excludes集合里的记录达到排除的目的
          if (excludes.indexOf(el) < 0) nodes.push(el)
        })
      }
      return $(nodes)//由于上面得到的结果是数组这里需要转成zepto对象以便继承其它方法实现链写
    },

      /*
       接收node和string作为参数给当前集合筛选出包含selector的集合
       isObject(selector)是判断参数是否是node因为typeof node == 'object'
       当参数为node时只需要判读当前记当里是否包含node节点即可
       当参数为string时则在当前记录里查询selector如果长度为0则为falsefilter函数就会过滤掉这条记录否则保存该记录
       */
    has: function(selector){
      return this.filter(function(){
        return isObject(selector) ?
          $.contains(this, selector) :
          $(this).find(selector).size()
      })
    },
      /**
       * 取Zepto中的指定索引的元素再包装成Zepto返回
       * @param idx
       * @returns {*}
       */
    eq: function(idx){
      return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
    },
      /*
       取第一条$(元素*/
    first: function(){
      var el = this[0]    //取第一个元素

       //$对象转换成$,
        //如果elementisObject会判断为truezepto也判断为true都会重新转换成$(el)
        //TODO:这里是bugreturn el && !isObject(el) ? el : $(el)
    },
      /*
       取最后一条$(元素*/
    last: function(){
      var el = this[this.length - 1]
      return el && !isObject(el) ? el : $(el)
    },
      /*
       在当前集合中查找selectorselector可以是集合选择器以及节点
       */
    find: function(selector){
      var result, $this = this
        //如果selector为node或者zepto集合时
      if (!selector) result = $()
      //遍历selector筛选出父级为集合中记录的selector
      else if (typeof selector == 'object')
        result = $(selector).filter(function(){
          var node = this
            //如果$.contains(parent, node)返回true则emptyArray.some也会返回true,外层的filter则会收录该条记录
          return emptyArray.some.call($this, function(parent){
            return $.contains(parent, node)
          })
        })
      //如果selector是css选择器
      //如果当前集合长度为1时调用zepto.qsa将结果转成zepto对象
      else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
      //如果长度大于1则调用map遍历
      else result = this.map(function(){ return zepto.qsa(this, selector) })
      return result
    },


      /**
       * 取最近的满足selector选择器的祖先元素
       * @param selector
       * @param context
       * @returns {*|HTMLElement}
       */
    closest: function(selector, context){
      var node = this[0], collection = false
      if (typeof selector == 'object') collection = $(selector)

          //node递归parentNode直到满足selector表达式返回$
      while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
          //当node 不是context,document的时候取node.parentNode
        node = node !== context && !isDocument(node) && node.parentNode
      return $(node)
    },
      /**
       * 取得所有匹配的祖先元素
       * @param selector
       * @returns {*}
       */
    parents: function(selector){
      var ancestors = [], nodes = this

      //先取得所有祖先元素
      while (nodes.length > 0)   //到不再有父元素时退出循环
        //取得所有父元素 //nodes被再赋值为收集到的父元素数组
        nodes = $.map(nodes, function(node){
            //获取父级isDocument(node) 到Document为止
            //    ancestors.indexOf(node)去重复
          if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
            ancestors.push(node)
              //收集已经获取到的父级元素用于去重复
            return node
          }
        })

      //筛选出符合selector的祖先元素
      return filtered(ancestors, selector)
    },
      /**
       * 获取父元素
       * @param selector
       * @returns {*|HTMLElement}
       */
    parent: function(selector){
      return filtered(uniq(this.pluck('parentNode')), selector)
    },
      /**
       *   获取子元素集
       * @param selector
       * @returns {*|HTMLElement}
       */
    children: function(selector){
      return filtered(this.map(function(){ return children(this) }), selector)
    },
      /**
       * 获取iframe的docment或子节集
       * @returns {*|HTMLElement}
       */
    contents: function() {
      return this.map(function() { return this.contentDocument || slice.call(this.childNodes) })
    },
      /**
       * 获取兄弟节点集
       * @param selector
       * @returns {*|HTMLElement}
       */
    siblings: function(selector){
      return filtered(this.map(function(i, el){
         //到其父元素取得所有子节点再排除本身
        return filter.call(children(el.parentNode), function(child){ return child!==el })
      }), selector)
    },
      /**
       * 移除所有子元素
       * 原理innerHTML = ''
       * @returns {*}
       */
    empty: function(){
      return this.each(function(){ this.innerHTML = '' })
    },

      /**
       * 根据是否存在此属性来获取当前集合
       * @param property
       * @returns {*}
       */
    pluck: function(property){
      return $.map(this, function(el){ return el[property] })
    },
      /**
       * 展示
       * @returns {*}
       */
    show: function(){
      return this.each(function(){
          //清除内联样式display="none"
        this.style.display == "none" && (this.style.display = '')
          //计算样式display为none时重赋显示值
        if (getComputedStyle(this, '').getPropertyValue("display") == "none")
          this.style.display = defaultDisplay(this.nodeName)
          //defaultDisplay是获取元素默认display的方法
      })
    },
      /**
       * 替换元素
       * 原理  before
       * @param newContent
       * @returns {*}
       */
    replaceWith: function(newContent){
        //将要替换内容插到被替换内容前面然后删除被替换内容
      return this.before(newContent).remove()
    },
      /**
       * 匹配的每条元素都被单个元素包裹
       * @param structure   fun/
       * @returns {*}
       */
    wrap: function(structure){
      var func = isFunction(structure)
      if (this[0] && !func) //如果structure是字符串
        //直接转成DOM
        var dom   = $(structure).get(0),
            //如果DOM已存在(通过在文档中读parentNode判断),$集不止一条需要克隆避免DOM被移动位置
            clone = dom.parentNode || this.length > 1

      return this.each(function(index){
          //递归包裹克隆的DOM
        $(this).wrapAll(
          func ? structure.call(this, index) :
            clone ? dom.cloneNode(true) : dom  //克隆包裹
        )
      })
    },
      /**
       * 将所有匹配的元素用单个元素包裹起来
       * @param structure   包裹内容
       * @returns {*}
       */
    wrapAll: function(structure){
      if (this[0]) {
          //包裹内容插入到第一个元素前
        $(this[0]).before(structure = $(structure))
        var children
        // drill down to the inmost element
          // drill down to the inmost element
          //取包裹内容里的第一个子元素的最里层
          while ((children = structure.children()).length) structure = children.first()

          //将当前$插入到最里层元素里
        $(structure).append(this)
      }
      return this
    },
      /**
       * 包裹到里面  将每一个匹配元素的子内容(包括文本节点)用HTML包裹起来
       * 原理  获取节点的内容
       * @param structure
       * @returns {*}
       */
    wrapInner: function(structure){
      var func = isFunction(structure)
      return this.each(function(index){
          //遍历获取节点的内容然后用structure将内容包裹
        var self = $(this), contents = self.contents(),
            dom  = func ? structure.call(this, index) : structure
        contents.length ? contents.wrapAll(dom) : self.append(dom) //内容不存在直接添加structure
      })
    },
      /**
       * 去包裹  移除元素的父元素
       * 原理子元素替换父元素
       * @returns {*}
       */
    unwrap: function(){
      this.parent().each(function(){
        $(this).replaceWith($(this).children())
      })
      return this
    },
      /**
       * 复制元素的副本 TODO:事件自定义数据会复制吗* 原理  cloneNode
       * @returns {*|HTMLElement}
       */
    clone: function(){
      return this.map(function(){ return this.cloneNode(true) })
    },
      /**
       * 隐藏
       * @returns {*}
       */
    hide: function(){
      return this.css("display", "none")
    },
      /**
       * 不给参数切换显示隐藏
       * 给参数  true show  false hide
       * @param setting
       * @returns {*}
       */
    toggle: function(setting){
      return this.each(function(){

        var el = $(this)
        ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
      })
    },
      /**
       * 筛选前面所有的兄弟元素
       * @param selector
       * @returns {*}
       */
    prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') },


      /**
       * 筛选后面所有的兄弟元素
       * @param selector
       * @returns {*}
       */
    next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') },


      /**
       * 读写元素HTML内容
       * 原理 通过innerHTML读内容,append()写内容
       * @param html
       * @returns {*|string|string|string|string|string}
       */
    html: function(html){
      return 0 in arguments ?
        this.each(function(idx){
          var originHtml = this.innerHTML   //记录原始的innerHTMl
            //如果参数html是字符串直接插入到记录中//如果是函数则将当前记录作为上下文调用该函数且传入该记录的索引和原始innerHTML作为参数
          $(this).empty().append( funcArg(this, html, idx, originHtml) )
        }) :
        (0 in this ? this[0].innerHTML : null)
    },
      /**
       * 读写元素文本内容
       * 原理通过 textContent 读写文本
       * @param text
       * @returns {*}
       */
    text: function(text){
      return 0 in arguments ?
        this.each(function(idx){                               //传参遍历写入
          var newText = funcArg(this, text, idx, this.textContent)
          this.textContent = newText == null ? '' : ''+newText
        }) :
        (0 in this ? this[0].textContent : null)   //未传参读
    },

      /**
       * 元素的HTML属性读写
       * 原理是getAttribute
       * 原理是setAttribute
       * @param name
       * @param value
       * @returns {undefined}
       */
    attr: function(name, value){
      var result
       //仅有name且为字符串时表示读
      return (typeof name == 'string' && !(1 in arguments)) ?
          //$是空的 或里面的元素非元素返回undefined
        (!this.length || this[0].nodeType !== 1 ? undefined :
            //直接用getAttribute(name),
          (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
        ) :  //否则是写不管name为对象{k:v},或name value 都存在
        this.each(function(idx){
          if (this.nodeType !== 1) return   //非元素
            //如果name为对象批量设置属性
          if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
          //处理value为函数/null/undefined的情况
          else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
        })
    },
      /**
       * 元素的删除
       * @param name 单个值 空格分隔
       * @returns {*}
       */
    removeAttr: function(name){
      return this.each(
          function(){
              this.nodeType === 1 && name.split(' ').forEach(
                  function(attribute){
                      //不传value会直接调用removeAttribute删除属性
                    setAttribute(this, attribute)
                 }, this)
        })
    },
      //获取第一条数据的指定的name属性或者给每条数据添加自定义属性注意和setAttribute的区别

      /**
       * 元素的DOM属性读写
       * 原理Element[name] 操作
       * @param name
       * @param value
       * @returns {*}
       */
    prop: function(name, value){
        //优先读取修正属性DOM的两字母属性都是驼峰格式
      name = propMap[name] || name
      //没有给定value时为获取给定value则给每一条数据添加value可以为值也可以是一个返回值的函数
      return (1 in arguments) ?
          //有value遍历写入
        this.each(function(idx){
          this[name] = funcArg(this, value, idx, this[name])
        }) :
          //读第一个元素
        (this[0] && this[0][name])
    },

      /**
       * 设置自定义数据
       * 注意与jQuery的区别jQuery可以读写任何数据类型这里原理是H5的data-或直接setAttribute/getAttribute只能读写字符串
       * @param name
       * @param value
       * @returns {*}
       */
    data: function(name, value){
      var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase()

      var data = (1 in arguments) ?
        this.attr(attrName, value) :
        this.attr(attrName)

      return data !== null ? deserializeValue(data) : undefined
    },
      /**
       * 适合表单元素读写
       * 写入每个元素   element.value
       * 读第一个元素
       * @param value  /函数
       * @returns {*}
       */
    val: function(value){
      return 0 in arguments ?
          //只有一个参数是写this.each(function(idx){
          this.value = funcArg(this, value, idx, this.value)
        }) :
          //如果是读
        (this[0] && (this[0].multiple ?    //对多选的select的兼容处理返回一个包含被选中的option的值的数组
           $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') :
           this[0].value)
        )
    },
      /**
       * /写坐标  距离文档document的偏移值
       * 原理 getBoundingClientRect视窗坐标-页面偏移   坐标-父元素坐标
       * @param coordinates
       * @returns {*}
       */
    offset: function(coordinates){
          //写入坐标
      if (coordinates) return this.each(function(index){
        var $this = $(this),
            //如果coordinates是函数执行函数coords = funcArg(this, coordinates, index, $this.offset()),
        //取父元素坐标
            parentOffset = $this.offsetParent().offset(),
         //计算出合理的坐标
            props = {
              top:  coords.top  - parentOffset.top,
              left: coords.left - parentOffset.left
            }
          //修正postin  static-relative
        if ($this.css('position') == 'static') props['position'] = 'relative'

        //写入样式
        $this.css(props)
      })
      //读取坐标 取第一个元素的坐标
      if (!this.length) return null
          //如果父元素是document
      if (!$.contains(document.documentElement, this[0]))
        return {top: 0, left: 0}

          //读取到元素相对于页面视窗的位置
      var obj = this[0].getBoundingClientRect()

        //window.pageYOffset就是类似Math.max(document.documentElement.scrollTop||document.body.scrollTop)
      return {
        left: obj.left + window.pageXOffset,    //文档水平滚动偏移
        top: obj.top + window.pageYOffset,      //文档垂直滚动偏移 pageYOffset和scrollTop的区别是width: Math.round(obj.width),
        height: Math.round(obj.height)
      }
    },
      /**
       * 读写样式   内联样式  计算样式
       *   原理 elment[style]/getComputedStyle this.style.cssText 行内样式设值
       * @param property   String/Array/Fun
       * @param value
       * @returns {*}
       */
    css: function(property, value){
        //只有一个传参
      if (arguments.length < 2) {
        var computedStyle, element = this[0]
        if(!element) return
         //getComputedStyle是一个可以获取当前元素所有最终使用的CSS属性值返回的是一个CSS样式声明对象([object CSSStyleDeclaration]),只读
          //读到计算样式
        computedStyle = getComputedStyle(element, '')
          //设置样式
        if (typeof property == 'string')// 字符串
            //优先读行内样式再读计算样式行内样式级别最高TODO:似乎有bug如果设置了!important 
          return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
        else if (isArray(property)) {     //数组
          var props = {}
          $.each(property, function(_, prop){     //遍历读取每一条样式存入JSON返回
            props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
          })
          return props
        }
      }

          //如果是写
      var css = ''
      if (type(property) == 'string') {
        if (!value && value !== 0)  //nullundefined时删掉样式
          this.each(function(){
              //删除        dasherize是将字符串转换成css属性(background-color格式this.style.removeProperty(dasherize(property))
          })
        else
           //-格式值 + px单位
          css = dasherize(property) + ":" + maybeAddPx(property, value)
      } else {
        for (key in property)     //是对象时
          if (!property[key] && property[key] !== 0)
          //当property[key]的值为null/undefined删除属性
            this.each(function(){ this.style.removeProperty(dasherize(key)) })
          else
            //-格式值 + px单位
            css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
      }

        //设值   //TODO:   this.style.cssText +=  未考虑去重了
      return this.each(function(){ this.style.cssText += ';' + css })
    },
    index: function(element){
        //这里的$(element)[0]是为了将字符串转成node,因为this是个包含node的数组
        //当不指定element时取集合中第一条记录在其父节点的位置
        //this.parent().children().indexOf(this[0])这句很巧妙和取第一记录的parent().children().indexOf(this)相同
      return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
    },
      /**
       * 是否含有指定的类样式
       * @param name
       * @returns {boolean}
       */
    hasClass: function(name){
      if (!name) return false
          //some ES5的新方法 有一个匹配即返回truereturn emptyArray.some.call(this, function(el){
          //this是classRE(name)生成的正则
        return this.test(className(el))
      }, classRE(name))
    },
      /**
       * 增加一个或多个类名
       * @param name  类名/空格分隔的类名/函数
       * @returns {*}
       */
    addClass: function(name){
      if (!name) return this

      //遍历增加
      return this.each(function(idx){
        //已存在返回
        if (!('className' in this)) return
        classList = []
        var cls = className(this), newName = funcArg(this, name, idx, cls)  //修正类名处理name是函数SVG动画兼容的情况

          //多个类空格分隔为数组
        newName.split(/\s+/g).forEach(function(klass){
          if (!$(this).hasClass(klass)) classList.push(klass)
        }, this)

          //设值
        classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
      })
    },
      /**
       *删除一个或多个类名 同addClass
       * 原理className.repalce 替换撒谎年初
       * @param name 类名/空格分隔的类名/函数
       * @returns {*}
       */
    removeClass: function(name){
      return this.each(function(idx){
        if (!('className' in this)) return
        if (name === undefined) return className(this, '')
        classList = className(this)
        funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
          //替换删除
          classList = classList.replace(classRE(klass), " ")
        })
        className(this, classList.trim())
      })
    },

      /**
       *切换类的添加或移除
       * 原理 如果存在即removeClass移除不存在即addClass添加
       * @param name   类名/空格分隔的类名/函数
       * @param when
       * @returns {*}
       */
    toggleClass: function(name, when){
      if (!name) return this
      return this.each(function(idx){
        var $this = $(this), names = funcArg(this, name, idx, className(this))
        names.split(/\s+/g).forEach(function(klass){
          (when === undefined ? !$this.hasClass(klass) : when) ?
            $this.addClass(klass) : $this.removeClass(klass)
        })
      })
    },
      /**
       * 读写元素 滚动条的垂直偏移
       * 第一个元素  scrollTop  pageYOffset
       * 所有元素     scrollTop
       * 如果设置的偏移值滚动做不到可能不生效不会取滚动最大值
       * @param value
       * @returns {*}
       */
    scrollTop: function(value){
      if (!this.length) return
      var hasScrollTop = 'scrollTop' in this[0]
          //
      if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset//取scrollTop  pageYOffset(Sarifri老版只有它//
      return this.each(hasScrollTop ?
        function(){ this.scrollTop = value } :  //支持scrollTop直接赋值
        function(){ this.scrollTo(this.scrollX, value) }) //滚到指定坐标
    },
      /**
       * 读写元素 滚动条的垂直偏移
       * 第一个元素  scrollLeft  pageXOffset
       * 所有元素     scrollLeft
       * @param value
       * @returns {*}
       */
    scrollLeft: function(value){
      if (!this.length) return
      var hasScrollLeft = 'scrollLeft' in this[0]
      if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
      return this.each(hasScrollLeft ?
        function(){ this.scrollLeft = value } :
        function(){ this.scrollTo(value, this.scrollY) })
    },

      /**
       * 获取相对父元素的坐标  当前元素的外边框magin到最近父元素内边框的距离
       * @returns {{top: number, left: number}}
       */
    position: function() {
      if (!this.length) return

      var elem = this[0],
        // Get *real* offsetParent
          //读到父元素
        offsetParent = this.offsetParent(),
        // Get correct offsets
          //读到坐标
        offset       = this.offset(),
          //读到父元素的坐标
        parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()

      // Subtract element margins
      // note: when an element has margin: auto the offsetLeft and marginLeft
      // are the same in Safari causing offset.left to incorrectly be 0
       //坐标减去外边框
      offset.top  -= parseFloat( $(elem).css('margin-top') ) || 0
      offset.left -= parseFloat( $(elem).css('margin-left') ) || 0

      // Add offsetParent borders
       //加上父元素的border
      parentOffset.top  += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0
      parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0

      // Subtract the two offsets
      return {
        top:  offset.top  - parentOffset.top,
        left: offset.left - parentOffset.left
      }
    },
      /**
       * 返回第一个匹配元素用于定位的祖先元素
       * 原理读取父元素中第一个其position设为relative或absolute的可见元素
       * @returns {*|HTMLElement}
       */
    offsetParent: function() {
        //map遍历$在回调函数里读出最近的定位祖先元素再返回包含这些定位元素的$对象
      return this.map(function(){
          //读取定位父元素没有则body
        var parent = this.offsetParent || document.body

          //如果找到的定位元素  position=static继续往上找直到body/Html
        while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
          parent = parent.offsetParent
        return parent
      })
    }
  }

  // for now
  $.fn.detach = $.fn.remove

  // Generate the `width` and `height` functions
  /*
   * width height 模板方法  读写width/height
   */
  ;['width', 'height'].forEach(function(dimension){
        //将width,hegiht转成Width,Height用于document获取
    var dimensionProperty =
      dimension.replace(/./, function(m){ return m[0].toUpperCase() })

    $.fn[dimension] = function(value){
      var offset, el = this[0]
        //读时是window 用innerWidth,innerHeight获取
      if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] :
          //是document用scrollWidth,scrollHeight获取
        isDocument(el) ? el.documentElement['scroll' + dimensionProperty] :
        (offset = this.offset()) && offset[dimension]   //TODO:否则用 offsetWidth offsetHeight

      //
      else return this.each(function(idx){
        el = $(this)
          //设值支持value为函数
        el.css(dimension, funcArg(this, value, idx, el[dimension]()))
      })
    }
  })

  function traverseNode(node, fun) {
    fun(node)
    for (var i = 0, len = node.childNodes.length; i < len; i++)
      traverseNode(node.childNodes[i], fun)
  }

  // Generate the `after`, `prepend`, `before`, `append`,
  // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
    /**
     * TODO: 模板方法DOM的插入操作
     */
  adjacencyOperators.forEach(function(operator, operatorIndex) {
    var inside = operatorIndex % 2 //=> prepend, append  有余数 注意forEach遍历出的索引从0开始

    $.fn[operator] = function(){
      // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
        //nodes  HTML字符串生成的DOM集
      var argType, nodes = $.map(arguments, function(arg) {
            argType = type(arg)
              //传参非 objectarraynull就直接调用zepto.fragment生成DOM
            return argType == "object" || argType == "array" || arg == null ?
              arg : zepto.fragment(arg)
          }),
        //如果$长度>1,需要克隆里面的元素
          parent, copyByClone = this.length > 1

      if (nodes.length < 1) return this     //为0不需要操作直接返回

      //遍历源$,执行插入   _指代此参数无效或不用
      return this.each(function(_, target){
        parent = inside ? target : target.parentNode //prepend, append取父元素

        // convert all methods to a "before" operation

          //用insertBefore模拟实现
        target = operatorIndex == 0 ? target.nextSibling :   //aftertarget等于下一个兄弟元素然后将DOM通过insertBefore插入到target前
                 operatorIndex == 1 ? target.firstChild :   //prepend target为parent的第一个元素然后将DOM通过insertBefore插入到target前
                 operatorIndex == 2 ? target :       // before  直接将将DOM通过insertBefore插入到target前
                 null                                //  append  直接调用$(target).append

          //父元素是否在document中
        var parentInDocument = $.contains(document.documentElement, parent)

          //遍历待插入的元素
        nodes.forEach(function(node){
            //克隆
          if (copyByClone) node = node.cloneNode(true)

          //定位元素不存在,,没法执行插入操作直接删除返回
          else if (!parent) return $(node).remove()

            //插入节点后如果被插入的节点是SCRIPT则执行里面的内容并将window设为上下文
          //插入元素
          parent.insertBefore(node, target)

          //如果父元素在document里修正script标签原因是script标签通过innerHTML加入DOM不执行需要在全局环境下执行它
          if (parentInDocument) traverseNode(node, function(el){
            if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
               (!el.type || el.type === 'text/javascript') && !el.src)
              window['eval'].call(window, el.innerHTML)
          })
        })
      })
    }

    // after    => insertAfter
    // prepend  => prependTo
    // before   => insertBefore
    // append   => appendTo
      /**
       * 插入方法转换
       * @param html
       * @returns {*}
       */
    $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){
      $(html)[operator](this)
      return this
    }
  })

   // zepto.Z.prototype 继承所有$.fn所有原型方法
  zepto.Z.prototype = Z.prototype = $.fn

  // Export internal API functions in the `$.zepto` namespace
  zepto.uniq = uniq
  zepto.deserializeValue = deserializeValue
  $.zepto = zepto

  return $
})()

// If `$` is not yet defined, point it to `Zepto`
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

构造Zepto对象

  • 结构
    var Zepto = (function() {

    //实际构造函数`
    function Z(dom, selector) {
        var i, len = dom ? dom.length : 0
        for (i = 0; i < len; i++) this[i] = dom[i]
        this.length = len
        this.selector = selector || ''
    }

    zepto.Z = function(dom, selector) {
        return new Z(dom, selector)
    }

    //是否Zepto对象
    zepto.isZ = function(object) {
        return object instanceof zepto.Z
    }

    // 初始化参数DOM
     zepto.init = function(selector, context) {
       ...
        return zepto.Z(dom, selector)
    }

    //构造函数
    $ = function(selector, context){
        return zepto.init(selector, context)
    }
    //原型设置
    $.fn = { ... }


    zepto.Z.prototype = Z.prototype = $.fn

    $.zepto = zepto

    return $
})()

// If `$` is not yet defined, point it to `Zepto`
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
  • 方法图
    enter image description here

属性操作

enter image description here

DOM遍历

enter image description here

DOM操作

enter image description here

样式操作

enter image description here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant