属性节点与属性
属性节点只设置在DOM元素上,而属性则是对象通用,有四个方法
attr(name, value)
– 查询 | 替换 属性节点值removeAttr(name)
– 移除指定属性节点prop(name, value)
– 查询 | 替换 属性值removeProp(name)
– 移除指定属性
jQuery
在这点上有点坏坏的,如果给一个DOM元素,使用prop()
添加的属性名是class
时,它会自动替换成className
attr(name[, value])
-
概述:查询 | 替换 属性节点值
-
解析:
- 没有参数时会直接报错
- 只有一个参数时
- 是对象参数,则拆分键值对,设置为属性节点和属性节点值
- 是字符串,则返回第一个匹配元素对应的属性节点值
- 而第二参数可选属性值和函数,依然按照遍历法
-
实现:
attr(name, value) { if (!arguments.length) { return; } if (arguments.length === 1) { if (type(name) === "object") { return this.each(function() { for (const k in name) { this.setAttribute(k, name[k]); } }); } else { return this[0].getAttribute(name); } } else { if (isFunction(value)) { return this.each(function(index) { const data = value.call(this, index, this.getAttribute(name)); if (typeof data !== undefined) { this.setAttribute(name, data); } }); } return this.each(function() { this.setAttribute(name, value); }); } }
removeAttr(name)
-
概述:移除属性节点
-
实现:
removeAttr(name) { return this.each(function() { this.removeAttribute(name); }); }
prop(name[, value])
-
概述:查询 | 替换 属性值
-
解析:
- 对于参数的处理和
attr
是一致的,直接过
- 对于参数的处理和
-
实现:
prop(name, value) { if (!arguments.length) { return; } //一个参数时检查一下情况 if (arguments.length === 1) { if (type(name) === "object") { return this.each(function() { for (const k in name) { this[k] = name[k]; } }); } else { return this[0][name]; } } else { //对于函数形式 if (isFunction(value)) { return this.each(function(index) { const data = value.call(this, index, this[name]); if (typeof data !== undefined) { this[name] = data; } }); } else { return this.each(function() { this[name] = value; }); } } }
removeProp(name)
-
概述:移除属性
-
实现:
removeProp(name) { return this.each(function() { delete this[name]; }); }
事件处理
事实上,在实现事件函数前,我觉得不会很难吧,像click()
,不就是把onclick()
给简写一下而已
不过后续发现,并不能使用onclick
这个属性,因为需要存在多个事件,需要更专业的addEventListener()
而后还需要移除事件,事实上也有对应的removeEventListener()
,不过它需要相应的引用值,像匿名函数是不行的。可对应off()
函数是一键移除所有事件,所以所有的函数都需要自己做一个存档保管
再者,可以使用选择器,过滤可以触发事件的后代元素,也就是委派
当然,只需要关注on()
函数的实现,其他事件都只是简写形式
过程当中会使用递归,所以先在外部书写,后续在原型当中调用
-
参数
-
参数最多可以接受六个
ele
– 元素type
– 事件类型selector
– 过滤选择器data
– 绑定数据fn
– 触发的回调函数one
– 是否触发一次后就移除事件,提供给one()
函数的绿道
-
参数比较多,主要关注对外开放的四个
-
type
可以是一个对象,将触发的事件类型和事件集合在一起写上,主要用于一次绑定多个事件 -
字符串形式下也可以绑定多个事件,多个类型中间使用空格隔开
-
回调函数会提供相应的事件对象参数,如果填写了
data
,那么data
会绑定至事件对象上 -
触发函数,至少要有事件类型和回调函数,以下是对于参数的处理部分
const on = function(ele, types, selector, data, fn, one) { if (typeof types === "object") { //对于第二参数非字符串时 if (!isString(selector)) { [selector, data] = [null, selector]; } //遍历这个对象,无视函数参数位置 $.each(types, (key, value) => { on(ele, key, selector, data, value, one); }); return ele; } //对于非对象时,那就只有字符串状态了 //除了对象情况,分配参数 if (data == null && fn == null) { [fn, selector] = [selector, fn]; } else if (fn == null) { if (isString(selector)) { [selector, data, fn] = [selector, null, data]; } else { [selector, data, fn] = [null, selector, data]; } } //... };
-
-
事件存储
-
后续要移除事件,因此对于匿名函数也需要进行存储,原生并不提供一键清空事件
-
对于这方面的处理,
jQuery
有一套完整的系统,不过过于复杂,此处选择在对应元素上开辟一个属性,将相应事件存入当中,取名为_eventsCache
,初始化为对象 -
而不同类型的事件,需要在此对象上再开辟一个数组空间,存储多个事件
-
-
委托
- 因为有委托的存在,在触发所填函数前,需要进行条件判断
- 对此需要存储两个函数,一个是自己所填写的函数,另一个是委托判断函数
- 填写的函数在委托判断函数当中,最后绑定的函数也是委托判断函数
- 填写的函数除了触发之外,还要与事件移除函数对接,当对应时,移除掉相应的委托判断函数
将两点结合
//...参数判断之下
return ele.each(function() {
//type有可能有多个类型存在,需要正则分割
$.each(types.split(/\s+/), (_i, type) => {
//第一次使用事件监听的元素,给其赋值
if (!this._eventsCache) {
this._eventsCache = {};
}
//需要为对应类型的事件再开辟一个数组空间
if (!this._eventsCache[type]) {
this._eventsCache[type] = [];
}
//执行的回调函数,也就是委托判断函数
const callback = function(e) {
//在事件上绑定data参数
e.data = data;
//如果有选择器参数
if (selector) {
//将选择器拼接后搜寻
const elems = document.querySelectorAll(this.localName + " " + selector);
//根据path来往上飘
for (const v of e.path) {
//当遍历到自身,就该结束了
if (v === this) {
break;
} else {
//遍历寻找的元素,当符合条件时,修改函数this指向并执行
for (const target of elems) {
if (target === v) {
fn.call(target, e);
if (one) {
$(this).off(type, fn);
}
}
}
}
}
} else {
fn.call(this, e);
if (one) {
$(this).off(type, fn);
}
}
};
//将两个函数放入一个对象当中,并塞入数组
this._eventsCache[type].push({ fn, callback });
//绑定事件监听
this.addEventListener(type, callback);
});
});
//...
对于当中的选择器拼接,事实上我回看的时候也感觉挺怪的,这难道不能直接写成这样?
const elems1 = document.querySelectorAll(this.localName + " " + selector);
const elems2 = this.querySelectorAll(selector);
关于这一点,回看核心函数处理吧