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源码解读一 #9

Open
wulala opened this issue Mar 20, 2017 · 0 comments
Open

Zepto源码解读一 #9

wulala opened this issue Mar 20, 2017 · 0 comments

Comments

@wulala
Copy link
Owner

wulala commented Mar 20, 2017

写不好开头,如同讲不好故事,那么阅读就会食不知味,味同嚼蜡。所以我在思考,思考怎么让你们读完引言就让你醍醐灌顶大彻大悟,从此超脱世俗,走向极乐。
可是可是我胸无点墨,这是一个哲学问题。再多也我扯不出来了。

本文基于 http://zeptojs.com/zepto.js v1.2.0 解读。

Zepto的 $() 如同狗皮膏药,啥东西往里面一放都能给你整出一点用法。我们最常用的莫过于通过它来选择、创建元素了。
$('.class'), $('p'), $('#id'), $('[type="text"]'), $('<div>new element</div>') 这么写个用法。

那么Zepto是如何通过传递进来的值来识别需求,让我们一起走进Zepto的内心世界,层层解剖,看一看Zepto的心到底是不是黑的。

毫无疑问,$() 是一个接受参数的函数定义,查看源码得知:

$ = function(selector, context){
  return zepto.init(selector, context)
}

就是返回另一个函数 zepto.init() 的执行结果。很明显咧,这根本看不出啥,只能接着看 zepto.init() 的定义:

zepto.init = function(selector, context) {
  if (!selector) return zepto.Z()
  return zepto.Z(dom, selector)
}

通过 $() 我们晓得这货需要一个执行结果,来看看这货返回了什么。代码被我精简了一下, 看到的又是返回另一个函数 zepto.Z() 的执行结果。我走过最长的路,就是你的套路,这调用都是一层又一层啊。只能接着往下看了:

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

...套路差不多要完了吧。还好,到这里我们终于知道了,这货返回了一个对象。既然都是套路,我们用套路的想法理解下, 构造函数 Z(), 应该会给对象提供一些初始的属性或方法,(构造函数之所以是一个构造函数,一般来说咧需要给对象实例提供一些成员属性和方法,才能成为一个构造函数,要不空的构造函数,直接继承 Object 好了), 查看下 Z() 的定义:

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 || ''
}

很简单咯, 就是添加 this.length, this.selector, 一个节点对应一个成员属性(this[i] = dom[i]

一轮下来,我们知道了调用 $() 最终得到了一个对象,对象有 2 + n (有多少节点就有多少个n) 个属性。归根到底咧,$() 就是通过参数取得节点信息。现在跟随镜头让我们重新走入 zepto.init() 的内心,看看它是怎么撩到node节点的芳心。

zepto.init = function(selector, context) {
  var dom
  // 如果没传递参数,返回空对象
  if (!selector) return zepto.Z()

  // 如果是字符串
  else if (typeof selector == 'string') {
    selector = selector.trim() // 去掉字符串的首尾空白

    // ...
    if (selector[0] == '<' && fragmentRE.test(selector))
      // 可以发现这里调用了一个 处理片段的方法,让我们跳出这里,先去看看这个函数怎么处理html片段,
      //    - 可以先看下传递进去的都是啥参数值:
      //      1. 字符串片段,
      //      2. 标签名,
      //      3. 上下文环境
      // 看完了,知道这货就是返回一个被处理成数组形式的 NodeList 对象
      dom = zepto.fragment(selector, RegExp.$1, context), selector = null
    // 如果提供上下文,则先创建上下文的Zepto对象,然后从$(context).find(selector)
    else if (context !== undefined) return $(context).find(selector)
    // 如果是一个CSS选择器(1.字符串,2. 没有<开头),则执行 zepto.qsa() 方法(代码挺简单,就不挂尸了),获取node节点列表的数组形式
    else dom = zepto.qsa(document, selector)
  }
  
  // 如果是对象
  else if (isFunction(selector)) return $(document).ready(selector)
  
  // 如果本身就是 Zepto对象,直接返回 Zepto对象, 这好傻逼啊,
  //      $( $('div') ) => $('div') => zepto.init() => 就是,如果有双层皮,先剥掉一层在循环一遍...
  else if (zepto.isZ(selector)) return selector

  // 如果既不是对象,又不是 Zepto对象,也不是函数
  else {
    // 友情提示: 通过 var getNodes = document.querySelectorAll('div'), var getNodes = document.querySelector('body') 获取的是NodeList对象,不是数组,
    //    ... 一些成熟的类库就是判断过多导致体积变大,
    //    ... 通过 querySelectorAll 获取的 NodeList 对象可以 通过 Array.prototype.slice.call(getNodes) 转成这里需要的数组形式
    if (isArray(selector)) dom = compact(selector)
    // 如果传进来的是 NodeList 对象的话
    else if (isObject(selector))
      dom = [selector], selector = null
    // 如果传进来的是html片段, 就是:
    //    <xxx> => 判断比较简单咯, 只要匹配到  < \w | ! > 就可以了
    // 那么就通过html字符串取得它的节点列表
    else if (fragmentRE.test(selector))
      dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
    else if (context !== undefined) return $(context).find(selector)
    else dom = zepto.qsa(document, selector)
  }
  // create a new Zepto collection from the nodes found... 英文更好理解
  return zepto.Z(dom, selector)
}

这里是处理html 片段时间。我累了,有空再补上一行一行的注释

// `$.zepto.fragment` takes a html string and an optional tag name
// to generate DOM nodes from the given html string.
// The generated DOM nodes are returned as an array.
// This function can be overridden in plugins for example to make
// it compatible with browsers that don't support the DOM fully.
// 
// 我特意留着英文注释给你们看, generate DOM nodes from the given html string.
// 
zepto.fragment = function(html, name, properties) {
  var dom, nodes, container

  // 如果是简单标签,=> <div></div>, <br />
  // 直接创建一个节点
  if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

  if (!dom) {
    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
    if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
    if (!(name in containers)) name = '*'
    container = containers[name]
    container.innerHTML = '' + html
    dom = $.each(slice.call(container.childNodes), function(){
      container.removeChild(this)
    })
  }

  if (isPlainObject(properties)) {
    nodes = $(dom)
    $.each(properties, function(key, value) {
      if (methodAttributes.indexOf(key) > -1) nodes[key](value)
      else nodes.attr(key, value)
    })
  }

  return dom
}

按照国际惯例,来个未完待续...

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