什么是zepto?
Zepto是一个轻量级的、针对现代高级浏览器的JavaScript工具库,它兼容jQuery的API。使用方式和jQuery类似,只需将其 js 文件引入即可使用其内置方法,提高 js 开发效率
关于本文
由于 Zepto 全部源代码内容较多,这里暂时只讨论关于 Zepto 的核心函数 Zepto / $ 函数的实现原理,虽然设计内容不多,但只要理解其核心设计思路后,便可自己着手实现一个类似的 js 工具库。加之其余代码更多是对其核心功能的扩展,因此在学习时可选择性忽略
关于完整源代码可以在 Zepto 中文网获得: https://www.zeptojs.com.cn/
外层结构
最外层使用立即执行函数包裹核心函数 Zepto 。最开始使用 define 检查是否存在 require 依赖项,如果存在则使用define定义一个并返回并执行一个传入global 参数的factory函数,否则直接执行factory函数,并用global作为参数
(function(global, factory) {
if (typeof define === 'function' && define.amd){
define(function() { return factory(global) })
}
else
factory(global)
}(this, function(window) {
var Zepto = (function() {
...
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
...
return Zepto
}))
注: global此时为window,factory 为第7行的函数
网络上关于第3行代码具体作用的相关回答:
此代码检查是否存在 require.js,即JavaScript依赖项管理库。
如果'define'不是未定义的,并且它是一个函数,并且'amd'(异步模块定义)也被定义了,那么代码假定require.js正在运行。
如果是这样,那么它定义'工厂'并将jQuery作为依赖项传递给它。否则,它通过将代码附加到根对象来设置代码所需的依赖关系。
至于'工厂'是什么:它不是由Javascript框架定义的,它最有可能是同一个文件中的一个函数。它将采用参数jQuery。
注册全局函数
使用 window 全局对象注册 Zepto 函数,并为此函数注册别名 $,函数执行完毕后返回 Zepto 接受用户输入的参数
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
...
return Zepto
核心函数结构
内部代码量比较多,总体可分为三大部分
第一部分:外层变量的声明和定义
第二部分:工具类函数的声明和定义
第三部分: $ / Zepto 函数的对象实现
其中,第三部分为重点,第一二部分会在分析第三部分挑选关联项说明
init
使用 window.Zepto() 调用全局函数(window 可省略)后返回 $ 函数, 也可直接 $() 调用 $ 函数;
执行后返回 zepto.init 函数处理传入参数
$ = function(selector, context){
return zepto.init(selector, context)
}
// 此函数对传入参数进行校验,根据适合的参数类型返回对应的处理函数或执行相应操作
zepto.init = function(selector, context) {
// 创建空的用于接收 dom 的变量
var dom
// 如果没有给定参数, 返回一个空的 Zepto 集合。关于 zepto.Z 的详细信息在下方给出
if (!selector) return zepto.Z()
// 优化字符串选择器
else if (typeof selector == 'string') {
// 清除两端空格
selector = selector.trim()
/*
判断是否为 html 标签, 如果为真则使用 zepto.fragment 函数创建相应的 dom 节点
Note: 在 Chrome 21 和 Firefox 15 以上版本内如果 fragment 不以 < 字符开始时
会抛出代号为 12 的错误。关于 zepto.fragement 的详细信息在下方给出
*/
if (selector[0] == '<' && fragmentRE.test(selector))
// RegExp.$1 -> 返回第一个满足正则表达式的结果
// selector = null -> 创建节点前选择器应置空
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// 如果存在 context, 优先创建具有 context 的 dom 节点, 并返回 find 函数结果
else if (context !== undefined) return $(context).find(selector)
// 如果为 CSS 选择器则将其传入 qsa 函数内查询相应 dom 元素
else dom = zepto.qsa(document, selector)
}
// 如果 selector 类型为函数,则
else if (isFunction(selector)) return $(document).ready(selector)
// 如果 selector 参数为 Zepto 实例,则返回自身
else if (zepto.isZ(selector)) return selector
else {
// 如果给定节点数组,则规范化数组
if (isArray(selector)) dom = compact(selector)
// 包裹 dom 节点
else if (isObject(selector))
dom = [selector], selector = null
// fragmentRE = /^\s*<(\w+|!)[^>]*>/
else if (fragmentRE.test(selector))
// 如果存在 context, 则使用 zepto.fragment 函数优先创建含有 context 参数的 dom 节点
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
// 不存在则返回 find 函数结果
else if (context !== undefined) return $(context).find(selector)
// 最后, 如果仅为 css 选择器,则调用 zepto.qsa 函数查询相应 dom 元素
else dom = zepto.qsa(document, selector);
}
// 从 dom 集合中创建一个新的 Zepto collection 并在执行完后返回结果
return zepto.Z(dom, selector)
}
zepto.Z
zepto.Z = function(dom, selector) {
// 返回 Z 实例,将 dom 节点和选择器作为此构造函数的参数
return new Z(dom, selector)
}
Z
function Z(dom, selector) {
var i, len = dom ? dom.length : 0;
// 将实例对象初始化为类数组对象
for (i = 0; i < len; i++) this[i] = dom[i];
// 类数组对象在拥有操作数组的方法和 length 属性后会变成一个数组
// 另外两个方法在 $.fn 对象内,此对象为 Zepto 的共享对象
this.length = len;
this.selector = selector || '';
}
$.fn
$.fn = {
constructor: zepto.Z,
length: 0,
// emptyArray = []
// 对返回的 Zepto 对象集合统一注入数组的方法,使其具有一般数组的功能
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
splice: emptyArray.splice,
indexOf: emptyArray.indexOf,
...,
filter: function(selector){
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element){
return zepto.matches(element, selector)
}))
},
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
},
ready: function(callback){
// readyRE = /complete|loaded|interactive/
// 满足条件后确定 dom 加载准备好后执行回调
if (readyRE.test(document.readyState) && document.body) callback($)
else document.addEventListener('DOMContentLoaded', function(){ callback($) },
false)
return this
},
attr: function(name, value){
var result
return (typeof name == 'string' && !(1 in arguments)) ?
(0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) :
this.each(function(idx){
if (this.nodeType !== 1) return
if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(nam e)))
})
},
...
}
zepto.fragment
zepto.fragment = function(html, name, properties) {
var dom, nodes, container
// 优化单标签参数
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1));
// 非单标签或 dom 未创建的情况
if (!dom) {
// tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)
// (([\w:]+)[^>]*)\/>/ig
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
// fragmentRE = /^\s*<(\w+|!)[^>]*>/
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
/*
containers = {
'tr': document.createElement('tbody'),
'tbody': table, 'thead': table, 'tfoot': table,
'td': tableRow, 'th': tableRow,
'*': document.createElement('div')
},
*/
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 为 Zepto 实例
nodes = $(dom);
// methodAttributes = ['val', 'css', 'html', 'text', 'data',
// 'width', 'height', 'offset']
$.each(properties, function(key, value) {
// 查询是否存在当前 key,如果存在则调用 nodes 上属性为 key 的函数,将 value 值传入进去
if (methodAttributes.indexOf(key) > -1) {
nodes[key](value);
}
// 否则视为普通属性,调用 attr 函数设置属性
else nodes.attr(key, value)
})
}
return dom
}
zepto.qsa
zepto.qsa = function(element, selector){
var found,
maybeID = selector[0] == '#',
maybeClass = !maybeID && selector[0] == '.',
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
isSimple = simpleSelectorRE.test(nameOnly)
// 确保选中[0]号位字符
return (element.getElementById && isSimple && maybeID) ?
// Safari DocumentFragment 没有 getElementById
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
(element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
slice.call(
// DocumentFragment 没有 getElementsByClassName/TagName
isSimple && !maybeID && element.getElementsByClassName ?
maybeClass ? element.getElementsByClassName(nameOnly) :
element.getElementsByTagName(selector) :
element.querySelectorAll(selector)
)
}
工具类函数
isFunction
function isFunction(value) { return type(value) == "function" }
type
function type(obj) {
return obj == null ? String(obj) :
// 使用 toString 原型方法并调用 call 将 this 指向重置为传入的 obj 参数检查判断其类型是否为 object
// class2type = {}
class2type[toString.call(obj)] || "object"
}
isObject
function isObject(obj) { return type(obj) == "object" }
zepto.isZ
zepto.isZ = function(object) { return object instanceof zepto.Z }
isPlainObject
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
总结
基于以上代码,可以实现 $ / Zepto 函数本身的全部功能,部分函数实现可以查询 Zepto源代码
代码本身相对来说较为直观,难点更大程度上在于边界处理和类型校验,感兴趣的话可以更加深入研究
本文介绍 Zepto.js 的核心实现原理,包括其结构、工具函数及主要功能的实现方式。Zepto 是一个轻量级 JavaScript 库,兼容 jQuery API,适用于现代浏览器。
169

被折叠的 条评论
为什么被折叠?



