zepto源码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了zepto源码相关的知识,希望对你有一定的参考价值。

/**
*
*    ┏┓   ┏┓
*   ┏┛┻━━━┛┻┓
*   ┃       ┃
*   ┃   ━   ┃
*   ┃ ┳┛ ┗┳ ┃
*   ┃       ┃
*   ┃   ┻   ┃
*   ┃       ┃
*   ┗━┓   ┏━┛Code is far away from bug with the animal protecting
*     ┃   ┃ 神兽保佑,代码无bug
*     ┃   ┃
*     ┃   ┗━━━┓
*     ┃      ┣┓
*     ┃     ┏┛
*     ┗┓┓┏━┳┓┏┛
*      ┃┫┫ ┃┫┫
*      ┗┻┛ ┗┻┛
*
*/

/* Zepto 1.1.4 - zepto event ajax form ie detect fx fx_methods assets data deferred callbacks selector touch gesture stack ios3 - zeptojs.com/license */
/***
中文注释由 李祥威 添加,包含了zepto所有17个模块,为个人对细节的理解,官方解释很详细的地方就不说啦
难免有错漏,联系我: [email protected]
***/
//把代码用闭包保护起来,这一块定义的是zepto对象和他的属性方法。
//用小括号包住一个匿名函数,返回的就是这个函数,后面跟再跟()就相当于调用这个匿名函数了
var Zepto = (function() {
//定义本对象需要用到的对象,以及缓存快捷方式(优化性能)
var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
document = window.document,
//elementDisplay对象用来缓存元素节点默认的display属性
elementDisplay = {},
//classCache对象用来保存class的名字以及它对应的正则表达式
classCache = {},
//cssNumber对象保存的属性都是不需要添加px后缀的
cssNumber = { ‘column-count‘: 1, ‘columns‘: 1, ‘font-weight‘: 1, ‘line-height‘: 1,‘opacity‘: 1, ‘z-index‘: 1, ‘zoom‘: 1 },
//检测是否为html标签正则,举栗子:<div> <h1>
fragmentRE = /^\s*<(\w+|!)[^>]*>/,
//出于优化考虑,匹配只有单一标签的情况<div /> <div></div> <div>情况,里面不含子元素的
singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
//检测没有正确闭合的标签,举栗子:<div />
tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
//检测是不是<body><html>根标签
rootNodeRE = /^(?:body|html)$/i,
//检测有没有大写字母
capitalRE = /([A-Z])/g,

// special attributes that should be get/set via method calls
methodAttributes = [‘val‘, ‘css‘, ‘html‘, ‘text‘, ‘data‘, ‘width‘, ‘height‘, ‘offset‘],
//和相邻节点有关的操作方法
adjacencyOperators = [ ‘after‘, ‘prepend‘, ‘before‘, ‘append‘ ],
//把创建表格的相关操作缓存起来
table = document.createElement(‘table‘),
tableRow = document.createElement(‘tr‘),
containers = {
‘tr‘: document.createElement(‘tbody‘),
‘tbody‘: table, ‘thead‘: table, ‘tfoot‘: table,
‘td‘: tableRow, ‘th‘: tableRow,
‘*‘: document.createElement(‘div‘)
},
//元素加载好的的状态
readyRE = /complete|loaded|interactive/,
//检测简单dom选择器,只能是数字字母组合
simpleSelectorRE = /^[\w-]*$/,
//用来存放每种对象类型,{["object Array"]: array, ["object String"]: string ...},
class2type = {},
//缓存class2type的字符串
toString = class2type.toString,
zepto = {},
camelize, uniq,
//创建临时div容器
tempParent = document.createElement(‘div‘),
//把下面可能出现的错误属性名称,替换为正确的驼峰形式的属性名称
propMap = {
‘tabindex‘: ‘tabIndex‘,
‘readonly‘: ‘readOnly‘,
‘for‘: ‘htmlFor‘,
‘class‘: ‘className‘,
‘maxlength‘: ‘maxLength‘,
‘cellspacing‘: ‘cellSpacing‘,
‘cellpadding‘: ‘cellPadding‘,
‘rowspan‘: ‘rowSpan‘,
‘colspan‘: ‘colSpan‘,
‘usemap‘: ‘useMap‘,
‘frameborder‘: ‘frameBorder‘,
‘contenteditable‘: ‘contentEditable‘
},
//判断时候是否为数值,如果有原生isArray方法就用原生的
isArray = Array.isArray ||
function(object){ return object instanceof Array }

//判断给的元素是不是匹配选择器,类似原生的matches()方法,这是一个比较新的方法,下面可以看到,带前缀
zepto.matches = function(element, selector) {
//任意一个参数为空或者传进来的元素不是element类型就退出函数
if (!selector || !element || element.nodeType !== 1) return false
//获取各内核下的matchesSelector方法,存在的话就用啦
var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
element.oMatchesSelector || element.matchesSelector
if (matchesSelector) return matchesSelector.call(element, selector)
// fall back to performing a selector:
//temp = !parent 有父元素就为false,没有结构为TRUE
var match, parent = element.parentNode, temp = !parent
//没有父元素就创建一个临时的父元素,然后把element放里面
if (temp) (parent = tempParent).appendChild(element)
//在父元素上调用queryselectorall来匹配selector(说就是把parent作为查询上下文),在返回的结构中查找是否存在element,如果不存在是会返回-1的,通过位运算~可以-1转化为0,因为这个方法是要返回布尔值
match = ~zepto.qsa(parent, selector).indexOf(element)
//把临时的父元素div删除
temp && tempParent.removeChild(element)
return match
}

//判断类型,null返回"null",然后再使用对象的toString方法来判断对象类型,如果class2type没有对应的,就返回“object”,其他返回对应的类型值
function type(obj) {
return obj == null ? String(obj) :
class2type[toString.call(obj)] || "object"
}

//判断对象类型
function isFunction(value) { return type(value) == "function" }
function isWindow(obj) { return obj != null && obj == obj.window }
function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }
function isObject(obj) { return type(obj) == "object" }
//是否为简单原始对象,例如Object.create(null);就返回false了
function isPlainObject(obj) {
return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}
//判断是否为类数组对象 nodeList等
function likeArray(obj) { return typeof obj.length == ‘number‘ }
//压缩数组,把值为null和undefined的删除掉
function compact(array) { return filter.call(array, function(item){ return item != null }) }
//复制数组,concat()方法参数为空的话,就会返回对当前函数的复制
function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array }
//把get-name转化为驼峰形式getName,(.)表示跟在-后的字符,chr保留着被()匹配内容的引用,把函数赋予变量可以根据情况选择性地赋予需要执行的函数
camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : ‘‘ }) }
//字符串格式转化,驼峰转为line-height,::转化为/
function dasherize(str) {
return str.replace(/::/g, ‘/‘)
.replace(/([A-Z]+)([A-Z][a-z])/g, ‘$1_$2‘)
.replace(/([a-z\d])([A-Z])/g, ‘$1_$2‘)
.replace(/_/g, ‘-‘)
.toLowerCase()
}
//去掉数组里重复的内容,通过判断indexOf在数组的位置和循环时的索引是否相等
uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) }
//返回类名对应的正则表达式
function classRE(name) {
return name in classCache ?
classCache[name] : (classCache[name] = new RegExp(‘(^|\\s)‘ + name + ‘(\\s|$)‘))
}
//判断是否需要为该属性值加"px"后缀
function maybeAddPx(name, value) {
return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
}
//获取元素节点默认的display类型
function defaultDisplay(nodeName) {
var element, display
//看看有没有缓存,没有就进行判断,判断完了缓存起来
if (!elementDisplay[nodeName]) {
element = document.createElement(nodeName)
//要把元素添加到页面里才能获取他的样式
document.body.appendChild(element)
display = getComputedStyle(element, ‘‘).getPropertyValue("display")
element.parentNode.removeChild(element)
//当display:none的时候,可以对元素设置宽高等,类似看不见的块元素
display == "none" && (display = "block")
elementDisplay[nodeName] = display
}
return elementDisplay[nodeName]
}
//获取子元素集,有原生的children就用,把子元素复制到到数组里,没有就用zepto的map方法对当前元素的子元素都进行判断。 children只返回nodeType为element的,childNodes会返回所有类型
function children(element) {
return ‘children‘ in element ?
slice.call(element.children) :
$.map(element.childNodes, function(node){ if (node.nodeType == 1) return node })
}

// `$.zepto.fragment` takes a html string and an optional tag name
// to generate DOM nodes nodes from the given html string.
// The generated DOM nodes are returned as an array.
// This function can be overriden in plugins for example to make
// it compatible with browsers that don‘t support the DOM fully.
zepto.fragment = function(html, name, properties) {
var dom, nodes, container

// A special case optimization for a single tag 对于单一元素,没有子元素什么的直接创建一个,不用像下面那样处理
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

if (!dom) {
//如果标签残缺就重新生成,像是<div />,RegExp.$1缓存正则中的()内容,就是节点名称
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
//判断是不是表单标签,是的话创建对应表单容器,不是就用div作为容器了
if (!(name in containers)) name = ‘*‘

container = containers[name]
container.innerHTML = ‘‘ + html
//这里把container.childNodes复制给了dom,作为一个数组返回(数组里包含dom结构),再把container的子元素一个个删除
dom = $.each(slice.call(container.childNodes), function(){
container.removeChild(this)
})
}

if (isPlainObject(properties)) {
nodes = $(dom)
$.each(properties, function(key, value) {
//如果需要设置的属性是有对应设置方法的话,例如$.width(),那就直接用,没有的话就用通用的attr()方法
if (methodAttributes.indexOf(key) > -1) nodes[key](value)
else nodes.attr(key, value)
})
}

return dom
}

// `$.zepto.Z` swaps out the prototype of the given `dom` array
// of nodes with `$.fn` and thus supplying all the Zepto functions
// to the array. Note that `__proto__` is not supported on Internet
// Explorer. This method can be overriden in plugins.
zepto.Z = function(dom, selector) {
dom = dom || []
//让dom数组继承$.fn上的所有方法,因为是Dom结构,所以没有prototype,就只能利用__proto__(是指内部[[prototype]]),ie同学悲剧了(用ie模块兼容)
//dom.__proto__ 就是该对象的内部属性[[prototype]],指向该对象构造函数的prototype
dom.__proto__ = $.fn
dom.selector = selector || ‘‘
return dom
}

// `$.zepto.isZ` should return `true` if the given object is a Zepto
// collection. This method can be overriden in plugins.
zepto.isZ = function(object) {
//zepto.init里至始至终没有使用new,之所以能够用instanceof来检测是否为继承关系,是因为 dom.__proto__ = $.fn 改变了该对象对构造函数原型的引用(用来记录谁是它构造函数)
//而zepto.Z.prototype = $.fn 改变了zepto.Z的原型
//一个对象的__proto__ 等于 一个函数的prototype时,为继承关系
//例如 var a = function(){}; a.prototype = window; b = {}; b.__proto__ = window; b instanceof a; 会返回TRUE
return object instanceof zepto.Z
}

// `$.zepto.init` is Zepto‘s counterpart to jQuery‘s `$.fn.init` and
// takes a CSS selector and an optional context (and handles various
// special cases).
// This method can be overriden in plugins.
zepto.init = function(selector, context) { //创建zepto集合,选择对应的节点
var dom
// If nothing given, return an empty Zepto collection
if (!selector) return zepto.Z()
// Optimize for string selectors
else if (typeof selector == ‘string‘) {
selector = selector.trim()
// If it‘s a html fragment, create nodes from it
// Note: In both Chrome 21 and Firefox 15, DOM error 12
// is thrown if the fragment doesn‘t begin with <
if (selector[0] == ‘<‘ && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// If there‘s a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// If it‘s a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// If a function is given, call it when the DOM is ready
else if (isFunction(selector)) return $(document).ready(selector)
// If a Zepto collection is given, just return it
else if (zepto.isZ(selector)) return selector
else {
// normalize array if an array of nodes is given
if (isArray(selector)) dom = compact(selector)
// Wrap DOM nodes.
else if (isObject(selector))
dom = [selector], selector = null
// If it‘s a html fragment, create nodes from it
else if (fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
// If there‘s a context, create a collection on that context first, and select
// nodes from there
else if (context !== undefined) return $(context).find(selector)
// And last but no least, if it‘s a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
}
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}

// `$` will be the base `Zepto` object. When calling this
// function just call `$.zepto.init, which makes the implementation
// details of selecting nodes and creating Zepto collections
// patchable in plugins.
$ = function(selector, context){
return zepto.init(selector, context) //$("a:alilink", $("body"))其实就是zepto.init("a:alilink", $("body"))
}

//把source上的数据拓展到target上
function extend(target, source, deep) {
for (key in source)
//查看是否需要深度拓展,深度拓展完全产生新对象,因此改变source就不会改变target的了
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
//如果source的key是对象,而target对应key不是,那就给他创建新对象
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
//同理,数组也一样
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
//深度拓展的话继续看当前key下是否有东西,有就继续拓展下去
extend(target[key], source[key], deep)
}
//把每个key的值复制过去,非深度拓展只复制对象的基本类型,对象仍属于原来的引用。 这里没有判断因此source上的属性会覆盖掉target上的。
else if (source[key] !== undefined) target[key] = source[key]
}

// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1) //arguments包含了所有实参,从第二个开始复制
if (typeof target == ‘boolean‘) { //传进来的第一个参数决定是不是要深度拓展
deep = target
target = args.shift() //把target对象从参数数组拿出来,给到target,剩下的就作为source对象了
}
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}

// `$.zepto.qsa` is Zepto‘s CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overriden in plugins.
zepto.qsa = function(element, selector){
var found,
//从第一个字符判断是否选择id 类
maybeID = selector[0] == ‘#‘,
maybeClass = !maybeID && selector[0] == ‘.‘,
nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
isSimple = simpleSelectorRE.test(nameOnly)
return (isDocument(element) && isSimple && maybeID) ?
//如果element是document,是简单选择器,可能为id的情况,直接查id,没有找到返回空数组
( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
//判断element是不是节点元素或者document,都不是返回空数组
(element.nodeType !== 1 && element.nodeType !== 9) ? [] :
slice.call(
isSimple && !maybeID ?
maybeClass ? element.getElementsByClassName(nameOnly) : // If it‘s simple, it could be a class
element.getElementsByTagName(selector) : // Or a tag
element.querySelectorAll(selector) // Or it‘s not simple, and we need to query all
)
}

//从nodes集合中过滤,只包含匹配selector的集合
function filtered(nodes, selector) {
return selector == null ? $(nodes) : $(nodes).filter(selector)
}

//查看浏览器有没有原生的contain方法
$.contains = document.documentElement.contains ?
function(parent, node) {
//有原生contains方法的话,如果两者不相等,并且包含就返回TRUE
return parent !== node && parent.contains(node)
} :
//没有原生方法的话,循环判断node的父元素是否等于parent,一直到最顶部 null 才停
function(parent, node) {
while (node && (node = node.parentNode))
if (node === parent) return true
return false
}

//判断arg是否为函数,是的话作为函数调用(payload可以携带相关的信息数据),不是则作为值
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}

//设置value为null,或者undefined的话,就执行删除属性操作,有值的话就设置属性值
function setAttribute(node, name, value) {
value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
}

// access className property while respecting SVGAnimatedString
function className(node, value){
var klass = node.className || ‘‘,
svg = klass && klass.baseVal !== undefined

if (value === undefined) return svg ? klass.baseVal : klass
svg ? (klass.baseVal = value) : (node.className = value)
}

// "true" => true
// "false" => false
// "null" => null
// "42" => 42
// "42.5" => 42.5
// "08" => "08"
// JSON => parse if valid
// String => self
//反序列话,变回原来的类型
function deserializeValue(value) {
var num
try {
return value ?
value == "true" ||
( value == "false" ? false :
value == "null" ? null :
!/^0/.test(value) && !isNaN(num = Number(value)) ? num :
//判断是不是JSON对象,\[是判断是否以[开头是对于[{a: 1,b: 2}]数组里放对象情况
/^[\[\{]/.test(value) ? $.parseJSON(value) :
value )
: value
} catch(e) {
return value
}
}

//把方法拓展到zepto对象上去
$.type = type
$.isFunction = isFunction
$.isWindow = isWindow
$.isArray = isArray
$.isPlainObject = isPlainObject

$.isEmptyObject = function(obj) {
var name
//判断有没有属性在里面,哪怕一个就返回false了,为空对象
for (name in obj) return false
return true
}

$.inArray = function(elem, array, i){
//判断elem是否在数组里,i指的是默认开始找的地方
return emptyArray.indexOf.call(array, elem, i)
}

$.camelCase = camelize
$.trim = function(str) {
return str == null ? "" : String.prototype.trim.call(str)
}

// plugin compatibility
//那些拓展模块中用到
$.uuid = 0
$.support = { }
$.expr = { }

//遍历elements,把每个element放到callback处理
$.map = function(elements, callback){
var value, values = [], i, key
//如果是数组或者nodeList,遍历它们,执行结果为null和undefined会被过滤掉
if (likeArray(elements))
for (i = 0; i < elements.length; i++) {
value = callback(elements[i], i)
if (value != null) values.push(value)
}
//如果是对象的话
else
for (key in elements) {
value = callback(elements[key], key)
if (value != null) values.push(value)
}
//复制返回一个新的数组,不会影响原来的
return flatten(values)
}

$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
//和上面map的区别是,这里将每个元素作为上下文来运行callback,并且把索引和对应值传进去,返回false会退出循环
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
}

//暴露过滤方法,返回在callback执行结果为TRUE的元素组成的新数组
$.grep = function(elements, callback){
return filter.call(elements, callback)
}

if (window.JSON) $.parseJSON = JSON.parse

// Populate the class2type map
$.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase()
})

// Define methods that will be available on all
// Zepto collections
//给zepto对象拓展各种方法
$.fn = {
// Because a collection acts like an array
// copy over these useful array functions.
forEach: emptyArray.forEach,
reduce: emptyArray.reduce,
push: emptyArray.push,
sort: emptyArray.sort,
indexOf: emptyArray.indexOf,
concat: emptyArray.concat,

// `map` and `slice` in the jQuery API work differently
// from their array counterparts
//在zepto的Dom方法里也搞一份map
map: function(fn){
return $($.map(this, function(el, i){ return fn.call(el, i, el) }))
},
//复制集合,返回一个zepto对象
slice: function(){
return $(slice.apply(this, arguments))
},

ready: function(callback){
// need to check if document.body exists for IE as that browser reports
// document ready when it hasn‘t yet created the body element
if (readyRE.test(document.readyState) && document.body) callback($)
//DOMContentLoaded事件当Dom加载完成就触发,不需要等待图片,脚本等加载完成,在load之前触发,load需要等待页面完全加载完
else document.addEventListener(‘DOMContentLoaded‘, function(){ callback($) }, false)
return this
},
get: function(idx){
//没有传入参数就返回包含所有元素集合转化为数组,当参数为负数的时候,通过加总长度变为获取倒数元素
return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
},
toArray: function(){ return this.get() },
//获取当前集合的长度
size: function(){
return this.length
},
remove: function(){
//将当前集合每个元素从Dom删除,判断是否存在父节点,再调用原生removeChild()方法,如果是document的话直接把document对象放数组返回
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
},
each: function(callback){
//every方法测试当前集合是否都通过callback测试,都通过返回TRUE。
emptyArray.every.call(this, function(el, idx){
//如果执行过程中有callback显式返回false(不包括0 null undefined),会退出循环
return callback.call(el, idx, el) !== false
})
return this
},
//过滤当前集合,返回结果为TRUE的
filter: function(selector){
//如果selector是函数的话,第一次执行this.not(selector)返回函数运行结果为falsy(0,false,null,undefined)的集合,第二次调用this.not(this.not(selector))相当于不要第一次返回的集合,要剩下的那些。
if (isFunction(selector)) return this.not(this.not(selector))
return $(filter.call(this, function(element){
return zepto.matches(element, selector)
}))
},
//把selector选择到的元素追加到当前集合,会去掉重复的
add: function(selector,context){
return $(uniq(this.concat($(selector,context))))
},
//判断当前集合中的第一个元素this[0]是否匹配selector
is: function(selector){
return this.length > 0 && zepto.matches(this[0], selector)
},
//从当前集合筛选出不在选中范围内的集合
not: function(selector){
var nodes=[]
//如果selector为函数并且可以通过call存在的话,把执行结果为false的添加到nodes里
if (isFunction(selector) && selector.call !== undefined)
this.each(function(idx){
if (!selector.call(this,idx)) nodes.push(this)
})
else {
//excludes 表示的是被selector选中的集合,如果是字符串的话,直接filter过滤出来
var excludes = typeof selector == ‘string‘ ? this.filter(selector) :
//如果是nodeList的话,直接复制当前nodeList,item方法只有函数上才有,数组没有,用来排除selector为数组情况。以上情况都不是那就作为选择器使用
(likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
this.forEach(function(el){
//遍历集合,如果没有出现在excludes里,就表示该元素没有没选中,添加到nodes数组中,最后返回nodes集合
if (excludes.indexOf(el) < 0) nodes.push(el)
})
}
return $(nodes)
},
//过滤当前集合,筛选出包含selector的集合
has: function(selector){
return this.filter(function(){
return isObject(selector) ?
//如果selector是对象(node)的话,就使用contain查找当前集合是否包含selector
$.contains(this, selector) :
//如果不是对象,就是用find查找selector
$(this).find(selector).size()
})
},
//这里把-1单独判断是因为slice(-1)可以取最后一个,不需要第二个参数end。而 + idx + 1 是因为倒数取值情况slice(-3,-2), + -3 + 1 = -2,这里的第一个加号是用来转化为数字类型,避免用户eq("1")
eq: function(idx){
return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
},
//获取第一个集合,判断el是否为zepto对象(isObject如果是zepto对象会返回false),是的话直接返回,不是弄成zepto对象再返回
first: function(){
var el = this[0]
return el && !isObject(el) ? el : $(el)
},
//从当前集合中获取最后一个集合
last: function(){
var el = this[this.length - 1]
return el && !isObject(el) ? el : $(el)
},
//查找匹配selector的元素
find: function(selector){
var result, $this = this
//如果selector不存在,返回空数组
if (!selector) result = []
//如果selector为对象(Dom或者zepto对象),遍历selector,在当前集合($this)上查看是否包含任意selector,有的话就会返回TRUE,并记录该selector到result里面
else if (typeof selector == ‘object‘)
result = $(selector).filter(function(){
var node = this
//some回调第一个参数指向当前对象,因此为parent,如果里面$.contains(parent, node)返回TRUE,那么some也会返回TRUE
return emptyArray.some.call($this, function(parent){
return $.contains(parent, node)
})
})
//如果当前集合只有一个子集,那么直接就在这个子集上查找selector
else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
//如果当前集合有多个子集,则使用map遍历
else result = this.map(function(){ return zepto.qsa(this, selector) })
return result
},
//查找第一个匹配到的父级元素
closest: function(selector, context){
var node = this[0], collection = false
//如果是元素节点或者zepto集合,那么转化为zepto对象并且放到collection
if (typeof selector == ‘object‘) collection = $(selector)
//node存在,判断collection是否为false,如果node不在collection集合里,或者node里没有匹配的selector,进行下一步判断,如果前面判断失败,表示查找完成,返回$(node)
while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
//node不等于给定的上下文环境,并且node不为document,并且node.parentNode存在的话,把node.parentNode 赋给node,一层一层把父元素拿去判断。如果context和node相等的话返回这个node
node = node !== context && !isDocument(node) && node.parentNode
return $(node)
},
//返回所有父元素,如果提供了selector,返回匹配的所有父元素
parents: function(selector){
var ancestors = [], nodes = this
//一直循环到最顶层,它的长度为0,停止
while (nodes.length > 0)
//遍历所有父级,如果父级还有父元素,并且不是document,没有和ancestors里重复,就把nodes指向上一级,继续遍历
nodes = $.map(nodes, function(node){
if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
ancestors.push(node)
return node
}
})
//如果提供了selector的,过滤出符合条件的
return filtered(ancestors, selector)
},
//获取直接父元素,通过plunk遍历集合获取父元素节点
parent: function(selector){
return filtered(uniq(this.pluck(‘parentNode‘)), selector)
},
//获取当前集合的直接子元素们
children: function(selector){
return filtered(this.map(function(){ return children(this) }), selector)
},
//获取当前集合的子节点,childNodes是会返回text节点和注释节点的,children属性则不返回
contents: function() {
return this.map(function() { return slice.call(this.childNodes) })
},

siblings: function(selector){
//这一层过滤是如果提供了selector,只返回符合条件的sibling
return filtered(this.map(function(i, el){
//这一层过滤是通过children(el.parentNode)获取所有子元素,然后剔除掉当前那个
return filter.call(children(el.parentNode), function(child){ return child!==el })
}), selector)
},
//通过把innerHTML设置为空清除掉当前节点下的Dom
empty: function(){
return this.each(function(){ this.innerHTML = ‘‘ })
},
// `pluck` is borrowed from Prototype.js
pluck: function(property){
//遍历当前集合获取属性值
return $.map(this, function(el){ return el[property] })
},
show: function(){
return this.each(function(){
//把当前集合的每个元素作为上下文,去判断display属性,被内联样式设置为none的话恢复显示
this.style.display == "none" && (this.style.display = ‘‘)
//如果被CSS样式设置为none的,通过内联样式设置回默认值
if (getComputedStyle(this, ‘‘).getPropertyValue("display") == "none")
this.style.display = defaultDisplay(this.nodeName)
})
},
//通过before方法把新内容添加到当前集合元素前,然后把当前的移除掉
replaceWith: function(newContent){
return this.before(newContent).remove()
},
//把当前集合中的每个元素用structure包裹起来
wrap: function(structure){
var func = isFunction(structure)
//如果当前集合有子元素且没有传函数进来的话,缓存structure,注意get(0)返回的是原生的Dom
if (this[0] && !func)
var dom = $(structure).get(0),
clone = dom.parentNode || this.length > 1
//以当前集合中的每个子集为上下文调用wrapAll(),如果传进来的structure是函数,那么就调用这个函数,使用它返回的结构作为structure。如果不是判断clone
//clone存在表示传进来的structure可能为当前页面的dom,或者需要wrap的元素超过一个,因此需要深度克隆dom作为wrapAll的structure,否则就是用dom
return this.each(function(index){
$(this).wrapAll(
func ? structure.call(this, index) :
clone ? dom.cloneNode(true) : dom
)
})
},
//把当前集合全部放到一个结构里
wrapAll: function(structure){
if (this[0]) {
//把structure转为zepto后插入到第一个匹配到的子集前面,然后把structure覆盖为最里面的元素,再把this把当前集合全部移进到最里面
$(this[0]).before(structure = $(structure))
var children
// drill down to the inmost element
while ((children = structure.children()).length) structure = children.first()
$(structure).append(this)
}
return this
},
//把当前集合的内容(不包含Dom结构)放到structure里
wrapInner: function(structure){
var func = isFunction(structure)
return this.each(function(index){
//使用contents()只获取每个元素里的内容,如果内容存在就把内容依次放到dom里,没内容的话就把dom移进去。
//因为每一次dom都会被移动,也就是先把structure移到第一个匹配到的子集里,然后移动第二个,第三个,因此最后的位置就在最后一个匹配到的子集元素里
var self = $(this), contents = self.contents(),
dom = func ? structure.call(this, index) : structure
contents.length ? contents.wrapAll(dom) : self.append(dom)
})
},
//把直接父元素灭了,原理就是用当前集合的children作为新内容替换掉自己
unwrap: function(){
this.parent().each(function(){
$(this).replaceWith($(this).children())
})
return this
},
//克隆节点都是深度克隆,所有子节点都被复制
clone: function(){
return this.map(function(){ return this.cloneNode(true) })
},
hide: function(){
return this.css("display", "none")
},
toggle: function(setting){
return this.each(function(){
var el = $(this)
//判断传进来的setting,如果是undefined则判断当前是否隐藏,用来下一步控制显隐
//如果不是undefined,则判断setting的真假值,进而判断显隐性。
;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
})
},
//调用原生的属性方法previousElementSibling,nextElementSibling,再过滤出匹配的
prev: function(selector){ return $(this.pluck(‘previousElementSibling‘)).filter(selector || ‘*‘) },
next: function(selector){ return $(this.pluck(‘nextElementSibling‘)).filter(selector || ‘*‘) },
html: function(html){
//0 in argument判断是否传入了参数,如果传入了的话,把原本的HTML结构清空,如果传进来的HTML结构,那么就用它替代旧的,如果是对原来结构的调整函数,那就调用这个函数
return 0 in arguments ?
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
}) :
//没有传进参数的话,就看当前集合有没有东西,有的化返回第一个子元素的结构,没有的话返回null
(0 in this ? this[0].innerHTML : null)
},
text: function(text){
return 0 in arguments ?
this.each(function(idx){
//这里和html方法不同之处在于保存funcArg的结果作为新的内容,‘‘+newText用来转化为字符串
var newText = funcArg(this, text, idx, this.textContent)
this.textContent = newText == null ? ‘‘ : ‘‘+newText
}) :
(0 in this ? this[0].textContent : null)
},
//读取或设置Dom 的attitude
attr: function(name, value){
var result
return (typeof name == ‘string‘ && !(1 in arguments)) ?
//如果name为字符串并且只传了一个参数的话,表示读取attribute的值,那么判断当前集合是不是是否存在子集,如果不存在或者第一个子集不是元素类型的话,返回undefined
(!this.length || this[0].nodeType !== 1 ? undefined :
//如果getAttribute获取返回null,但是name的确在这个元素上,那么表示这个attribute是直接定义上去的,例如element.num = 1 ,需要通过this[0][name]来取值
(!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
) :
//这里是进行attribute设置操作
this.each(function(idx){
if (this.nodeType !== 1) return
//如果传进来的是对象,那么就遍历该对象,把属性设置到当前集合每个元素上
if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
//最后一种情况是处理第二个传入的参数,在当前元素上调用该方法把返回结果作为name的值
else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
})
},
removeAttr: function(name){
//原理就是使用上面提到的setAttribute,当没有传入值的时候为移除属性
return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) })
},
prop: function(name, value){
//修正有可能写错的属性名
name = propMap[name] || name
//判断有没有传入value
return (1 in arguments) ?
this.each(function(idx){
//传了value表示设置操作,调用funcArg,把结果作为该属性值
this[name] = funcArg(this, value, idx, this[name])
}) :
//没有传入第二个参数表示进行读取操作
(this[0] && this[0][name])
},
//读取或者设置带有data-前缀的attribute
data: function(name, value){
//把那么转化为-xx-xx的小写模式,但是这里的‘data-‘后面多了一行,变成data--mime了
//如果你使用的是包含了data模块的zepto,那么这个方法就被覆盖了,那就不是简单滴存储到元素上了
//data模块支持存储任意对象
var attrName = ‘data-‘ + name.replace(capitalRE, ‘-$1‘).toLowerCase()

var data = (1 in arguments) ?
this.attr(attrName, value) :
this.attr(attrName)
//如果是获取值的话,返回原本的数据类型
return data !== null ? deserializeValue(data) : undefined
},
//设置或者获取表单控件的值
val: function(value){
//如果传入了参数表示设置控件的值
return 0 in arguments ?
this.each(function(idx){
this.value = funcArg(this, value, idx, this.value)
}) :
//没有给值的话也是返回第一个元素的值,需要判断是不是多选的,如果是就把选择的值放在数组里返回
(this[0] && (this[0].multiple ?
$(this[0]).find(‘option‘).filter(function(){ return this.selected }).pluck(‘value‘) :
this[0].value)
)
},
//获取元素相对于document的位置
offset: function(coordinates){
//如果提供了上,左位置,就会设置该位置
if (coordinates) return this.each(function(index){
var $this = $(this),
coords = funcArg(this, coordinates, index, $this.offset()),
//parentOffset为当前元素第一个position为“relative”, “absolute”或“fixed”的元素,因为常用这些来限制子元素为absolute的活动范围
parentOffset = $this.offsetParent().offset(),
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
//如果是static,设置为relative来移动,不影响其他元素
if ($this.css(‘position‘) == ‘static‘) props[‘position‘] = ‘relative‘
$this.css(props)
})
if (!this.length) return null
//getBoundingClientRect返回一个对象,其中包含了left、right、top、bottom四个属性,分别对应了该元素的左上角和右下角相对于浏览器窗口(viewport)左上角的距离
var obj = this[0].getBoundingClientRect()
return {
//left和top加上滚动了的距离
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
}
},
css: function(property, value){
//如果只传入property的情况
if (arguments.length < 2) {
var element = this[0], computedStyle = getComputedStyle(element, ‘‘)
if(!element) return
//如果property为字符串,那么先看内联样式有没有,这里要使用驼峰形式。没有则获取最终应用到元素上的样式,这里不用驼峰
if (typeof property == ‘string‘)
return element.style[camelize(property)] || computedStyle.getPropertyValue(property)
//传入property数组的话,返回一个props对象,把每个属性查询结果存放在里面
else if (isArray(property)) {
var props = {}
$.each(property, function(_, prop){
props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop))
})
return props
}
}

var css = ‘‘
if (type(property) == ‘string‘) {
//如果value为falsy值,那么就从style里删除掉这个样式,removeProperty不能用驼峰形式
if (!value && value !== 0)
this.each(function(){ this.style.removeProperty(dasherize(property)) })
else
//不是删除的话,就转化为样式写法,background-color: red; 要添加px后缀的就添加上
css = dasherize(property) + ":" + maybeAddPx(property, value)
} else {
//如果property是对象的话,遍历它,如果值为falsy切不为0的话,就去掉,否者也转化成样式
for (key in property)
if (!property[key] && property[key] !== 0)
this.each(function(){ this.style.removeProperty(dasherize(key)) })
else
css += dasherize(key) + ‘:‘ + maybeAddPx(key, property[key]) + ‘;‘
}
//一次性把CSS样式添加到各元素style里,注意是+=不是=,不然会覆盖掉原有的
return this.each(function(){ this.style.cssText += ‘;‘ + css })
},
index: function(element){
//获取元素位置,如果有element的话,返回第一个element在当前集合位置,没有的话返回当前集合第一个元素相对于兄弟姐妹的位置
return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
},
hasClass: function(name){
if (!name) return false
return emptyArray.some.call(this, function(el){
//这里的this是classRE(name)生成的对应类名正则,因为some方法第二个参数可以指定callback的this。
//集合中只要有一个含有该类就返回TRUE
return this.test(className(el))
}, classRE(name))
},
addClass: function(name){
if (!name) return this
return this.each(function(idx){
if (!(‘className‘ in this)) return
classList = []
var cls = className(this), newName = funcArg(this, name, idx, cls)
//把要添加的类逐个进行判断,看原来有没有,没有才添加到classList数组
newName.split(/\s+/g).forEach(function(klass){
if (!$(this).hasClass(klass)) classList.push(klass)
}, this)
//添加新类的时候判断原来有没有类,有的话用空格分开,classList数组转化为用空格分开的字符串
classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
})
},
removeClass: function(name){
return this.each(function(idx){
if (!(‘className‘ in this)) return
if (name === undefined) return className(this, ‘‘)
classList = className(this)
funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
//通过replace把匹配到的类名替换为空,后面再把多余的空格去掉
classList = classList.replace(classRE(klass), " ")
})
className(this, classList.trim())
})
},
toggleClass: function(name, when){
if (!name) return this
return this.each(function(idx){
var $this = $(this), names = funcArg(this, name, idx, className(this))
names.split(/\s+/g).forEach(function(klass){
//如果提供了when的话,当它的值为真才添加类,否则移除。如果when为undefined,那么就看目前有没有这个类,有就移除,没有就添加
(when === undefined ? !$this.hasClass(klass) : when) ?
$this.addClass(klass) : $this.removeClass(klass)
})
})
},
//判断向下滚动了多少
scrollTop: function(value){
if (!this.length) return
//判断有没有原生的scrollTop属性
var hasScrollTop = ‘scrollTop‘ in this[0]
//没有传入value的话表示读取,返回scrollTop(body的情况),没有的话返回pageYOffset(window)
if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset
//同样如果是设置位置的话,支持scrollTop的用它,不支持的话使用window的scrollTo方法
return this.each(hasScrollTop ?
function(){ this.scrollTop = value } :
function(){ this.scrollTo(this.scrollX, value) })
},
//判断向右滚了多少
scrollLeft: function(value){
if (!this.length) return
var hasScrollLeft = ‘scrollLeft‘ in this[0]
if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset
return this.each(hasScrollLeft ?
function(){ this.scrollLeft = value } :
function(){ this.scrollTo(value, this.scrollY) })
},
//获取相对于非static祖先元素的位置
position: function() {
if (!this.length) return
//获取当前集合第一个元素
var elem = this[0],
// Get *real* offsetParent
offsetParent = this.offsetParent(),
// Get correct offsets
offset = this.offset(),
parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()

// Subtract element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat( $(elem).css(‘margin-top‘) ) || 0
offset.left -= parseFloat( $(elem).css(‘margin-left‘) ) || 0

// Add offsetParent borders
//因为现在的top和left是相对左上角,因此要减掉边距
parentOffset.top += parseFloat( $(offsetParent[0]).css(‘border-top-width‘) ) || 0
parentOffset.left += parseFloat( $(offsetParent[0]).css(‘border-left-width‘) ) || 0

// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
}
},
offsetParent: function() {
return this.map(function(){
//获取每个元素的offsetParent,没有的话表示为document.body,因为body的offsetParent为null
var parent = this.offsetParent || document.body
//如果不为根节点,并且它的布局方式是默认的static,那么就用它的offsetParent覆盖当前parent,继续向上判断,直到非static的祖先
while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
parent = parent.offsetParent
return parent
})
}
}

// for now
$.fn.detach = $.fn.remove

// Generate the `width` and `height` functions
;[‘width‘, ‘height‘].forEach(function(dimension){
//把首字母进行大写
var dimensionProperty =
dimension.replace(/./, function(m){ return m[0].toUpperCase() })
//把width,height方法拓展到$.fn里
$.fn[dimension] = function(value){
var offset, el = this[0]
// 没有传入值表示查询,如果是window对象,返回innerWidth(Height)的值,浏览器窗口显示高宽
if (value === undefined) return isWindow(el) ? el[‘inner‘ + dimensionProperty] :
//如果是document,返回scrollWidth(Height),当前文档的高宽
isDocument(el) ? el.documentElement[‘scroll‘ + dimensionProperty] :
//其他元素调用offset方法取值
(offset = this.offset()) && offset[dimension]
//传入值的话把当前集合每个元素都用css方法设置宽高
else return this.each(function(idx){
el = $(this)
el.css(dimension, funcArg(this, value, idx, el[dimension]()))
})
}
})

function traverseNode(node, fun) {
//先对node本身操作一遍,判断是不是script标签,有的话需要执行里面的脚本
fun(node)
//再遍历后代节点,看里面有没script标签,整个node检查一遍
for (var i = 0, len = node.childNodes.length; i < len; i++)
traverseNode(node.childNodes[i], fun)
}

// Generate the `after`, `prepend`, `before`, `append`,
// `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
adjacencyOperators.forEach(function(operator, operatorIndex) {
//adjacencyOperators = [ ‘after‘, ‘prepend‘, ‘before‘, ‘append‘ ], prepend, append求余结果为0
var inside = operatorIndex % 2 //=> prepend, append

$.fn[operator] = function(){
// arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
var argType, nodes = $.map(arguments, function(arg) {
argType = type(arg)
//对参数进行判断,如果是HTML字符串,标签名就先转化为Dom结构
return argType == "object" || argType == "array" || arg == null ?
arg : zepto.fragment(arg)
}),
//copyByClone判断当前集合在页面上是否为多个,多个的话nodes不能直接移动,要克隆,才能给集合中每个元素都进行操作
parent, copyByClone = this.length > 1
//如果没有传参数,返回当前对象
if (nodes.length < 1) return this
//_是各元素在当前集合索引,target是各元素对象
return this.each(function(_, target){
//prepend, append为1,after,before为0,append和append用自己作为父元素,通过覆盖target为子元素来实现insertBefore
parent = inside ? target : target.parentNode

// convert all methods to a "before" operation
target = operatorIndex == 0 ? target.nextSibling : //0是after操作,insertBefore方法的话就是插入到目标元素的相邻下一个元素的前面
operatorIndex == 1 ? target.firstChild : //1是prepend操作,insertBefore方法的话就是插入到目标元素所在集合的第一个元素前面
operatorIndex == 2 ? target :
null //append操作,insertBefore方法第二个参数为null的话,默认插到当前集合最后一个

var parentInDocument = $.contains(document.documentElement, parent)

nodes.forEach(function(node){
//如果是要对多个元素进行操作append等操作
if (copyByClone) node = node.cloneNode(true)
//如果当前元素没有parent的话,就退出并且删除该元素,执行不了insertBefore方法
else if (!parent) return $(node).remove()

//到这里就进行插入操作
parent.insertBefore(node, target)
//如果是在script里面插入内容,那么要以window为上下文执行里面的内容
if (parentInDocument) traverseNode(node, function(el){
if (el.nodeName != null && el.nodeName.toUpperCase() === ‘SCRIPT‘ &&
(!el.type || el.type === ‘text/javascript‘) && !el.src)
window[‘eval‘].call(window, el.innerHTML)
})
})
})
}

// after => insertAfter
// prepend => prependTo
// before => insertBefore
// append => appendTo
$.fn[inside ? operator+‘To‘ : ‘insert‘+(operatorIndex ? ‘Before‘ : ‘After‘)] = function(html){
//在里面反向调用上面的方法,把传入的HTML作为当前集合,把当前集合作为传入的HTML
$(html)[operator](this)
return this
}
})

zepto.Z.prototype = $.fn

// Export internal API functions in the `$.zepto` namespace
zepto.uniq = uniq
zepto.deserializeValue = deserializeValue
$.zepto = zepto

return $
})()

//把Zepto和$拓展到全局对象上,如果$被占用了,就用Zepto吧
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

 

//接下来这部分是事件处理模块
;(function($){
//定义该模块内部使用的变量函数等
var _zid = 1, undefined,
slice = Array.prototype.slice,
isFunction = $.isFunction,
isString = function(obj){ return typeof obj == ‘string‘ },
//handles的大概结构:
//handles = {
// 元素的zid:[handler对象1,handler对象2 ...]
//}
handlers = {},
specialEvents={},
focusinSupported = ‘onfocusin‘ in window,
//不支持冒泡的事件
focus = { focus: ‘focusin‘, blur: ‘focusout‘ },
hover = { mouseenter: ‘mouseover‘, mouseleave: ‘mouseout‘ }

specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = ‘MouseEvents‘

//获取/赋予元素一个标示符
function zid(element) {
return element._zid || (element._zid = _zid++)
}

//查找元素的事件处理函数
function findHandlers(element, event, fn, selector) {
event = parse(event)
if (event.ns) var matcher = matcherFor(event.ns)
return (handlers[zid(element)] || []).filter(function(handler) {
return handler
//如果event.e不为falsy值,判断当前处理函数的事件类型是否等于传入的事件类型,其实就是想执行后面的判断
&& (!event.e || handler.e == event.e)
//判断时间的命名空间时候相同
&& (!event.ns || matcher.test(handler.ns))
//判断fn是否指向同一个,因为函数是引用类型,你在上面加点东西,其他对该函数的引用也能看到
&& (!fn || zid(handler.fn) === zid(fn))
//判断选择器是否相同
&& (!selector || handler.sel == selector)
})
}

//解析事件为数组
function parse(event) {
//先转化为字符串再切割为数组
var parts = (‘‘ + event).split(‘.‘)
//返回一个对象,包含事件类型,命名空间
return {e: parts[0], ns: parts.slice(1).sort().join(‘ ‘)}
}

//生成命名空间的正则
function matcherFor(ns) {
return new RegExp(‘(?:^| )‘ + ns.replace(‘ ‘, ‘ .* ?‘) + ‘(?: |$)‘)
}

//设置捕获阶段,如果是focus和blur事件,则设为捕获阶段,来达到冒泡的目的
function eventCapture(handler, captureSetting) {
return handler.del &&
(!focusinSupported && (handler.e in focus)) ||
!!captureSetting
}

//不支持mouseenter和mouseleave的话,就用mouseover和mouseout来替代
//如果支持focusin事件的话,就用focusin和focusout替代focus和blur
function realEvent(type) {
return hover[type] || (focusinSupported && focus[type]) || type
}

//处理事件监听
function add(element, events, fn, data, selector, delegator, capture){
var id = zid(element), set = (handlers[id] || (handlers[id] = []))
//同时添加多个事件支持,空格切割为数组
events.split(/\s/).forEach(function(event){
//如果是ready事件,就添加到document上
if (event == ‘ready‘) return $(document).ready(fn)
var handler = parse(event)
//把数据保存到该handler上去
handler.fn = fn
//selector 可以选择是不是只执行一次事件处理函数
handler.sel = selector
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
//relatedTarget 是指相对于触发事件元素的另一个元素,因为鼠标无非从一个元素移动到另一个元素上,mouseover和mouseout才有这个事件属性,因此mouseenter, mouseleave需要模仿
var related = e.relatedTarget
//第一种情况!related表示为mouseenter, mouseleave事件,执行事件处理函数
//第二种情况如果related有值,并且不是元素自己,也不是触发元素的子节点,那也执行事件处理
if (!related || (related !== this && !$.contains(this, related)))
return handler.fn.apply(this, arguments)
}
handler.del = delegator
var callback = delegator || fn
handler.proxy = function(e){
e = compatible(e)
//判断有没有调用过stopImmediatePropagation方法,如果一个元素对一个事件添加多个事件监听,那么stopImmediatePropagation可以禁止剩下那些事件处理函数调用
//并且也不会冒泡了,代理不了,所以返回
if (e.isImmediatePropagationStopped()) return
//把当前的事件对象信息保存到e上去,因为这些事件对象也是引用的,例如 MouseEvent
e.data = data
var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
//如果事件处理函数返回false的时候,阻止默认行为和冒泡
if (result === false) e.preventDefault(), e.stopPropagation()
return result
}
//设置这个handler是第几个,因为length是之前set的长度,刚好就

以上是关于zepto源码的主要内容,如果未能解决你的问题,请参考以下文章

zepto源码分析-代码结构转载

zepto源码

zepto源码--核心方法7(管理包装集)--学习笔记

读Zepto源码之样式操作

zepto源码解读——zpeto.init()——

读zepto源码之工具函数