Zepto源码跟读——选择器

截至到目前,你的zepto.rewrite.js代码为

var Zepto = (function(){
  var $,
  zepto = {}
  $ = function(selector, context){
    return zepto.init(selector, context)
  }
  zepto.init = function(selector, context) {
    //选择器初始化①
  }
  zepto.Z = function(dom, selector) {n
    dom.__proto__ = $.fn
    dom.selector = selector || ''
    return dom
  }
  $.fn = {
    //函数属性等
  }
  return $
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
View Code

分析一$(selector,context)的实现关键

$(selector,context),如$("#ho"),$(".ho"),$("div"),$("ul li ....")一类的选择实现的关键方法是getElementById,getElementsByTagName,getElementsByClassName及querySelectorAll(https://docs.webplatform.org/wiki/dom/Document)。

其中getElementsByClassName及querySelectorAll是使用HTML5新增的API,由于Zepto并没有兼容ie,也没有对兼容做调整,不过这样做效率很高。

<body>
<ul id="ho" class="ho">
  <li></li>
  <li></li>
  <li></li>
</ul>
<div></div>
<div></div>
<script>
console.log(document.getElementById("ho"))//<ul class="ho" id="ho"> ....
console.log(document.getElementsByClassName("ho").length)//1
console.log(document.getElementsByTagName("div").length)//2
console.log(document.querySelectorAll("ul li").length)//3
</script>
</body>

分析二$(selector,context)的实现关键

使用call间接调用数组的slice方法,并利用这个方法将NodeList 转化为数组,如

注意NodeList不是数组。

var emptyArray= [],
slice = emptyArray.slice,
container = document.createElement("div");
console.log(container.childNodes)//NodeList [  ]
console.log(slice.call(container.childNodes))//Array [ #text "12" ]

分析三$(function(){})的实现关键

监听DOMContentLoaded事件判断DOM对象是否已经形成

console.log(document.readyState);//loading
document.addEventListener("DOMContentLoaded", function(event) {
  console.log(document.readyState);//interactive
},false);

 

源码分析:

  1 var Zepto = (function(){
  2   
  3   //定义相关变量
  4   var $, emptyArray = [], slice = emptyArray.slice,filter = emptyArray.filter,
  5     document = window.document,
  6     cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
  7     fragmentRE = /^\s*<(\w+|!)[^>]*>/,
  8     singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
  9     tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
 10     // special attributes that should be get/set via method calls
 11     methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
 12     table = document.createElement('table'),
 13     tableRow = document.createElement('tr'),
 14     containers = {
 15       'tr': document.createElement('tbody'),
 16       'tbody': table, 'thead': table, 'tfoot': table,
 17       'td': tableRow, 'th': tableRow,
 18       '*': document.createElement('div')
 19     },
 20     readyRE = /complete|loaded|interactive/,
 21     simpleSelectorRE = /^[\w-]*$/,
 22     
 23     //判断对象类型的辅助对象,后面会初始化为{[object Function]: "function"...}
 24     class2type = {},
 25     zepto = {}
 26     
 27     //判断是否为数组
 28     isArray = Array.isArray ||
 29       function(object){return object instanceof Array }
 30   
 31   //判断目标类型,利用class2type对象输出具体类型
 32   function type(obj) {console.log("type");
 33     return obj == null ? String(obj) :
 34       class2type[toString.call(obj)] || "object"
 35   }
 36   
 37   //判断是否为function
 38   function isFunction(value) { console.log("isFunction"); return type(value) == "function" }
 39   
 40   //判断是否为window
 41   function isWindow(obj)     { console.log("isWindow"); return obj != null && obj == obj.window }
 42   
 43   //判断是否为document节点
 44   function isDocument(obj)   { console.log("isDocument"); return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
 45   
 46   //判断是否为object
 47   function isObject(obj)     { console.log("isObject"); return type(obj) == "object" }
 48   
 49   //是否为纯碎的对象,如{}
 50   function isPlainObject(obj) { console.log("isPlainObject"); 
 51     return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
 52   }
 53   
 54   //判断是否为类似数组,即是否有length属性
 55   function likeArray(obj) { console.log("likeArray"); return typeof obj.length == 'number' }
 56   
 57   //去除为null的数组元素
 58   function compact(array) { console.log("compact"); return filter.call(array, function(item){ return item != null }) }
 59   
 60   //去除复杂字符,格式化为可匹配字符串
 61   function dasherize(str) { console.log("dasherize");
 62     return str.replace(/::/g, '/')
 63            .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
 64            .replace(/([a-z\d])([A-Z])/g, '$1_$2')
 65            .replace(/_/g, '-')
 66            .toLowerCase()
 67   }
 68   
 69   //有些属性需要加px
 70   function maybeAddPx(name, value) { console.log("maybeAddPx"); 
 71     return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
 72   }
 73   
 74   //获取html字符串生成DOM节点
 75   zepto.fragment = function(html, name, properties) { console.log("zepto.fragment"); 
 76     var dom, nodes, container
 77     
 78     //当为单个标签时,如<p />,直接返回$包装后的节点
 79     if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
 80     
 81     //多个标签情况,将html赋值给一个父亲节点,然后获取这个节点并转化为节点数组。
 82     if (!dom) {
 83       if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
 84       if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
 85       if (!(name in containers)) name = '*'
 86 
 87       container = containers[name]
 88       container.innerHTML = '' + html
 89       dom = $.each(slice.call(container.childNodes), function(){
 90         container.removeChild(this)
 91       })
 92     }
 93 
 94     //一般标签情况,将html赋值给一个父亲节点,然后获取这个节点并转化为节点数组。
 95     if (isPlainObject(properties)) {
 96       nodes = $(dom)
 97       $.each(properties, function(key, value) {
 98         if (methodAttributes.indexOf(key) > -1) nodes[key](value)
 99         else nodes.attr(key, value)
100       })
101     }
102 
103     return dom
104   }
105 
106   //修改dom.__proto__属性为指向$.fn,使dom可以访问对象$.fn的所有属性
107   zepto.Z = function(dom, selector) {console.log("zepto.Z"+108)
108     dom = dom || []
109     dom.__proto__ = $.fn
110     dom.selector = selector || ''
111     return dom
112   }
113   
114   //判断是否为zepto对象
115   zepto.isZ = function(object) {console.log("zepto.isZ")
116     return object instanceof zepto.Z
117   }
118   
119   //zepto.init相当于jQuery的$.fn.init,接收selector,和一个可选参数context
120   zepto.init = function(selector, context) {console.log("zepto.init")
121     var dom
122     // 如果没有给出selector,返回空zepto对象
123     if (!selector) return zepto.Z()
124     // 字符串selectors的情况
125     else if (typeof selector == 'string') {
126       selector = selector.trim()
127       //html标签情况
128       if (selector[0] == '<' && fragmentRE.test(selector))
129         dom = zepto.fragment(selector, RegExp.$1, context), selector = null
130       //如果有context,先创建一个context Zepto对象,然后查找selector
131       else if (context !== undefined) return $(context).find(selector)
132       // CSS selector, 选取节点
133       else dom = zepto.qsa(document, selector)
134     }
135     // 如果是function,转换为页面加载时运行
136     else if (isFunction(selector)) return $(document).ready(selector)
137     // 如果本来就是zepto对象,直接返回
138     else if (zepto.isZ(selector)) return selector
139     else {
140       // 如果是数组,调整后返回
141       if (isArray(selector)) dom = compact(selector)
142       // 如果是DOM节点,包裹成数组
143       else if (isObject(selector))
144         dom = [selector], selector = null
145       // 如果是HTML标签,根据它创建节点
146       // 以下这部分的情况我不太理解,没找到适应情况,只能根据功能翻译
147       else if (fragmentRE.test(selector)){
148         dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null}
149       
150       //如果有context,先创建一个context Zepto对象,然后查找selector
151       else if (context !== undefined) return $(context).find(selector)
152       //最后,如果是一个CSS selector,进行节点选择。
153       else dom = zepto.qsa(document, selector)
154     }
155     // 创建zepto对象
156     return zepto.Z(dom, selector)
157   }
158 
159   // "$" 是将作为返回值要返回给Zepto,它调用zepto.init初始化,返回Zepto对象
160   $ = function(selector, context){console.log("$")
161     return zepto.init(selector, context)
162   }
163 
164     
165   // zepto.qsa 是Zepto's CSS 选择器,它调用document.querySelectorAll方法和一些效率高的document方法,如document.getElmentById
166   zepto.qsa = function(element, selector){console.log("zepto.qsa")
167     var found,
168         maybeID = selector[0] == '#',
169         maybeClass = !maybeID && selector[0] == '.',
170         nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // 在ID和class时,只获取名字
171         isSimple = simpleSelectorRE.test(nameOnly)
172     return (isDocument(element) && isSimple && maybeID) ?
173       ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
174       (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
175       slice.call(
176         isSimple && !maybeID ?
177           maybeClass ? element.getElementsByClassName(nameOnly) : // class情况 
178           element.getElementsByTagName(selector) : // tag情况
179           element.querySelectorAll(selector) // 复合情况,如"ul li",
180       )
181   }
182   
183   //以下内容标注不讲的原因是不是选择器的原理,但选择器却调用到了一些$,$.fn方法及相关
184   //$.each暂且不讲,先通过API理解用法
185   $.each = function(elements, callback){ console.log("$.each"); 
186     var i, key
187     //
188     if (likeArray(elements)) {
189       for (i = 0; i < elements.length; i++)
190         if (callback.call(elements[i], i, elements[i]) === false) return elements
191     } else {
192       for (key in elements)
193         if (callback.call(elements[key], key, elements[key]) === false) return elements
194     }
195 
196     return elements
197   }
198   
199   //利用each初始化class2type
200   $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
201     class2type[ "[object " + name + "]" ] = name.toLowerCase()
202   })
203   
204   //$.fn里面的功能方法调用的函数也不讲
205   function funcArg(context, arg, idx, payload) { console.log("funcArg"); 
206     return isFunction(arg) ? arg.call(context, idx, payload) : arg
207   }
208   
209   function setAttribute(node, name, value) { console.log("setAttribute"); 
210     value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
211   }
212   
213   //功能方法$.fn{}暂时不讲,列在这里是因为有些选择器实际是变形调用,如$(function(){})会变形为 $(document).ready(function(){})调用$.fn.ready
214   $.fn = {
215     ready: function(callback){ console.log("ready"); 
216       if (readyRE.test(document.readyState) && document.body) callback($)
217       else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
218       return this
219     },
220     each: function(callback){ console.log("each"); 
221       emptyArray.every.call(this, function(el, idx){
222         return callback.call(el, idx, el) !== false
223       })
224       return this
225     },
226     css: function(property, value){ console.log("css"); 
227       if (arguments.length < 2) {
228         var element = this[0], computedStyle = getComputedStyle(element, '')
229         if(!element) return
230         if (typeof property == 'string')
231           return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
232         else if (isArray(property)) {
233           var props = {}
234           $.each(isArray(property) ? property: [property], function(_, prop){
235             props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
236           })
237           return props
238         }
239       }
240 
241       var css = ''
242       if (type(property) == 'string') {
243         if (!value && value !== 0)
244           this.each(function(){ this.style.removeProperty(dasherize(property)) })
245         else
246           css = dasherize(property) + ":" + maybeAddPx(property, value)
247       } else {
248         for (key in property)
249           if (!property[key] && property[key] !== 0)
250             this.each(function(){ this.style.removeProperty(dasherize(key)) })
251           else
252             css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
253       }
254 
255       return this.each(function(){ this.style.cssText += ';' + css })
256     },
257     
258     text: function(text){ console.log("text"); 
259       return 0 in arguments ?
260         this.each(function(idx){
261           var newText = funcArg(this, text, idx, this.textContent)
262           this.textContent = newText == null ? '' : ''+newText
263         }) :
264         (0 in this ? this[0].textContent : null)
265     },
266     attr: function(name, value){ console.log("attr"); 
267       var result
268       return (typeof name == 'string' && !(1 in arguments)) ?
269         (!this.length || this[0].nodeType !== 1 ? undefined :
270           (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
271         ) :
272         this.each(function(idx){
273           if (this.nodeType !== 1) return
274           if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
275           else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
276         })
277     },
278     //其它方法
279   }
280   return $
281 })()
282 window.Zepto = Zepto
283 window.$ === undefined && (window.$ = Zepto)

HTML测试页面

<body>
<ul id="ho" class="ho">
  <li></li>
  <li></li>
  <li></li>
</ul>
<div id="div"></div>
<div></div>

<script>
console.log($("#ho"))
console.log($("#ho,#div"))
console.log($("div"))
console.log($(".ho li"))
console.log($(function(){
    alert("DOM is loaded");
}))
console.log($("<p id='greeting' style='darkblue'>Hello</p>"))
console.log($("<p />", { text:"Hello", id:"greeting", css:{color:'darkblue'} }))
</script>
</body>

以下图片可以大体表示选择器的工作流程

posted on 2014-10-21 20:18  吹过的风  阅读(194)  评论(0)    收藏  举报