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)
分析一$(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>
以下图片可以大体表示选择器的工作流程

浙公网安备 33010602011771号