配电脑的比喻
目标
对于
BOM/DOM/Canvas/jQuery/Date/LocalStorage/RegExp/Lodash/Underscore/URL 处理
的封装,先实现部分DOM
目录
- 术语
- 两种风格封装DOM操作
- 命名风格
- 细节
- 增删改查
- 后续
- 设计模式
术语
- 库:工具代码
- API:应用编程接口(中文翻译)(外部可使用的库的函数或属性)
- 框架:framwork(英文翻译)
- 封装(encapsulation)
- 意会即可
两种风格封装DOM操作
对象风格
也叫命名空间风格
-
window.dom
是所提供的全局函数
-
示例代码
各功能接口
增
接口
dom.create(`<div>hi</div>`) // 用于创建节点
dom.after(node, node2) // 用于向后追加兄弟节点
// 原生的提供了一个兼容性不佳的实验性接口`ChildNode.after() MDN`
dom.before(node, node2) // 用于向前追加兄弟节点
dom.append(parent, child) // 用于创建子节点
dom.wrap(`<div></div>`) // 用于创建父节点
index.html
<body>
示例
<div id="dad">
<div id="test">test</div>
</div>
<script src="dom.js"></script>
<script src="main.js"></script>
</body>
dom.js
// // 相当于 dom.create = function() {}
window.dom = {
// create: function() {} // 可简化为
create(tagName /* 语义化 形参 */ ) {
// return document.createElement(tagName) // 不能创建带有结构的 HTML 元素`<div><span>1</span></div>`
// const container = document.createElement("div")
const container = document.createElement("template")
container.innerHTML = tagName.trim() // 除去空格
// return container.children[0]
return container.content.firstChild
/* 存在 不可识别元素(<td></td>)的 bug
** <td</td>> 不能单独存在 只能放在<table></table> 里<tr></tr>或<tbody></tbody> 里,放在 div 里不符合 HTML 语法
** 可以放任意元素,不出 bug 的标签是 <template></template>
** <template></template> 是专门用来容纳任意标签的
** <template></template> 用template.content.firstChild拿到
*/
},
after(node, node2) { // 在后面插入节点,就相当于在此 node 后面的节点的前面插 // 必须调用父节点的 insertBefore() 方法
console.log(node.siblings) // null ?
node.parentNode.insertBefore(node2, node.nextSibling)
/* 判断 排除最后一个节点 没有下一个节点 null 也符合 */
},
before(node, node2) {
node.parentNode.insertBefore(node2, node)
},
append(parent, node) {
parent.appendChild(node)
},
wrap(node, parent) {
dom.before(node, parent) // 将要包裹的“父节点”先插到目标节点的前面
dom.append(parent, node) // 再把目标节点用 append 移至将要包裹的父节点的下面
}
}
main.js
// 对比 document.createElement("div") 简化代码
// const div = dom.create("div")
/* 直接写出 HTML 结构 */
const div = dom.create("<div><span>newDiv</span></div>")
const td = dom.create("<tr><td>TD</td></tr>")
console.log(div)
console.log(td)
/* after */
dom.after(test, div)
const div3 = dom.create('<div id="wrapper">DIV3</div>') // 父节点
dom.wrap(test, div3)
删
接口
dom.remove(node) //用于删除节点
dom.empty(parent) //用于删除后代节点
dom.js
window.dom = {
remove(node) {
// node.remove() // IE 不支持 兼容性不好
node.parentNode.removeChild(node);
return node; // 仍然需要获取此节点的引用
},
empty(node) {
// 清空 node 里面的所有子元素
// node.innerHTML = ''
// const childNodes = node.childNodes 可以改写成以下的写法
/*
** const {childNodes} = node // 解构赋值
*/
const array = [];
/*
** for (let i = 0; i < childNodes.length; i++) { // 不需要i++的循环就用 while 循环代替
** console.log(childNodes)
** console.log(childNodes.length)
** dom.remove(childNodes[i]) // remove( nodes) 会实时改变 nodes 的长度每次减一 导致循环的长度不固定 出现 bug
** array.push(childNodes[i])
** }
*/
// 不需要i++的循环就用 while 循环代替for 循环
/* 获取第一个子节点 并 push 进数组 */
let x = node.firstChild;
while (x) {
// 如果 x 存在
array.push(dom.remove(node.firstChild));
x = node.firstChild; // 第一个子节点已经移除 原先第二节点就变为现在的第一个节点
}
return array; // 仍然需要获取此节点的引用
}
}
main.js
dom.class.add(test, "ftSize");
dom.class.remove(test, "ftSize");
/* empty test */
const nodes = dom.empty(window.empty);
console.log(nodes);
改
接口
dom.attr(node, 'title', ?) // 用于读写属性
dom.text(node, ?) // 用于读写文本内容
dom.html(node, ?) // 用于读写HTML内容
dom.style(node, "color", "red") // 用于修改style
dom.style(node, {color: "red"}) // 用于修改style
dom.style(node, "border") // 用于读取style
dom.class.add(node, 'blue') // 用于添加class
dom.class.remove(node, 'blue') // 用于删除class
dom.on(node, 'click', fn) // 用于添加事件监听
/* 是否能有效删除事件监听? */
dom.off(node, 'click', fn) // 用于删除事件监听
dom.toggle(node,'click',fn) // 切换开关
dom.js
window.dom = {
/* 改 用于读写属性 */
/* 用判断 arguments 的个数来重载函数 */
/* 重载
** 有三个形参时,就是设置;
** 第二个形参时,就是读取
*/
attr(node, name, value) {
// 组合
if (arguments.length === 3) {
node.setAttribute(name, value); // 原生DOM setAttribute(name, value)
} else if (arguments.length === 2) {
return node.getAttribute(name); // 原生DOM getAttribute(name) 并返回值
}
},
/* 用于读/写文本内容 */
/* 重载
** 有两个形参时,就是设置;
** 第一个形参时,就是读取
*/
text(node, string) {
// 设计模式 之 适配
// console.log('innerText' in node) //true
if (arguments.length === 2) {
/* 写 */
if ("innerText" in node) {
node.innerText = string; // IE // 会将节点原本的所有内容,包括标签全部改变
} else {
node.textContent = string; // Chrome/ Firefox // 会将节点原本的所有内容,包括标签全部改变
}
} else if (arguments.length === 1) {
/* 读 */
if ("innerText" in node) {
return node.innerText; // IE // 会将节点原本的所有内容,包括标签全部改变
} else {
return node.textContent; // Chrome/ Firefox // 会将节点原本的所有内容,包括标签全部改变
}
}
},
/* 用于读/写HTML内容 */
html(node, string) {
if (arguments.length === 2) {
node.innerHTML = string;
} else if (arguments.length === 1) {
return node.innerHTML;
}
},
/* 用于修改style */
/* 重载
** 第二个形参是对象时,就是设置;dom.style(div, {color: "red"})
** 有三个形参时,也是设置;dom.style(div, 'color', 'red')
** 第二个形参是字符串时,就是读取 dom.style(div, 'color')
*/
style(node, name, value) {
if (arguments.length === 3) {
// dom.style(div, "color", "red"')
return node.style[name] = value;
// node.style.name = value;
} else if (arguments.length === 2) {
if (typeof name === "string") {
// 读取 dom.style(div, 'color')
return node.style[name];
} else if (name instanceof Object /* true */ ) {
// dom.style(div, {color:'red'})
const object = name;
for (let key in object) {
// 遍历读取所有对应的key
// key: border | color | ···
// node.style.border = ...
// node.style.color = ...
// 调用属性值 []方法 读取的时变量;点方法 读取的是字符串
// node.style.key; // 字符串
node.style[key] = object[key];
}
return object
}
}
},
class: {
/* 用于添加class */
add(node, className) {
node.classList.add(className)
},
/* 用于删除class */
remove(node, className) {
node.classList.remove(className)
},
has(node, className) {
return node.classList.contains(className)
}
},
/* 事件相关 */
on(node, eventName, fn) {
node.addEventListener(eventName, fn)
},
off(node, eventName, fn) {
console.log(`${eventName}取消事件`)
node.removeEventListener(eventName, fn)
},
toggle(node, eventName, fn) {
node.addEventListener("mousedown", function() {
console.log("鼠标按下了");
node.addEventListener(eventName, fn);
node.addEventListener("mouseup", function() {
console.log("鼠标抬起了");
node.removeEventListener(eventName, fn)
})
});
}
}
main.js
/* 改 */
/* 用于读写属性 attr(node, attributeName, value) */
dom.attr(test, "title", "Hi, Jack");
const title = dom.attr(test, "title");
console.log(`title: ${title}`);
/* 用于读写文本内容 */
dom.text(test, "Hello,this is a new text");
/* 用于读写HTML内容 */
const Dad = dom.html(dad);
console.log(`Dad: ${Dad}`);
/* 用于修改style */
dom.style(test, {
border: "1px solid cyan",
color: "red"
});
console.log(dom.style(test, "border"));
/* 重载 修改 style */
dom.style(test, "border", "5px solid olive");
/* 重载 读取 style */
const styleBorder = dom.style(test, "border");
console.log(styleBorder);
/* 用于添加class */
dom.class.add(test, "bgColor");
dom.class.add(test, "ftSize");
dom.class.remove(test, "ftSize");
console.log(dom.class.has(test, "bgColor"));
console.log(dom.class.has(test, "ftSize"));
/* 用于添加事件监听 */
function addFn() {
console.log('指到这里')
console.log(`点击 ${newDiv} 取消事件`)
console.log(newDiv)
}
// test.addEventListener('click') // TL,DR
dom.on(test, "mouseenter", addFn);
/* 用于删除事件监听 */
// test.removeEventListener(eventName, fn) // TL,DR
dom.off(test, "click", addFn);
dom.toggle(test, "click", addFn);
用到了设计模式:重载 适配
查
接口
/* 用 ID 来返回元素 */
dom.find('选择器') // 用于获取标签(们)
/* 无法动态地查找 因为用了 querySelector */
dom.parent(node) // 用于获取父元素
dom.childern(node) // 用于获取子元素
dom.siblings(node) // 用于获取兄弟元素
dom.next(node) // 用于获取前一个元素
dom.previous(node) // 用于获取后一个元素
dom.each(node, fn) // 用于遍历所有节点
dom.index(node) // 用于获取排列下标
dom.js
window.dom = {
/* 查 */
/* scope 为查找的范围 节点对象 */
find(selector, scope) {
/* 如果有 scope 节点 就找 scope 里的;没有就找 document 里的 */
return (scope || document).querySelectorAll(selector)
/* 返回的是 NodeList 伪数组 取用加 NodeList[0] */
},
parent(node) {
return node.parentNode
},
children(node) {
return node.children
},
siblings(node) {
return Array.from(node.parentNode.children)
.filter(n => n !== node)
},
next(node) {
let x = node.nextSibling
/* 排除文本节点 */
while (x && x.nodeType === 3) {
x = x.nextSibling
}
return x
},
previous(node) {
let x = node.previousSibling
/* 排除文本节点 */
while (x && x.nodeType === 3) {
x = x.previousSibling
}
return x
},
each(nodeList, fn) {
for (let i = 0; i < nodeList.length; i++) {
fn.call(null, nodeList[i])
}
},
index(node) {
const list = dom.children(node.parentNode)
let i
for (i = 0; i < list.length; i++) {
if (list[i] === node) {
break
}
}
return i
}
}
main.js
/* 查 */
/*
<div id="siblings">
<div id="s1"></div>
<div id="s2"></div>
<div id="s3"></div>
</div>
<div id="travel">
<div id="t1">t1</div>
<div id="t2">t2</div>
<div id="t3">t3</div>
</div>
*/
console.log(`=== 找父、兄节点 ===`)
console.log('parent')
console.log(dom.parent(test))
console.log('children')
console.log(dom.children(test2))
console.log('siblings')
const s2 = dom.find('#s2')[0]
console.log(dom.siblings(s2))
console.log('next')
console.log(dom.next(s2))
console.log('previous')
console.log(dom.previous(s2))
console.log(`=== ===`)
/* 遍历 */
const t = dom.find('#travel')[0]
dom.each(dom.children(t), (n) => dom.style(n, 'color', 'red'))
/* 下标 */
console.log(dom.index(s2))
看思路,而不是源代码
·未完待续·
参考链接
相关文章
- 作者: Joel
- 文章链接:
-
版权声明
- 非自由转载-非商用-非衍生-保持署名