下载源码打开看了一下,几百上千行代码,安慰自己别慌,毕竟透出了这么多方法,这代码量已经很少了。
$ 的实现:
1⃣️:$ 方法
$ = function (selector, context) {
return zepto.init(selector, context)
}
2⃣️:init 方法:完成初始化工作
zepto.init = function(selector, context) {
1 var dom
2 if (!selector) return zepto.Z() // 传送门1 zepto.Z
3 else if (typeof selector == 'string') {
4 selector = selector.trim()
5 if (selector[0] == '<' && fragmentRE.test(selector))
6 dom = zepto.fragment(selector, RegExp.$1, context), selector = null
7 else if (context !== undefined) return $(context).find(selector)
8 else dom = zepto.qsa(document, selector)
9 }
10 else if (isFunction(selector)) return $(document).ready(selector)
11 else if (zepto.isZ(selector)) return selector
12 else {
13 if (isArray(selector)) dom = compact(selector)
14 else if (isObject(selector))
15 dom = [selector], selector = null
16 else if (fragmentRE.test(selector))
17 dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
18 else if (context !== undefined) return $(context).find(selector)
19 else dom = zepto.qsa(document, selector)
20 }
21 return zepto.Z(dom, selector)
}
概括:根据 selector 选择器和 context 属性筛选条件搜索符合条件的 DOM 节点并返回。
详情解读:
第2行:当 selector 未定义的时候,出现了zepto.Z()
往下看......
第5行:出现了 selector ,扒扒文档看看 $ 的用法:
$('div') //=> 所有页面中的div元素
$('#foo') //=> ID 为 "foo" 的元素
// 创建元素:
$("Hello
") //=> 新的p元素
// 创建带有属性的元素:
$("", { text:"Hello", id:"greeting", css:{color:'darkblue'} })
//=> Hello
selector 是元素选择器,也可以看作元素的标志字符串形式。
继续看代码,selector[0] == '<' && fragmentRE.test(selector)
,
Reg的定义为:fragmentRE = /^\s*<(\w+|!)[^>]*>/
,意思可以兼容空格类型开头的
selector 是标签的时候就会执行第6行代码
第7行:selector 不是标签且属性筛选条件 context 不为空,先命中 context 再去寻找 dom 节点
第8行:selector 不是标签且属性筛选条件为空,进入 zepto.qsa 方法
第10行:selector 为函数的时候会进入 ready 方法,在DOM树建成的时候触发,是这个库的小彩蛋吧
第11行:selector 是 zepto.Z 的实例直接返回
第13行:selector 是数组类型则表明数组属性是经历过筛选的 DOM 节点,过滤掉为 null 的值
第14~15行:selector 是对象的时候这里会认为是个已经经历过 new Z(dom, selector)的 DOM 节点,用数组包装一下,并释放 selector
第16~17行:selector 是自闭合/未闭合标签,借助 zepto.fragment 转化成真正的 DOM
第18~19行:跟 selector 为 string 类型里的处理是一样的
第21行:调用构造方法,创建 Zepto.Z 对象并初始化对象的属性并返回。
----------------------------------------------❀❀❀❀完结撒花❀❀❀❀----------------------------------------------
zepto.Z() :
zepto.Z = function(dom, selector) {
return new Z(dom, selector) // 传送门 Z
}
⚠️:Z():
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 || ''
}
Z 方法是 zepto 的构造器(构造方法),拷贝经过筛选命中的 DOM 节点并初始化 length、selector 属性,所以创建出来的对象是个类数组。
zepto.fragment() :
zepto.fragment = function(html, name, properties) {
var dom, nodes, container
// singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
if (!dom) {
if (html.replace) html = html.replace(tagExpanderRE, "<$1>$2>")
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
if (!(name in containers)) name = '*'
/**
* containers = {
* 'tr': document.createElement('tbody'),
* 'tbody': table, 'thead': table, 'tfoot': table,
* 'td': tableRow, 'th': tableRow,
* '*': document.createElement('div')
* },
*/
container = containers[name]
container.innerHTML = '' + html
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}
if (isPlainObject(properties)) {
nodes = $(dom)
$.each(properties, function(key, value) {
// methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height‘,‘offset’]
if (methodAttributes.indexOf(key) > -1) nodes[key](value)
else nodes.attr(key, value)
})
}
return dom
}
当 selector 满足是标签的的前提会更细致的检测它是否真正为标签元素,必须要闭合,通过检测之后创建该元素
如果上步骤没有通过检测,会认为用户可能误把不是自闭合的标签写成了自闭合标签如
,会初始化标签为创建包裹的 DOM 节点container: 表格元素会做一层向上兼容,如果是表格元素则会创建其父元素,并把当前元素添加到父元素里。是其他未知元素会默认创建 div 元素作为当前元素的父元素
上面的 container.removeChild(this)
里面的 this 可能会使人迷惑,但看完$.each 方法就会明白 this 指向 elements 入参的元素项
借助 container 将 string 类型的元素入参转成真正的 dom 元素,并释放内存
$.each(elements, callback):
$.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
}
遍历 elements 参数,执行回掉函数加工 elements 的元素项,当回调函数返回 true 跳出遍历。
likeArray(obj):
function likeArray(obj) {
var length = !!obj && 'length' in obj && obj.length,
type = $.type(obj)
return 'function' != type && !isWindow(obj) && (
'array' == type || length === 0 ||
(typeof length == 'number' && length > 0 && (length - 1) in obj)
)
}
可用性拉满的一个方法(考虑到了很多场景),检测 obj 是否为一个数组/类数组。
$.type(obj):
$.type = type
function type(obj) {
return obj == null ? String(obj) :
// class2type = {},是存放数据类型的对象。
class2type[toString.call(obj)] || "object"
}
返回 obj 调用 toString 方法后的数据类型 ‘ [object Object] ... ’
isPlainObject(obj):
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
判断 obj 是不是普通对象
zepto.qsa(element, selector):
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)
// simpleSelectorRE = /^[\w-]*$/
return (element.getElementById && isSimple && maybeID) ?
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
(element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
slice.call(
isSimple && !maybeID && element.getElementsByClassName ?
maybeClass ? element.getElementsByClassName(nameOnly) :
element.getElementsByTagName(selector) :
element.querySelectorAll(selector)
)
}
提醒:nodeType 为 1、9、11 分别代表 元素节点、document 对象、简化的 document 对象。
兼容 jq 的写法,对 选择器 selector 进行过滤,带 * 或 . 的前缀将会被过滤掉,拿到真正的筛选条件(id/class/tagName/其他)。
如果传递进来的 element 不为节点或 document 返回空数组。
否则根据 selector 类型用相应原生 js 选择器进行 dom 节点的检索并返回。
作者还对 Safari 和 IE9及一下低版本的浏览器做了兼容,但都只是简单的不报错的兼容,在功能 hack 上还是放弃了它们。
find(selector):
1 find: function(selector){
2 var result, $this = this
3 if (!selector) result = $()
4 else if (typeof selector == 'object')
5 result = $(selector).filter(function(){
6 var node = this
7 return emptyArray.some.call($this, function(parent){
8 return $.contains(parent, node)
9 })
10 })
11 else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
12 else result = this.map(function(){ return zepto.qsa(this, selector) })
13 return result
14 }
提醒:第二行的 this 指的是命中属性筛选条件的 dom 节点、第六行的 this 指的是 [selector] 数组。
如果 selector 为对象, $(selector) 会返回 [selector] 数组,所以经过 filter 之后
会从命中属性筛选条件的 dom 节点里包含 selector 子节点的节点。
如果 selector 为正常的 string 选择器则会直接使用 js 原生选择器,从命中属性筛选条件的 dom 节点里选找到命中 selector 的节点。
$.contains(parent, node):
$.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 节点是否为 parent 节点的后代。后代可以是孩子,孙子,曾孙等等
有没有一种套娃的感觉,一个功能点对应一个方法这种低耦合且复用性高的编写代码风格值得学习
ready(callback):
ready: function(callback){
// don't use "interactive" on IE <= 10 (it can fired premature)
if (document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll))
setTimeout(function(){ callback($) }, 0)
else {
var handler = function() {
document.removeEventListener("DOMContentLoaded", handler, false)
window.removeEventListener("load", handler, false)
callback($)
}
document.addEventListener("DOMContentLoaded", handler, false)
window.addEventListener("load", handler, false)
}
return this
},
提醒:document.readyState:当前文档的状态,分别为:uninitialized - 还未开始载入、loading - 载入中、interactive - 已加载,文档与用户可以开始交互、complete - 载入完成
当 readyState 为 interactive 的时候会触发 DOMContentLoaded 事件;
当 readyState 为 complete 的时候会触发 load 事件;
该方法会在页面 '准备好的状态' 下执行传递进来的 callback 方法:
当页面 readyState 为 complete 或 interactive 也就是 DOM 树建成的时候 ‘立即执行' callback 方法,但低版本 IE 浏览器可能会导致 interactive 状态提前,所以需要用 doScroll 配合 load 事件 hack 一下:
IE有个特有的方法doScroll可以检测DOM是否加载完成。 当页面未加载完成时,该方法会报错,直到doScroll不再报错时,就代表DOM加载完成了。
‘立即执行': setTimeout(function(){ callback($) }, 0)
这里的立即执行并不是真正意义上的立即执行而是会把 callback 添加到当前执行队列的尾部等待执行。
compact(array):
function compact(array) {
return filter.call(array, function(item){ return item != null })
}
过滤掉数组为 null 的值