DOM提供的api实在是不好用,每个API又臭又长
源代码链接
一、一些术语
- 库
- 我们把提供给其他人用的工具代码叫做库
- 比如jQuery、Underscore
- API
- 库暴露出来的函数或属性叫做API (应用编程接口)
- 框架
- 当你的库变得很大,并且需要学习才能看懂,
- 那么这个库就叫框架,比如Vue / React
- 注意
- 编程界的术语大部分都很随便,没有固定的解释
- 所以意会即可
二、对象风格
window.dom 是我们提供的全局对象
// 怎么样把一个对象声明为全局对象呢?
// 把对象挂载到window上
dom = window.dom
三、增删改查
增
1.创建节点
dom.create(`<div><span>hi</span></div>`) // 用于创建节点
- 一般我们创造节点的目的就是在别的节点中插入此节点,
- 那么我要封装一个 以输入 html格式的 的 create函数
- 能够在创造节点的同时在里面加一些其他节点
- 传入的字符串要是以有标签的 以html的形式来
dom.create源代码
create(string) {
// 创建容器 template标签可以容纳任意元素
const container = document.createElement('template')
// 要trim,防止拿到空字符
container.innerHTML = string.trim()
// 必须要 使用 .content 要不然拿不到
return container.content.firstChild
// 或者
// container.innerHTML = string
// return container.content.children[0]
}
//用法
dom.create('<div>你好</div>')
2.新增弟弟
思路:由于dom只提供了Insertbefore操作,没有提供insertAfter操作, 所以我们需要使用一点黑魔法假设有两个节点 div1 --- > div3我们想要再div1后面加一个div2, 这就等价于在div3前面插入div2所以我们现有node.nextSibling获取当前节点的下一个节点,再insertBefore
after(node, newNode) {
// 找到此节点的爸爸然后调用insertBefore(插入某个节点的前面)方法,
//把 newNode 插入到下一个节点的前面
node.parentNode.insertBefore(newNode, node.nextSibling)
}
// 用法示例:
dom.before(div1,div2)
3.新增哥哥
思路:找到爸爸节点,然后insertBefore
before(node, newNode) {
// 正常的返回DOM原生的添加前面的节点的方法即可
node.
parentNode.insertBefore(newNode, Node)
}
// 用法示例:
dom.before(div1,div2)
4.新增儿子
思路:找到爸爸节点,appendChild
append(parent, node) {
parent.appendChild(node)
}
// 用法示例:
let div1 = dom.create('<div>1</div>')
let div2 = dom.create('<div>2</div>')
dom.append(div1,div2)
5.新增爸爸
wrap(node, parent) {
// 把Newparent 放到node前面
// 把node append到newparent里
// 目标: div1
// ↓----> div2
// 变成 div1
// ↓----> div3
// ↓----> div2
// 先把div3 放到div2的前面,再div3.appendChild(div2)
dom.before(node, parent)
dom.append(parent, node)
}
// 用法示例:
let div1 = dom.create('<div>1</div>')
let div2 = dom.create('<div>2</div>')
dom.wrap(div1,div2)
使用方法
const div3 = dom.create('<div id="parent"></div>')
dom.wrap(test, div3)
删
1.删除节点
思路:找到爸爸节点, removeChild
remove(node){
node.parentNode.removeChild(node)//让这个节点的爸爸删除这个儿子
return node//还可以保留这个节点的引用
// 用法示例:
dom.remove(div1)
2.删除后代
用法: 删除这个节点的所有子代
思路: 遍历删除它的所有子节点,并返回 删除的节点
不能用for循环的原因:因为每次 dom.remove 删除的时候,它的长度就会随之改变, 而我们又在for循环它,因此我测试时候会出现bug,因此我们选择使用 while 循环 解决。
// empty 把所有子节点删掉
// 坑:childNodes.length每次的Length会变化
empty(node) {
// const {childNodes} = node 等价于const childNodes = node.childNodes
const array = []
let x = node.firstChild
while (x) {
array.push(dom.remove(node.firstChild))
x = node.firstChild
}
return array
}
// 用法示例:
dom.empty(div1))
改
1.读写属性
- 这里用到了重载,即当函数的参数不一样时,做不一样的处理
- 当只有两个参数时,就读属性,返回属性
- 当有三个参数时,就修改属性
- 这里由于同时可以读写属性,用到了getter和setter设计模式
// 根据参数的个数,实现不同的函数,这叫函数的重载
attr(node, name, value) {
if (arguments.length === 3) {
node.setAttribute(name, value)
} else if (arguments.length === 2) {
return node.getAttribute(name)
}
},
使用方法
// 修改 <div id="test" >test</div>
// #test的title属性值为 hello world
dom.attr(test, 'title', 'hello world') //修改属性
const title = dom.attr(test, 'title') //读属性
console.log(`title: ${title}`)
2.读写文本内容
- 如果只有一个参数就读文本内容,返回node.innerText
- 如果有两个参数,就写文本内容 node.innerText = string
- 同时还对不同浏览器做了适配,这里运用了适配器模式
text(node, string) {
if (arguments.length === 2) {
// 适配不同浏览器
if ('innerText' in node) { //ie
node.innerText = string
} else { // firefox / chrome
node.textContent = string
}
} else if (arguments.length === 1) {
if ('innerText' in node) { //ie
return node.innerText
} else { // firefox / chrome
return node.textContent
}
}
}
// 用法示例:
dom.text(div1) //读取div1里的文本内容
dom.text(div1,'你好') //改写div1里的文本内容
3.读写HTML内容 dom.html(node, ?)
html(node, string) {
if (arguments.length === 2) {
//修改
node.innerHTML = string
} else if (arguments.length === 1) {
// 获取内容
return node.innerHTML
}
}
// 用法示例:
dom.html(div1) //读取div1标签里的HTML内容
dom.html(div1,'<span>你好</span>') //改写div1标签里的HTML内容
4.修改style dom.style(node, {color:'red'})
// 原生DOM读取、修改样式:
node.style.name
node.style.name = 'value'
// 封装:
function style(node,name,value){
if(arguments.length === 3){
node.style[name] = value
}else if(arugments.length === 2){
return node.style[name]
}
}
// 用法示例:
dom.style(div1,'color') //读取div1标签的样式里的color的值
dom.style(div1,'color','red') //修改div1标签的样式里的color的值
// 有的人喜欢这样写:dom.style(div1,{color:'red'}),所以可以继续优化:
function style(node,name,value){
if(arguments.length === 3){
node.style[name] = value
}else if(arugments.length === 2){
if(typeof name === 'string'){
//如果输入的第二个参数的数据类型是字符串的话,就返回样式属性的值
renturn node.style[name]
}else if(typeof name === 'object'){
//如果输入的第二个参数的数据类型是对象的话,就执行下面的代码
const object = name //把name的值赋值给object变量
for(let key in object){ //在object变量里放入一个key,代表属性值
node.style[key] = object[key]
}
}
}
}
// 用法示例:
dom.style(div1,{color:'red'})
// 这里name、object和key有点绕,下面配一个图示就能看的容易一些了:
instanceof
用typeof可以判断一个输入的变量的数据类型,当判断的东西是对象时,我们还可以用instanceof;
instanceof 翻译是实例的意思,是一个对象运算符,用来判断一个对象是谁的实例,检测构造函数的prototype属性是否出现在某个实例对象的原型链上。所以也可以用来检测对象数据类型。
用法:obj1 instanceof obj2
如果 obj1 是 obj2 的实例的话,就返回true,不是的话,就返回false;
instanceof 和 typeof 的不同:
- 形式不同:(typeof sth) 和 (sth1 instanceof sth2)
- typeof 可以判断所有数据类型的变量,返回值是字符串,返回值有:number、string、symbol、bool、null、undefined、function、object,而instanceof仅适用于对象这一数据类型;
- typeof 用来判断丰富的对象实例时,只能返回一个'object'字符串。用instanceof用来判断对象,可以返回true或false;
- instanceof 可以对不同的对象实例进行判断,判断方法是根据对象的原型链依次向下查询,而 typeof 就不能;
所以之前的代码也可以用 instanceof 来改写
function style(node,name,value){
if(arguments.length === 3){
node.style[name] = value
}else if(arguments.length === 2){
if(typeof name === 'string'){
return node.style[name]
}else if(name instanceof Object){
// 对比: typeof name === 'object'
const object = name
for(let key in object){
node.style[key] = object[key]
}
}
}
}
5.添加、删除class
// 原生DOM对class的操作:
node.classList.add(className) //添加一个类
node.classList.remove(className) //移除一个类
node.classList.contains(className)//检测一个节点有没有某个类
// 封装:
windom.dom = {
class:{
add(node,className){
node.classList.add(className)
},
remove(node,className){
node.classList.remove(className)
},
has(node,className){
return node.classList.contains(className)
}
}
}
// 用法示例:
dom.class.add(div1,'red')
dom.class.remove(div1,'red')
dom.class.has(div1,'red')
6.添加、删除事件监听
// 原生DOM的事件监听:
// 自身属性里的onclick:
node.onclick = function(){...} //添加一个鼠标点击事件
node.onclick = null //移除鼠标点击事件
// 原型链里的EventTarget.prototype里的方法:
node.addEventListener(click,function(){...}) //添加一个鼠标点击事件
node.removeEventListener(click,function(){...}) //移除鼠标点击事件
// 封装:
function on(node,eventName,fn){
node.addEventListener(eventName,fn)
}
function off(node,eventName,fn){
node.removeEventListener(eventName,fn)
}
// 用法示例:
dom.on(div1,'click',function(){console.log('hi')})
function sayHi(){console.log('hi')}
dom.on(div1,'click',sayHi)
dom.off(div1,'click',sayHi)
// 用off的话,必须要提前把函数名字定好(使用具名函数,就是具有名字的函数),不然使用匿名函数是无效的:
// 错误示范:
dom.on(div1,'click',function(){console.log('hi')})
dom.off(div1,'click',function(){console.log('hi')})
// 这样写off是不生效的,点击了依然还会执行原来添加的功能
查
1.获取标签
// 原生DOM里寻找元素的办法:
doucment.getElementById('id')
document.getElementsByClassName('div')[0]
document.getElementsByClassName('red')[0]
document.querySelector('#id')
document.querySelectorAll('.red')[0]
// 封装:
function find(selector){
return document.querySelectorAll(selector)
}
// 用法示例:
dom.find('#test')[0] //因为返回的是个伪数组,所以要记得在后面加下标
// 拓展:
// 假如我想在下面的代码里把p标签从div标签里找出来,如何实现?
<div id='test'>
<p class='red'>文字</p>
</div>
// 实现:
function find(selector,scope){ //scope 范围的意思
return (scope || document).querySelectorAll(selector)
}
// 用法示例:
let test = dom.find('#test')[0]
dom.find('.red',test)
2.获取父元素和子元素
// 获取父元素
function parent(node){
return node.parentNode
}
// 获取子元素
function children(node){
return node.children
}
// 用法示例:
dom.parent(div1)
dom.children(div1)
3.获取兄弟姐妹元素
// 原生DOM里用 node.parentNode.children 可以获取到兄弟姐妹元素,所以可以封装为:
function siblings(node){
return node.parentNode.children
}
// 这样可以获取到所有的兄弟姐妹元素,但是里面也包括了自己,所以要把自己给排除出去,剩下的才是真正严格意义上的所有兄弟姐妹节点
// 这里的思路要用到数组的过滤方法:filter,但是返回的是一个伪数组,所以要先把返回结果变成数组再过滤
function siblings(node){
return Array.from(node.parentNode.children).filter((n)=> n !== node)
// 使用Array.from 使之变成数组
// filter((n)=> n !== node) 意思是如果n不等于node,就留下来放到数组里,等于node就给过滤出去
}
// 用法示例:
dom.siblings(div1)
4.获取哥哥和弟弟元素
// 原生DOM获取下一个节点:node.nextSibling
// 原生DOM获取上一个节点:node.previousSibling
// 注意这里是节点,不是元素,因为获取到的可能是文本节点,而我们想获取的不是文本节点,所以在封装的时候要排除:
// 获取弟弟元素
function next(node){
let x = node.nextSibling
while(x && x.nodeType === 3){
// 当x存在时,并且节点类型为3,也就是文本节点时,就继续寻找下一个节点,直到不是文本节点为止
x = x.nextSibling
}
return x
}
// 获取哥哥元素
function pervious(node){
let x = node.previousSibling
while(x && x.nodeType === 3){
x = x.previousSibling
}
return x
}
odeType详细信息:nodeType MDN
5.遍历所有节点
function each(nodeList,fn){
for(let i = 0;i<nodeList.length;i++){
fn.call(null,nodeList[i]) // this设置为空
}
}
// 用法示例:
// html:
<div id="div1">
<div id="div2">2</div>
<div id="div3">3</div>
</div>
// js:
let divList = document.querySelector('#div1').children
dom.each(divList,(n) => n.style.color = 'red')
6.获取元素排行老几
// 思路:把给出元素的所有兄弟姐妹元素都找出来遍历循环,循环到自己的时候返回数字
function index(node){
const list = dom.children(node.parentNode)
let i //之所以在循环外面声明i,是因为用let声明时,作用域仅限于循环内,到外面就不能返回了
for(i = 0;i<list.length;i++){
if(list[i] === node){
break;
}
}
return i
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!