介绍
Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。源码相比jQuery较简单,适合初学者阅读。我们这里从Core模块开始阅读。
Core模块结构
1 2 3 4 5 6
| var Zepto = (function() { ... })() window.Zepto = Zepto window.$ === undifined && (window.$ = Zepto)
|
我们可以看到,Zepto库提供了一个Zepto全局变量,该变量指向了,块级作用域执行后返回的结果,同时将这个全局变量赋值给window对象的Zepto和$变量作为属性。
Core模块核心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| var Zepto = (function() { var undefined, key, $, classList, ... zepto = {} ... function Z(dom, selector) { ... } ... zepto.Z = function(dom, selector) { return new Z(dom, selector) } ... zepto.init = function(selector, context) { ... return zepto.Z(dom, selector) } ... $ = function(selector, context){ return zepto.init(selector, context) } ... zepto.qsa = function(element, selector){ ... } ... $.fn = { ... } zepto.Z.prototype = Z.prototype = $.fn $.zepto = zepto return $ })()
|
Zepto变量指向了模块级作用域返回的$()函数,返回Zepto collection对象,$()函数里调用了zepto局部对象的init()函数,init()函数调用了zepto对象的Z()方法,Z()方法中又创建了Zepto collection对象。同时设置了Z函数显式原型为$.fn对象,并将zepto内部对象设置为$()函数的属性。
Zepto collection对象
1 2 3 4 5 6
| 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 collection对象是一个类数组对象,它包括了dom对象数组、获取到的dom元素的个数以及选择器。同时该函数对象的原型对象被设置为$.fn。
我个人觉得这个对象较难理解的有以下几点,我会在接下来的博客中解释:
- 何为类数组对象。
- 原型的设置及隐式原型与显示原型。
- 为什么在Chrome console输出Zepto collection对象的时候,对象是被[]数组符号包括的。
逐个方法解析
zepto.init()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null else if (context !== undefined) return $(context).find(selector) else dom = zepto.qsa(document, selector) } else if (isFunction(selector)) return $(document).ready(selector) else if (zepto.isZ(selector)) return selector else { if (isArray(selector)) dom = compact(selector) else if (isObject(selector)) dom = [selector], selector = null 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) } return zepto.Z(dom, selector) }
|
上段代码为Zepto选择器功能的逻辑,主要通过以下几种方式进行获取dom:
- 选择器为字符串
- 选择器为标签
- 有context,先获取context,在进行选择
- 选择器为CSS选择器
- 选择器为function()
- 选择器为Zepto collection
- 选择器为dom、object、数组、html片段等
$.each()
$.each(collection, function(index, item){ … }) => collection
遍历数组元素或以key-value值对方式遍历对象。回调函数返回 false 时停止遍历。
1 2 3 4 5 6 7 8 9 10 11 12
| $.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 }
|
$.each()方法与数组Array.map()方法的区别就是,$.each()方法考虑了集合的情况,使用Array.call()方法对集合内每个元素执行回调函数(i, elements[i]做为参数),并返回处理后结果。
$.contains()
$.contains(parent, node) => boolean
检查父节点是否包含给定的dom节点,如果两者是相同的节点,则返回 false。
1 2 3 4 5 6 7 8 9
| $.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 }
|
方法首先判断了浏览器是否支持Node.contains()方法,如果不支持,则一层一层判断node的父节点和parent是否相等。
$.extend()
$.extend(target, [source, [source2, …]]) => target
$.extend(true, target, [source, …]) => target
通过源对象扩展目标对象的属性,源对象属性将覆盖目标对象属性。
默认情况下为,复制为浅拷贝(浅复制)。如果第一个参数为true表示深度拷贝(深度复制)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function extend(target, source, deep) { for (key in source) if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {} if (isArray(source[key]) && !isArray(target[key])) target[key] = [] extend(target[key], source[key], deep) } else if (source[key] !== undefined) target[key] = source[key] } $.extend = function(target){ var deep, args = slice.call(arguments, 1) if (typeof target == 'boolean') { deep = target target = args.shift() } args.forEach(function(arg){ extend(target, arg, deep) }) return target }
|
$.extend方法处理的是一个可选参数的逻辑,首先假设deep为undifined。
- 如果第一个参数是布尔型则将deep设置为true,将args[0]表示未target, source为args[1…]。
- 如果第一个参数不是布尔型则deep为undifined,参数target表示target,source为args[0…]。
- 执行extend()方法,参数为target和source的组合。
extend()方法为一个递归深拷贝的方法,其中处理了当目标target[key]存在或者不存在的情况。
- 如果存在,则将其覆盖。
- 如果不存在,则创建新的空对象/数组,进行存储。
$.isNumeric()
$.isNumeric(value) => boolean
如果该值为有限数值或一个字符串表示的数字,则返回ture。
1 2 3 4 5 6
| $.isNumeric = function(val) { var num = Number(val), type = typeof val return val != null && type != 'boolean' && (type != 'string' || val.length) && !isNaN(num) && isFinite(num) || false }
|
这个方法应该注意的是使用Number(val)进行类型装换后,非字符串类型会被转换为NaN或者Finite类型。
$fn.find()
find(selector) => collection
find(collection) => collection v1.0+
find(element) => collection v1.0+
在当对象前集合内查找符合CSS选择器的每个元素的后代元素。
如果给定Zepto对象集合或者元素,过滤它们,只有当它们在当前Zepto集合对象中时,才回被返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| find: function(selector){ var result, $this = this if (!selector) result = $() else if (typeof selector == 'object') result = $(selector).filter(function(){ var node = this return emptyArray.some.call($this, function(parent){ return $.contains(parent, node) }) }) else if (this.length == 1) result = $(zepto.qsa(this[0], selector)) else result = this.map(function(){ return zepto.qsa(this, selector) }) return result }
|
$this代表执行find()方法的Zepto collection对象。选择器为collection/dom时,只有当它们在当前Zepto集合对象中时,才回被返回。
$fn.after()
after(content) => self
在每个匹配的元素后插入内容。内容可以为html字符串,dom节点,或者节点组成的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ] adjacencyOperators.forEach(function(operator, operatorIndex) { var inside = operatorIndex % 2 $.fn[operator] = function(){ var argType, nodes = $.map(arguments, function(arg) { var arr = [] argType = type(arg) if (argType == "array") { arg.forEach(function(el) { if (el.nodeType !== undefined) return arr.push(el) else if ($.zepto.isZ(el)) return arr = arr.concat(el.get()) arr = arr.concat(zepto.fragment(el)) }) return arr } return argType == "object" || arg == null ? arg : zepto.fragment(arg) }), parent, copyByClone = this.length > 1 if (nodes.length < 1) return this return this.each(function(_, target){ parent = inside ? target : target.parentNode target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null var parentInDocument = $.contains(document.documentElement, parent) nodes.forEach(function(node){ if (copyByClone) node = node.cloneNode(true) else if (!parent) return $(node).remove() parent.insertBefore(node, target) if (parentInDocument) traverseNode(node, function(el){ if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && (!el.type || el.type === 'text/javascript') && !el.src){ var target = el.ownerDocument ? el.ownerDocument.defaultView : window target['eval'].call(target, el.innerHTML) } }) }) }) } }
|