手写DOM库示例

114 阅读3分钟

project from jirengu.com fangyinghang Frontend class


背景储备:有两种风格封装DOM操作;分别是对象风格和链式风格。 本文主要介绍对象风格,也叫命名空间风格,首先window.dom是我们提供的全局对象。

  1. 新建一个项目示例:新建文件夹dom-1,用vscode打开文件夹,新建src文件夹,在其中分别新建index.html,main.js,dom.js文件,dom.js主要是用来封装的,main.js主要是直接用封装好的代码来进行DOM操作的。

2.初始化index.html,并添加script标签,引入main.jsdom.js

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dom 1</title>
</head>
<body>
    示例
    <script src="dom.js"></script>
    <script src="main.js"></script>
</body>
</html>
  1. 用parcel建立服务器,并能显示页面,新建终端,parcel src/index.html ,并开始封装:
window.dom = {}   //window.dom对象 = 空对象
window.dom.create = function (){};
//也可以写成如下格式:
window.dom = {      
    create:function(){
    };
}
//再一步简化,把function删掉:
window.dom = {
    create(){};
}
  1. 接受一个标签名tagName,返回一个标签:
window.dom ={
    create(tagName){
        return document.createElement(tagName);
    }
};
  1. 用main.js来直接调用元素,就可以在控制台直接打印出div标签:
const div =dom.create('<div>');
console.log(div);
  1. 如果在div里面加一个span标签,怎么能实现呢,直接写在div里面是不能实现的,但是dom是没有直接给接口的,无法调用。
const div =dom.create('<div><span>1</span></div>');   //定义一个变量div,让dom.create('div')获取到的div赋值给这个变量
const span =dom.create('span');
div.appendChild(span);
console.log(div);
//简化为如下:
const div =dom.create('<div><span>1</span></div>');
console.log(div);
  1. 那么该如何实现呢?
window.dom ={
    create(string){
        const div =document.createElement('div');
        div.innerHTML = string
        return div.children[0]
    },
}

用container代替div,重新写为:

window.dom ={
    create(string){
        const container =document.createElement('div');
        container.innerHTML = string
        return container.children[0]
    },
}
  1. 但是div标签是有局限性的,比如里面放hi标签,就不能加上,而新标签template却可以容纳任意标签,在index里面添加,不会显示在页面中;同时,不能用children拿到trtd标签,需要用.content.firstChild来获取:
window.dom ={
    create(string){
        const container =document.createElement
        ('template');
        container.innerHTML = string.trim();
        return container.content.firstChild
    },
}

8.1 上面的代码中string.trim(),是为了避免main.js中写标签时两边有空格的话,可以直接转化为正常,举例:

const div =dom.create('   <tr><td>1<tr></td>');

上面代码有很多空格,直接执行的话,页面就会显示>#text,但是加上string.trim(),即便是有空格,也都会正常显示。

  1. 在一个node节点后面再插入一个节点,用after来实现:
window.dom ={
    after(node,node2){    node.parentNode.insertBefore(node2,node.nextSibling);
    },
}
const div =dom.create('<div>newDiv</div>');
console.log(div);
dom.after(test,div);
  1. 看起来非常简单的接口,实际上有比较恶心繁琐的实现,其他增加接口实现代码如下,
window.dom ={
    create(string){
        const container =document.createElement
        ('template');
        container.innerHTML = string.trim();
        return container.content.firstChild
    },
    after(node,node2){
        node.parentNode.insertBefore(node2,node.nextSibling);
    },
    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)
    },
}
const div =dom.create('<div>newDiv</div>');
console.log(div);
dom.after(test,div);

const div3 =dom.create('<div id="parent"></div>')
dom.wrap(test,div3)
  1. 删除节点
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dom 1</title>
</head>
<body>
    示例
    <div>
    <div id="test">test</div>
    </div>
    <div id="empty">
    <div id="e1"></div>
    <div id="e2"></div>
    <div id="e3"></div>
    </div>
    <script src="dom.js"></script>
    <script src="main.js"></script>
</body>
</html>
window.dom ={
    remove(node){           //删除节点 
        node.parentNode.removeChild(node)
        return node
    },
    empty(node){
        //const childNodes = node.childNodes 等价于下面的一行代码
        const {childNodes}=node
        const array =[]
        let x =node.firstChild
        while(x){
            array.push(dom.remove(node.firstChild))
            x = node.firstChild
        }
        return array
    },
}
const nodes=dom.empty(window.empty)
console.log(nodes)
  1. 改节点
window.dom ={
attr(node,name,value){    //重载,根据参数个数写不同的代码,就是重载
        if(arguments.length===3){
            node.setAttribute(name,value)
        }else if(arguments.length===2){
            return node.getAttribute(name)
        }
    },
    text(node,string){        //适配
        if(arguments.length===2){
            if('innerText' in node){
                node.innerText = string  //支持IE
            }else{
                node.textContent = string  //支持firfox,chrome
            }
        }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,object){
        for (let key in object){  //遍历一下object,把里面所有的key都读到
            node.style[key] = object[key]  
        }
    },
            //key不确定,可能key:border 也可能key:color
            //正常代码是node.style.border = ...
            //     或是node.style.color = ...
            //现在border和color是不确定的,变量,变量做key的话,必须放在中括号[]里面,因为nide.style.key就变成字符串  
    style(node,name,value){
        if(arguments.length===3){
            //dom.style(div,'color','red')
            node.style[name] = value
        }else if(arguments.length===2){
            if(typeof name=== 'string'){
             //dom.style(div,{color:red}) 
             return node.style[name]  
            }else if(name instanceof Object){
                const object = name
              for (let key in object){  //遍历一下object,把里面所有的key都读到
                    node.style[key] = object[key]  
              }   
            }
        }
    },
    class:{
        add(node,className){
            node.classList.add(className)
        }
    },
        remove(node,className){
            node.classList.remove(className)
    },
        has(node,className){
            return node.classList.contains(className)
    },
}
dom.attr(test,'title',"Hi,I am 96")  //三个参数实现了改属性名及属性值
const title = dom.attr(test,'title') //两个参数实现了读,把读的结果返回给变量title
console.log('title:${title}')

dom.text(test,'你好,这是新的内容')
dom.text(test)

dom.style(test,{border: '1px solid red',color:'blue'}) //共2个参数,第2个参数是对象,就是设置参数值
console.log(dom.style(test,'border'))
//dom.style(test,'border')   //共2个参数,第2个参数是字符串,那就是读取参数值或者写
dom.style(test,'border','1px solid black')  //共3个参数

dom.class.add(test,'red')
dom.class.add(test,'blue')

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