承接第三篇末端内容,本篇连系官方 API 进入对 Zepto 中心的剖析,最先难度会比较大,须要重点明白几个中心对象的关联,方能找到线索。
$()
与 Z 对象建立
Zepto Core API 的首个要领 $()
依据其官方诠释:
Create a Zepto collection object by performing a CSS selector, wrapping DOM nodes, or creating elements from an HTML string.
经由历程运用 CSS 挑选器,或包装 DOM 节点,或从 HTML 片断中建立一个 Zepto 鸠合对象。该处即为中心模块的进口,对应 src/zepto.js
中的以下挪用:
//\ Line 231
$ = function(selector, context) {
return zepto.init(selector, context);
};
//\ Line 186
zepto.init = function(selector, context) {
//\ 该函数的几种出口
/*\ 1 */ return zepto.Z();
/*\ 2 */ return $(context).find(selector);
/*\ 3 */ return $(document).ready(selector);
/*\ 4 */ return selector;
/*\ 5 */ return $(context).find(selector);
/*\ 6 */ return zepto.Z(dom, selector);
};
$
是一个收紧进口的 zepto.init
别号,如许的函数通报使得 zepto.init
的具值参数最多为 2 个。进入 zepto.init
函数,先疏忽中心的处置惩罚细节,注重到终究出口共有如上枚举的六种,第 1 种与第 6 种堕入一个名为 Z
的对象建立历程,下文中发明 $.fn
对象将 constructor
指向了 zepto.Z
且设定了 Z
对象的原型,此处恰是 Zepto 的组织组织体式格局地点:
//\ Line 172
zepto.Z = function(dom, selector) {
return new Z(dom, selector);
};
//\ Line 408
$.fn = {
constructor: zepto.Z,
//\ ...
}
//\ Line 938
zepto.Z.prototype = Z.prototype = $.fn;
加载 Zepto 代码后,可在浏览器中举行以下调试即可开端熟悉 Z 对象的天生:
//\ 建立恣意一个 Zepto 对象,他的原型均指向 $.fn
> $().__proto__ === $.fn
true
//\ $ 对象中包括了 Zepto 包级别的东西函数,重要用于扩大 Zepto 功用,以 $.func 体式格局对外暴露
> Object.keys($)
(22) ["extend", "contains", "type", ... ]
//\ $.fn 对象包括了 Zepto 对外暴露的操纵 API,面向对象均为经由历程 zepto.Z 函数建立的 Z 对象
> Object.keys($.fn)
(75) ["constructor", "length", "forEach", "reduce", ... ]
//\ 因而 Z 对象上的函数挪用指向其原型 $.fn 上的同名函数
> $().hasClass === $.fn.hasClass
true
继承调试历程,剖析 Z 对象的本质:
//\ 建立一个空的 Z 对象,发明 Chrome 将其识别为一个“数组”
> $()
Z [selector: ""]
//\ 但其现实并非 Array 包装类的一个实例
> $() instanceof Array
false
> $() instanceof Zepto.zepto.Z
true
//\ Zepto 经由历程 "扩大数组" 这类体式格局使得其对外体现为一个对外包括成员变量 selector 的数组,对内能够举行函数式运算的天真数据组织
> Object.keys($())
(2) ["length", "selector"]
如许 “扩大数组” 天生的要领在于这个异常简明的函数,这里也展现了下文中 this
的指向为一个 Z
对象:
//\ Line 128
//\ Z 对象的 Constructor
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.init
要领,其本质即为挪用该组织函数天生 Z 对象(2/3/5 出口返回的是在某个 Z 对象下操纵天生的 Z 对象,4 出口现实上传入的是 Z 对象因而直接返回本身)。Z 对象 组织函数读入的两个参数 dom
与 selector
在 Zepto 中也有明白的范例,用以保证 Z 组织函数的简朴性。
dom
的天生与 selector
的取值局限
dom
的天生严厉依赖于以下的几个正则表达式以及其天生函数 zepto.fragment
,该步骤比较艰涩,简而言之功用为映照原生 dom 组织至响应 Z 对象的关联:
//\ Line 10
//\ 用以婚配一般的 HTML 片断,这内里 Group 1 (\w+|!) 用来拿标签范例,比方: div
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
//\ 用以婚配单标签或两个闭合标签间没有内容的状况,如:<p> / <abbr></abbr>
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
//\ 用以婚配不该自闭合的标签,Group 1 / Gruop 2 均为 Group 1 推断前提束缚的非自闭和标签
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
//\ Line 140
zepto.fragment = function(html, name, properties) {
var dom, nodes, container;
//\ 假如满足 singleTagRE 就挪用 document 建立该元素,再经由历程 $() 初始化
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1));
if (!dom) {
//\ 假如声明 replace 将 html 标签修复
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>");
//\ name 假如不指定,默许取标签范例
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1;
//\ 假如 name 不在全局变量 containers 内,取 *
if (!(name in containers)) name = "*";
//\ 从 containers 拿详细标签,建立 html 内容
container = containers[name];
container.innerHTML = "" + html;
//\ 将 container 中每个子节点的内容删除,此时 dom 盈余部份是一个 Z 元素
//\ 参考:https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
dom = $.each(slice.call(container.childNodes), function() {
container.removeChild(this);
});
}
//\ 假如传入第三个参数为一般对象
if (isPlainObject(properties)) {
//\ 依据 dom 建立 Z 对象,再将 properties 中定义的属性挂载到 $(dom) 上
nodes = $(dom);
$.each(properties, function(key, value) {
if (methodAttributes.indexOf(key) > -1) nodes[key](value);
else nodes.attr(key, value);
});
}
//\ 返回 dom
return dom;
};
剖析时期的几个疑问:
- 为何有三个正则用于测试:
为了保证下三种挪用体式格局天生的效果雷同(现实上形貌的也是同一个空标签):
> $.zepto.fragment("<div>")
Z [div, selector: ""]
> $.zepto.fragment("<div/>")
Z [div, selector: ""]
> $.zepto.fragment("<div></div>")
Z [div, selector: ""]
-
container
与containers
:
containers
用于当以 name
为某个标签写为 innerHTML
时其父元素是合理的,这里仅在是表格元素的状况下替换了父容器,其他状况下默许采纳 div
.
//\ Line 22
containers = {
tr: document.createElement("tbody"),
tbody: table,
thead: table,
tfoot: table,
td: tableRow,
th: tableRow,
"*": document.createElement("div")
},
-
isObject
与isPlainObject
:
本函数中剖断第三个参数范例时,采纳了 isPlainObject
而不仅仅是 isObject
是为了避开数组等其他能够被识别为 Object 的状况,此处校验该对象的原型。
//\ Line 74
function isPlainObject(obj) {
return (
isObject(obj) &&
!isWindow(obj) &&
Object.getPrototypeOf(obj) == Object.prototype
);
}
selector
较 DOM 要简朴很多,假如不引入分外的 Selector 模块,其取值局限就是 Document.querySelectorAll()
这一浏览器原生要领支撑的挑选器的取值局限。(注重 Zepto 源代码中 selector
多用于标识形参,这里提到的 selector
现实上是 Z 对象中的 selector
属性)
zepto.init
的六种出口
有了上面的铺垫,终究能够进入 zepto.init
,剖析其六种出口对应的 Z 对象建立或操纵历程:
zepto.init = function(selector, context) {
var dom
//\ 出口 1: 当第一个入参为 Falsy 值,那末直接返回一个空的 Z 对象(该出口行动为建立)
//\ 比方: $() / $(undfined)
if (!selector) return zepto.Z()
//\ 假如 selector 形参是字符串
else if (typeof selector == 'string') {
//\ 当 selector 形参第一个字符为 '<' 且相符 fragmentRE 的婚配效果,那末传入 zepto.fragment 要领,建立 Z 对象,比方:$("<p></p>")
//\ 跳至出口 6
if (selector[0] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
//\ 出口 2: 假如传入的 context 不为空,那末基于 context 建立一个 Z 对象并返回包括 context 的 Z 对象(该出口行动为挑选)
//\ 比方 $("p", { text:"Hello", id:"greeting", css:{color:'darkblue'} })
else if (context !== undefined) return $(context).find(selector)
//\ 出口3:假如没有 context 请求,则直接在全文 qsa(该出口行动为挑选)
//\ 比方 $("p#grt")
else dom = zepto.qsa(document, selector)
}
//\ 出口4:假如 selector 形参为函数,那将其挂载在 ready 要领内(该出口行动为逻辑 Defer)
//\ 比方 $(() => alert("DONE))
else if (isFunction(selector)) return $(document).ready(selector)
//\ 出口 5: 当第一个入参已为 Z 对象时直接返回本身(该出口行动为供应兼容)
//\ 比方 JSON.stringify($($('p'))) === JSON.stringify($('p'))
else if (zepto.isZ(selector)) return selector
//\ 假如火线推断前提悉数失利,进入终究的范例推断
else {
//\ 假如传入的 Selector 是数组,那末去除 Falsy 值兼并,比方 $([$('div'), $('body')])
if (isArray(selector)) dom = compact(selector)
//\ 此处为降级前提,跳入出口 6
else if (isObject(selector))
dom = [selector], selector = null
//\ 盈余部份参考出口 2/3 处置惩罚体式格局
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)
}
//\ 出口 6,以上文处置惩罚范例的 dom / selector 建立 Z 对象(该出口行动为建立)
return zepto.Z(dom, selector)
}
此时,也能够给出 dom
对象与 Z 对象的真正关联,dom
是用于 Z 对象天生历程当中的中心 Z 对象;zepto.init
与 zepto.fragment
必需兼并起来才真正明白 $()
能够多范例进,单范例出的设想技能及实在企图,这也是 Zepto 的英华地点。