封装DOM库

106 阅读6分钟

DOM封装可以被叫做对象风格封装或者命名空间封装

(实操学习)

代码示例:coolkechang/-DOM: 封装一个DOM (github.com)

增删改查

window.dom = {} 是我们提供的全局对象

1.创建节点: dom.create('<div>hi</div>')

(DOM封装)

create(string){
        const container = document.createElement("template");
        container.innerHTML = string.trim();
        return container.content.firstChild;
    },
  • container:容器
  • template 标签 :
    • 1.可以容纳任意元素;
    • 2.不能通过container.children[0]拿到 ; 要用container.content.firstChild;
  • string.trim() : trim 功能是将字符串两边的空格去掉

(JS实现)

const div = dom.create("<div>newDiv</div>");

2.新增弟弟 dom.after(node, node2)

(DOM封装)

after(node, node2){
        node.parentNode.insertBefore(node2, node.nextSibling);
    },
  • dom只提供了insertBefore操作,没有提供insertAfter操作
  • 用 insertBefore 的方法把 node2 插到 node 的下一个节点的前面,那也就是在node后面插个node2。即使node是最后一个也可以。

(JS实现)

dom.after(test, div);  //在test后加上div

3.新增哥哥 dom.before(node, node2)

(DOM封装)

before(node,node2){
        node.parentNode.insertBefore(node2, node);
    },

(JS实现)

dom.before(test, div);    //在test前面加上div

4.新增儿子 dom.append(parent, child)

(DOM封装)

append(parent,node){
        parent.appendChild(node);
    },

5.新增爸爸 dom.wrap('<div></div>')

(DOM封装)

 wrap(node,parent){
        dom.before(node,parent);
        dom.append(parent,node);
    },

把 parent 放到 node 前面;再把 node append 到 parent 里

(JS实现)

const div3 = dom.create('<div id="parent"></div>');
dom.wrap(test, div3);

先把 div3 放到 test 的前面,再 div3.appendChild(test)

1.删除节点 dom.remove(node)

(DOM封装)

remove(node){
        node.parentNode.removeChild(node);
        return node;//return一下可以保留对于这个节点的引用。
    },

2.删除后代 dom.empty(parent)

(DOM封装)

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;//删除并保留对于这个节点的引用。
    },
  • 很容易想到的一个思路是,用for循环遍历Parent.childNodes, 然后每次都remove
  • 这里不用for循环遍历是因为childNodes里面的元素删掉后它的length是会变化的,最终会多出一个undefined。
  • 注意这里用while循环会出现文本节点,因为有空格(回车)。

(JS实现)

const nodes = dom.empty(window.empty);

1.读写属性 dom.attr(node, 'title', ?)

(DOM封装)

attr(node, name, value){
        if(arguments.length === 3){
            node.setAttribute(name, value)
        }else if(arguments.length === 2){
            return node.getAttribute(name)
        }//这种方法叫“重载”;也就是根据不同的参数,获取到不同的效果
    },//attr : attribute的缩写
  • 这里用到了重载,即当函数的参数不一样时,做不一样的处理
  • 当只有两个参数时,就读属性,返回属性
  • 当有三个参数时,就修改属性
  • 这里由于同时可以读写属性,用到了getter和setter设计模式

(JS实现)

dom.attr(test, 'title', 'hi,I am web');//三个参数实现了写title
const title = dom.attr(test, 'title');//两个参数实现了读test的 'title',并返回一个值给变量
console.log(`title: ${title}`);

2.读写文本内容 dom.text(node, ?)

(DOM封装)

text(node, string){
        if(arguments.length === 2){
            if('innerText in node'){
              node.innerText = string//(ie)
            }else{
              node.textContent = string//(firefox / chrome)
            }//以上的写代码方式成为“适配”,可满足所有浏览器;
            //除了少数IE浏览器只支持第一种写法外外其他浏览器都支持两种写法;
            //会改变对应标签内所有内容,不保留标签内的标签
        }else if(arguments.length === 1){
            if('innerText in node'){
              return node.innerText
            }else{
              return node.textContent
            }
        }
    },
  • 如果只有一个参数就读文本内容,返回node.innerText
  • 如果有两个参数,就写文本内容 node.innerText = string

(JS实现)

dom.text(test, '你好,这是新的内容');//改写
dom.text(test);//读

3.读写 HTML 内容 dom.html(node, ?)

(DOM封装)

  html(node, string){
        if(arguments.length === 2){
          node.innerHTML = string
        }else if(arguments.length === 1){
          return node.innerHTML
        }
    },

4.修改 style dom.style(node, {color: 'red'})

(DOM封装)

style(node, name, value){
        if(arguments.length === 3){// dom.style(div, 'color', 'red')
            node.style[name] = value
        }else if(arguments.length === 2){// dom.style(div, 'color')
            if(typeof name === 'string'){
                return node.style[name]
            }else if(name instanceof Object){// dom.style(test, {color:'blue'})
                const object = name
                for(let key in object){
                    node.style[key] = object[key]
                }//key可能是border、color...是一个变量,不能用node.style.key = ...;如果这样写key是字符串,不是变量。
            }
        }
        
    },

注意,这里会有三种情况使用style函数:

  1. dom.style(node, 'color', 'red') 设置一个属性
  2. dom.style(node, 'color') 读一个属性
  3. dom.style(node, {'color': 'red', 'border': '1px solid black'}) 传一个对象,同时设置多个属性

我们需要对不同的参数进行处理,需要用到适配器模式。

  • 首先我们需要判断参数的个数,
  • 如果是3,则是第一种情况,直接node.style[name] = value
  • 如果是2,在此前提下再次进行判断
  • 如果第二个参数是字符串,直接return node.style[name]
  • 如果第二个参数是一个对象,遍历这个对象

(JS实现)

dom.style(test, {border: '1px solid red', color:'blue'});
console.log(dom.style(test, 'border'));
dom.style(test, 'border', '1px solid black');

5.添加与删除class

  • 添加class:dom.class.add(node, 'blue')
  • 删除class:dom.class.remove(node, 'blue')

(DOM封装)

class:{
        add(node,  className){
            node.classList.add(className)
        },
        remove(node, className){
            node.classList.remove(className)
        },
        has(node, className){
            return node.classList.contains(className)
        }
    },

(JS实现)

dom.class.add(test, 'red');
dom.class.add(test, 'blue');
dom.class.remove(test, 'blue');
console.log(dom.class.has(test, 'blue'));

7.添加事件监听 dom.on(node, 'click', fn)

(DOM封装)

on(node, eventName, fn){
        node.addEventListener(eventName, fn)
    },

(JS实现)

const fn = ()=>{
    console.log('点击了')
}
dom.on(test, 'click', fn)

8.删除事件监听 dom.off(node, 'click', fn)

(DOM封装)

off(node, eventName, fn){
        node.removeEventListener(eventName, fn)
    },

(JS实现)

const fn = ()=>{
    console.log('点击了')
}
dom.off(test, 'click', fn)

1.获取标签(们) dom.find('选择器')

(DOM封装)

find(selector, scope){
        return (scope || document).querySelectorAll(selector)
    },
  • 给一个选择器,返回对应的元素;无论给的选择器有几个元素,通过调用document.querySelectorAll全部返回一个数组
  • 如果有scope,就在scope里面调用document.querySelectorAll;没有就用document调用document.querySelectorAll
  • scope : 范围

(JS实现)

const testDiv = dom.find('#test')[0]
console.log(testDiv)
const test2 = dom.find('#test2')[0]
console.log(dom.find('.red', test2)[0])

2.获取父元素 dom.parent(node)

(DOM封装)

parent(node){
        return node.parentNode
    },

(JS实现)

console.log(dom.parent(test))

3.获取子元素 dom.children(node)

(DOM封装)

children(node){
        return node.children
    },

(JS实现)

console.log(dom.children(siblings))

4. 获取兄弟姐妹元素dom.siblings(node)

(DOM封装)

siblings(node){
        return Array.from(node.parentNode.children)
        .filter(n=>n!==node)
    },
  • 获取父节点的子节点,然后删除自己
  • 注意:这里node.parentNode.children得到的是伪数组,需要将其变成数组后使用 filter 将不等于 node 的放到数组里面

(JS实现)

console.log(dom.siblings(dom.find('#s2')[0]))

5. 获取弟弟dom.next(node)

(DOM封装)

 next(node){
        let x = node.nextSibling
        while(x && x.nodeType === 3){
            x = x.nextSibling
        }
        return x
    },
  • 代码意思是当 x 存在并且 x 的节点类型常量是文本时,继续找下一个,直到不是文本的节点,最后没有了也要返回
  • 这样写的原因是,节点里可能包括HTML标签、 文本、注释等,所以需要这样处理一下,保证我们找到的是一个HTML标签
  • nodeType 指节点类型
  • 3 表示节点类型常量的值是 3 ,这个常量是Node.TEXT_NODE( Element 或者 Attr 中实际的文字)
  • nodeType 可查MDN看到其常量和对应的值

(JS实现)

const s2 = dom.find('#s2')[0]
console.log(dom.next(s2))

:这里还可以用元素节点:

next(node) {
        let x = node.nextSibling
        while (x && x.nodeType !== 1) {
            x = x.nextSibling
        }
        return x
    },
  • 代码意思是如果 x 存在且 x 的节点类型常量不是HTML元素节点,就继续找下一个,直到找到一个节点是HTML元素节点
  • 1 表示节点类型常量的值是 1 ,这个常量是Node.ELEMENT_NODE( 一个 元素节点,例如 <p> 和 <div>

6.获取哥哥 dom.previous(node)

(DOM封装)

previous(node){
        let x = node.previousSibling
        while(x && x.nodeType === 3){
            x = x.previousSibling
        }
        return x
    },

(JS实现)

const s2 = dom.find('#s2')[0]
console.log(dom.previous(s2))

7.遍历所有节点 dom.each(nodes, fn)

(DOM封装)

each(nodeList, fn){
        for(let i=0;i<nodeList.length;i++){
            fn.call(null,nodeList[i])
        }
    },
  • 给一个 nodeList (节点列表,即一堆节点),调用一个函数,遍历这个节点列表。

(JS实现)

const t = dom.find('#travel')[0]
dom.each(dom.children(t), (n)=> dom.style(n, 'color', 'red'))
  • 找到 t 的所有子节点,对其进行 each 操作,每一个用 n 占位,加一个style(n 的颜色为红)

8. 获取排行老几dom.index(node)

(DOM封装)

index(node){
        const list = dom.children(node.parentNode)
        let i
        for(i=0;i<list.length;i++){
            if(list[i] === node){
                break
            }
        }
        return i
    }

(JS实现)

console.log(dom.index(s2))

参考资料:饥人谷前端学习课程