zepto源码研究 - zepto.js-4(常用的工具)
$.each:
/** * 以集合每一个元素作为上下文,来执行回调函数 * @param elements * @param callback * @returns {*} */ $.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可以是数组或者对象,如果是对象,则会将其原型里面的属性也遍历出来,最后返回elments本身,如果回调函数返回了false,则终止循环
remove:
/** * 删除元素集 * 原理 parentNode.removeChild * @returns {*} */ remove: function(){ //遍历到其父元素 removeChild return this.each(function(){ if (this.parentNode != null) this.parentNode.removeChild(this) }) },
这里的重点是parentNode.removeChild(this); 如果是没有父节点的这里不会执行。
is:
//返回集合中的第1条记录是否与selector匹配 is: function(selector){ return this.length > 0 && zepto.matches(this[0], selector) }
例如$("#id").is(".hasClass"); 实际就是调用matches进行匹配
not:
//排除集合里满足条件的记录,接收参数为:css选择器,function, dom ,nodeList not: function(selector){ var nodes=[] //当selector为函数时,safari下的typeof nodeList也是function,所以这里需要再加一个判断selector.call !== undefined if (isFunction(selector) && selector.call !== undefined) this.each(function(idx){ //注意这里收集的是selector.call(this,idx)返回结果为false的时候记录 if (!selector.call(this,idx)) nodes.push(this) }) else { //当selector为字符串的时候,对集合进行筛选,也就是筛选出集合中满足selector的记录 var excludes = typeof selector == 'string' ? this.filter(selector) : //当selector为nodeList时执行slice.call(selector),注意这里的isFunction(selector.item)是为了排除selector为数组的情况 //当selector为css选择器,执行$(selector) (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector); this.forEach(function(el){ //筛选出不在excludes集合里的记录,达到排除的目的 if (excludes.indexOf(el) < 0) nodes.push(el) }) } return $(nodes)//由于上面得到的结果是数组,这里需要转成zepto对象,以便继承其它方法,实现链写 },
这里首先判断selector是否为函数,如果是,则遍历执行函数,如果返回false,则push到数组中
如果css选择字符串则调用this.filter(seelctor),这里有个小技巧使得not和filter相互调用
如果是数组或对象,则直接返回$(他自己)
最后遍历this,只要不是在excludes里面的都是满足条件的
has:
/* 接收node和string作为参数,给当前集合筛选出包含selector的集合 isObject(selector)是判断参数是否是node,因为typeof node == 'object' 当参数为node时,只需要判读当前记当里是否包含node节点即可 当参数为string时,则在当前记录里查询selector,如果长度为0,则为false,filter函数就会过滤掉这条记录,否则保存该记录 */ has: function(selector){ return this.filter(function(){ return isObject(selector) ? $.contains(this, selector) : $(this).find(selector).size() }) },
重点提示下:这里的this如果也满足selector,则最后返回的结果集里也会有this
show:
/**
* 获取元素的默认display属性
* 是为了兼容什么?
* @param nodeName
* @returns {*}
*/
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",设置成blaock,即隐藏-显示
display == "none" && (display = "block")
elementDisplay[nodeName] = display //TODO:缓存元素的默认display属性,缓存干嘛?
}
return elementDisplay[nodeName]
}
/** * 展示 * @returns {*} */ show: function(){ return this.each(function(){ //清除内联样式display="none" this.style.display == "none" && (this.style.display = '') //计算样式display为none时,重赋显示值 if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) //defaultDisplay是获取元素默认display的方法 }) },
this.style.display = ' '是清除元素的style里面的display样式,恢复元素的默认样式。
getComputedStyle(dom)能获取dom的所有css样式,第二个参数可以不传
getComputedStyle(dom,":after")能获取dom的:after伪类的所有css样式(这里只谈移动端)
详情请看:http://www.zhangxinxu.com/wordpress/2012/05/getcomputedstyle-js-getpropertyvalue-currentstyle/
如果清除了style里面的display属性还是无效的话,直接显式的给display赋默认值,defaultDisplay的内部实际是在document里面新添加一个元素,来获取默认值的。
css:
/** * 读写样式 写:内联样式 读:计算样式 * 原理 读:elment[style]/getComputedStyle, 写 this.style.cssText 行内样式设值 * @param property String/Array/Fun * @param value * @returns {*} */ css: function(property, value){ //只有一个传参,读 if (arguments.length < 2) { var computedStyle, element = this[0] if(!element) return //getComputedStyle是一个可以获取当前元素所有最终使用的CSS属性值。返回的是一个CSS样式声明对象([object CSSStyleDeclaration]),只读 //读到计算样式 computedStyle = getComputedStyle(element, '') //设置样式 if (typeof property == 'string')// 字符串 //优先读行内样式,再读计算样式,行内样式级别最高? TODO:似乎有bug,如果设置了!important 呢 return element.style[camelize(property)] || computedStyle.getPropertyValue(property) else if (isArray(property)) { //数组 var props = {} $.each(property, function(_, prop){ //遍历读取每一条样式,存入JSON,返回 props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props } } //如果是写 var css = '' if (type(property) == 'string') { if (!value && value !== 0) //null,undefined时,删掉样式 this.each(function(){ //删除 dasherize是将字符串转换成css属性(background-color格式) this.style.removeProperty(dasherize(property)) }) else //‘-’格式值 + px单位 css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) //是对象时 if (!property[key] && property[key] !== 0) //当property[key]的值为null/undefined,删除属性 this.each(function(){ this.style.removeProperty(dasherize(key)) }) else //‘-’格式值 + px单位 css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' } //设值 //TODO: this.style.cssText += 未考虑去重了 return this.each(function(){ this.style.cssText += ';' + css }) },
判断只传了一个参数:arguments.length < 2 或者 1 in arguments,
流程大致如下:
1:判断参数,如果是一个参数则为只读模式
1.2:如果property为字符串,则 直接获取元素的css属性值
1.3:如果property为数组,则this.each遍历数组,获取每个属性的value值并形成props对象返回
2:若有2个参数,为写模式
2.1:如果property为字符串,则style.cssText+= property:value(这里的处理没有去重,但本人测了一下,这并不影响页面的渲染)
2.2:如果property为对象,则循环遍历property
这里有几个函数要提一下:
camelize() 将带“—”的转为驼峰命名例如“background-color”转为“backgroundColor”,对象属性是必须驼峰命名的。
dasherize() 将“backgroundColor”转为“background-color”,内部实现是用正则表达式匹配
style.removeProperty 删除style中的某个属性
style.setProperty(property,value,priority) 设置style中的某个属性,priority可取“important” ,(不太明白为什么zepto不用这个方法,望高人指教)
maybeAddPx (property,value):根据property类型给value加上“px”后缀
html:
/**
* 处理 arg为函数/值
* 为函数,返回函数返回值
* 为值,返回值
* @param context
* @param arg
* @param idx
* @param payload
* @returns {*}
*/
function funcArg(context, arg, idx, payload) {
return isFunction(arg) ? arg.call(context, idx, payload) : arg
}
/** * 读写元素HTML内容 * 原理 通过innerHTML读内容,append()写内容 * @param html * @returns {*|string|string|string|string|string} */ html: function(html){ return 0 in arguments ? this.each(function(idx){ var originHtml = this.innerHTML //记录原始的innerHTMl //如果参数html是字符串直接插入到记录中, //如果是函数,则将当前记录作为上下文,调用该函数,且传入该记录的索引和原始innerHTML作为参数 $(this).empty().append( funcArg(this, html, idx, originHtml) ) }) : (0 in this ? this[0].innerHTML : null) },
html():不传参数则获取元素的innerHTML,
html("<div></div>"):设置元素的innerHTML
html(function(){....}):可传入方法,返回值设置为元素的innerHTML
这里的funcArg的功能是判断html为函数则以this为上下文,以idx,originHtml为参数执行函数
text:
/** * 读写元素文本内容 * 原理: 通过 textContent 读写文本 * @param text * @returns {*} */ text: function(text){ return 0 in arguments ? this.each(function(idx){ //传参遍历写入 var newText = funcArg(this, text, idx, this.textContent) this.textContent = newText == null ? '' : ''+newText }) : (0 in this ? this[0].textContent : null) //未传参读 },