zepto源码研究 - zepto.js - 5(dom属性管理)
index:
$.fn = {
......
indexOf: emptyArray.indexOf,
}
index: function(element){ //这里的$(element)[0]是为了将字符串转成node,因为this是个包含node的数组 //当不指定element时,取集合中第一条记录在其父节点的位置 //this.parent().children().indexOf(this[0])这句很巧妙,和取第一记录的parent().children().indexOf(this)相同 return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]) },
obj.index(element) : 如果element有,则obj作为一个数组,取element在这个obj中的索引。如果element没有,则此函数的意思是取dom节点所在父类中的索引。
<div id="first" class="test"> <div id="second" class="test"> </div> </div> 测试: $(".test").index($("#second")); 1 $(".test").index($("#second")[0]); 1 $(".second").index(); -1 $("#second").index(); 0
hasClass:
/** * 是否含有指定的类样式 * @param name * @returns {boolean} */ hasClass: function(name){ if (!name) return false //some ES5的新方法 有一个匹配,即返回true 。 return emptyArray.some.call(this, function(el){ //this是classRE(name)生成的正则 return this.test(className(el)) }, classRE(name)) },
array.some(function(curentValue,index,arr){},thisValue),thisValue作为回调函数中的this,若没有指定thisValue,则回调函数中的this为undefined,
className(el) :一般取el.className(svg除外),classRE(name) 生成一个name的正则表达式,
test是RegExp的方法,参数是字符串,返回值是boolean类型。
match是String的方法,参数是正则表达式,返回值是数组。
classRE:
/** * 将参数变为正则表达式 * @param name * @returns {*} */ function classRE(name) { //classCache,缓存正则 //TODO 缓存可以理解,但应该在重复使用第二次时再缓存吧,直接缓存? return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) }
className:
/** * 对SVGAnimatedString的兼容? * @param node * @param value * @returns {*} */ 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) //class设值 }
addClass:
/** * 增加一个或多个类名 * @param name 类名/空格分隔的类名/函数 * @returns {*} */ 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) //修正类名,处理name是函数,SVG动画兼容的情况 //多个类,空格分隔为数组 newName.split(/\s+/g).forEach(function(klass){ if (!$(this).hasClass(klass)) classList.push(klass) }, this) //设值 如果所有的都是重复的,就不会执行 classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) }) },
addClass(name)的name参数可以为函数,newName.split(/\s+/g) :根据空格分为数组,这是一个技巧,这里是先对newName过滤,把重复的class去掉,
然后以字符串的形式连接原来的className并赋值给dom
removeClass:
/** *删除一个或多个类名 同addClass * 原理: className.repalce 替换撒谎年初 * @param name 类名/空格分隔的类名/函数 * @returns {*} */ 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){ //替换删除 classList = classList.replace(classRE(klass), " ") }) className(this, classList.trim()) }) },
这里要删除一个字符串里面的指定的子字符串,可以使用replace(classRE," ")
toggleClass:
/** * 切换类的添加或移除 * 原理 如果存在,即removeClass移除,不存在,即addClass添加 * @param name 类名/空格分隔的类名/函数 * @param when * @returns {*} */ 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可以作为标记,来判断是add还是remove (when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass) : $this.removeClass(klass) })
}) },
attr:
/** * 设置属性 * @param node * @param name * @param value */ function setAttribute(node, name, value) { //value为null/undefined,处理成删除,否则设值 value == null ? node.removeAttribute(name) : node.setAttribute(name, value) } /** * 元素的HTML属性读写 * 读:原理是getAttribute * 写:原理是setAttribute * @param name * @param value * @returns {undefined} */ attr: function(name, value){ var result; //仅有name,且为字符串时,表示读 return (typeof name == 'string' && !(1 in arguments)) ? //$是空的 或里面的元素非元素,返回undefined (!this.length || this[0].nodeType !== 1 ? undefined : //直接用getAttribute(name)读, (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result ) : //否则是写,不管name为对象{k:v},或name value 都存在 this.each(function(idx){ if (this.nodeType !== 1) return //非元素 //如果name为对象,批量设置属性 if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) //处理value为函数/null/undefined的情况 else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) }) },
经整理后如下:
//如果只有一个参数,且name为string类型,则此函数为读模式 if((typeof name == 'string' && !(1 in arguments)) ){ if(!this.length || this[0].nodeType !== 1) return undefined; if((result = this[0].getAttribute(name))){ // 如果dom有这个属性,则返回 return result; }else{ // 如果dom没有这个属性,则从内存中获取并返回 if(name in this[0]) return this[0][name]; } }else{ this.each(function(idx){ if (this.nodeType !== 1) return //非元素 //如果name为对象,批量设置属性 if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) //处理value为函数/null/undefined的情况,这里的funcArg函数在整个zepto中复用了很多次, // 考虑参数为函数的情况在很大方法中出现,setAttribute处理value为null的情况,实际就是removeAttribute else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) }) }
data:
/** * 设置自定义数据 * 注意与jQuery的区别,jQuery可以读写任何数据类型。这里原理是H5的data-,或直接setAttribute/getAttribute,只能读写字符串 * @param name * @param value * @returns {*} */ /*getAttribute/setAttribute可以操作所有的dataset内容,dataset内容只是attribute的一个子集, 特殊就特殊在命名上了。那么为什么我们还要用data-*呢, 一个最大的好处是我们可以把所有自定义属性在dataset对象中统一管理,遍历啊神马的都哦很方便, 而不至于零零散散了,所以用用还是不错的。*/ data: function(name, value){ 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 },
函数中的第一步是将驼峰命名的name转化为带“ - ”的小写命名:
var name = "aTcF";
name = name.replace(/([A-Z])/g, '-$1').toLowerCase();
console.log(name);
结果: a-tc-f
这里有一个不太懂的地方,就是如果value为对象的话,这里直接使用attr(attrName,value),最终属性值为"[object Object]",取出后直接JONS.parse会出错
所以我觉得attr里面的value应该再校验一下是否为对象,若是,则value = JSON.stringify(value);
这里存取自定义属性,可以用dateset 并兼容attr,例子如下:
function data(name, value){ var attrName = name.replace(capitalRE, '-$1').toLowerCase(); if(isObject(value)) value = JSON.stringify(value); if(this[0].dataset){ var data = (1 in arguments) ? this[0].dataset[attrName] = value : this[0].dataset[attrName]; }else{ attrName = 'data-'+attrName; var data = (1 in arguments) ? this.attr(attrName, value) : this.attr(attrName) } return data !== null ? deserializeValue(data) : undefined }
val:
/** * 适合表单元素读写 * 写: 写入每个元素 element.value * 读: 读第一个元素 * @param value 值/函数 * @returns {*} */ val: function(value){ return 0 in arguments ? //只有一个参数是写, this.each(function(idx){ this.value = funcArg(this, value, idx, this.value) }) : //如果是读 (this[0] && (this[0].multiple ? //对多选的select的兼容处理,返回一个包含被选中的option的值的数组 $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') : this[0].value) ) },
这里的this[0].multiple 可以判断是否为多选的select,select节点的value属性与被选中的option的value属性是相关联的。如下例子
select#third选中的option的value为1 console.log($("#third")[0].value); 结果:1 console.log($("#third")[0].value = 2); select#third选中的option的value变为2 结果:2