封装DOM – 硬核写DOM库
目录
前言
什么是封装
术语
常用功能自己手写DOM封装
封装DOM的总结
附上自己封装DOM库的github仓库
一、前言
【正文】
记住一个事实:原生DOM]
很难用!,如果你觉得DOM很傻,不要怀疑自己,相信自己的直觉!API的函数名和属性真的是长的不得了,本来我自己想简单写个原生DOM的博客,没想到就花了我好几个小时的时间去码字,真的是吐了,而且容易被这些函数名给绕晕!好了,废话就到这里了,接下来让我们进入正题吧!
二、什么是封装
举例
一个电脑就是CPU、内存、硬盘、主板、显卡等的封装,用户只需要接触显示器、键盘、鼠标、触控板等设备,即可操作复杂的计算机。
接口
被封装的东西需要暴露出一些功能给外部,这些功能就是接口,如USB接口,HDMI接口等。设备只要支持这些接口,即可与被封装的东西进行信息交换。比如键盘、鼠标支持USB接口,显示器支持HDMI接口。
两张图的对比就能说明一切!
三、术语
库
我们把提供给其他人用的工具代码叫做库,如jQuery,Underscore等。
API:Application Programming Interface
库暴露出来的函数或属性叫做API(应用编程接口)
框架
当你的库变的很大,并且需要学习才能看懂,那么这个库就加框架,比如Vue/React等。
四、硬核写DOM库
-
首先准备一个全局变量dom,直接将它挂载到window上
windom.dom = { /* 待写的封装函数 */ }
-
程序员的宿命:增删改查
- 增
dom.create('<div> hi </div>') | dom.create('div'); --> 创建带有内容的节点或者单个元素 dom.after(node,node2); --> 用于将node2添加在node之后 dom.before(node,node2); --> 用于将node2插入到node前面 dom.append(parent,child); --> 用于新增儿子 dom.wrap(newParent); --> 用于新增爸爸
实现后的代码:
template能容纳所有的标签,即里面可以加入任何标签
windom.dom = { /* 增 */ create(string) { if (!(string.trim()[0] === "<")) { return document.createElement(string); } else { const contaier = document.createElement("template"); contaier.innerHTML = string.trim(); return contaier.content.firstChild; } }, after(node1, node2) { node1.parentNode.insertBefore(node2, node1.nextSibling); }, before(node1, node2) { node1.parentNode.insertBefore(node2, node1); }, }
- 删
实现后的代码:dom.remove() | 用于删除节点 dom.empty() | 用于删除所有后代
remove(node) { node.parentNode.removeChild(node); return node; }, empty(node) { const arr = []; let x = node.firstChild; while (x) { arr.push(dom.remove(node.firstChild)); x = node.firstChild; } return arr; },
-
改
dom.attr(node,'title',?) | 用于读写属性 dom.text(node,?) | 用于读写内容 dom.html(node,?) | 用于读写HTML内容 dom.style(node,{color:"red"}) | 用于修改style dom.class.add(node,'blue') | 用于添加类名 dom.class.remove(node,'blue') | 用于删除class dom.on(node,'click',fn) |用于添加事件监听 dom.off(node,'click',fn) | 用于删除事件监听
实现后的代码
attr(node, name, value) { if (arguments.length === 3) { node.setAttribute("data-" + name, value); } else if (arguments.length === 2) { return node.getAttribute(name); } }, text(node, string) { /* 兼容写法 */ if (arguments.length === 2) { if ("innerText" in node) { node.innerText = strng; } else { node.textContent = string; } } else if (arguments.length === 1) { if ("innerText" in node) { return node.innerText; } else { return node.textContent; } } }, html(node, string) { if (arguments.length === 2) { node.innerHTML = string; } else if (arguments.length === 1) { return node.innerHTML; } }, style(node, name) { if (name instanceof Object) { const obj = name; for (let k in obj) { node.style[k] = obj[k]; } } else if (typeof name === String) { return node.style[name]; } }, class: { add(node, className) { node.classList.add(className); }, remoce(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) { node.removeEventListener(eventName, fn); },
-
查
dom.find('选择器') | 用于获取标签或标签们 dom.parent(node) | 用于获取父元素 dom.children(node) | 用于获取子元素 dom.siblings(node) | 用于获取兄弟姐妹元素 dom.next(node) | 用于获取下一个弟弟元素 dom.previous(node) | 用于获取上一个哥哥元素 dom.each(nodes,fn) | 用于遍历所有节点 dom.index(node) | 用于获取排行老几
实现后的代码
find(string, scope) { return (scope || document).querySelectorAll(string); }, 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() { 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) { console.log(node.parentNode); const list = dom.children(node.parentNode); for (let i = 0; i < list.length; i++) { if (list[i] === node) { return i + 1; } } },
五、总结
这是我第一次尝试去封装DOM,尝试将一些难用或者函数名很长的原生DOM封装成简单的、函数名短的简单API。从以上的函数封装来说。无疑是受益匪浅的,不仅能从中理清思维逻辑,还能进一步了解框架的一些封装技巧。下一次我将会总结一些关于jQuery的封装技巧,这些技巧让我重新认识到编程。