手写DOM库---原生JS

339 阅读5分钟

一、对象风格

也叫命名空间风格

window.dom 全局对象

第一行代码就是 window.dom = {};

增:

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

基础代码

简化一下,window可以删掉

window.dom = {};
dom.create = function() {};

create也可以直接写在window

window.dom = {
    create : function() {}
};

还可以再简化,直接把function也省略

最后就剩了这个 之后的所有代码都在window.dom = {};里面包着

功能实现

create函数接受一个tagName(标签名)参数,然后返回创建节点

main.js里试着创建一个div,然后获取到这个div

index.html里引入运行

增加点难度,同时创建一个div里有个span

  • 这里上面的方法太繁琐,要让下面这种写法实现

  • 实现原理"<div><span>1</span></div>"这一串其实就是HTML,要让它在HTML里,自动生效成HTML标签
    dom.js里首先创建一个div容器,然后通过innerHTML使用string替换掉div

优化

我们创建的容器是一个div容器,有些元素不能直接放到div里,可以使用一个新的标签<template>,可以容纳任意标签 ,不会在页面中显示,一个专门用来容纳的标签

template不能通过children直接获取要使用 template.content.firstChild 最后的代码:

window.dom = {
    create(string) {
        const container = document.createElement("template")
        container.innerHTML = string.trim();
        return container.content.firstChild
    }
}

trim可以把字符串两边的空格给去掉,直接显示标签

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

after(node, node2) {
        console.log(node.nextSibling)
        node.parentNode.insertBefore(node2, node.nextSibling);
    }

要给当前node之后放一个它的弟弟node2,就找到他的父亲,然后插入在它下一个儿子之前

  • Node.insertBefore() 方法在参考节点之前插入一个拥有指定父节点的子节点。

新建一个nextDiv,然后把nextDiv放到我们已经有的test的后面

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

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

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

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

直接调用appendChild

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

就是在当前标签之外再加一层div

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

先把parent放在当前node的前面,当这个node的兄弟,然后用appendnode加到parent里面,append不能让一个节点同时出现在两个地方,所以原来当parent兄弟的那个node在变成它儿子的时候已经从原来的地方消失了

这好像是一段绕口令,应该是看不懂的吧。。。。。

删:

删除节点dom.remove(node)

删除当前整个节点

remove(node) {
        node.parentNode.removeChild(node)
        return node  //保留当前节点的引用
    }

删除孩子dom.empty(parent)

删除当前节点的所有孩子,保留自己 最简单的方法就是直接获取到这个节点,然后innerHTML为''(空字符串)

empty(node) {
        node.innerHTML = ''
    }

如果想要获取到删除节点的引用,

empty(node) {
        const { childNodes } = node
        //直接从node获取到它的childNodes,等价于const childNods = node.childNodes
        const array = []
        for (let i = 0; i < childNodes.length; i++) {
            dom.remove(childNodes[i])
            array.push(childNodes[i])
        }
        return array
    }

获取到这个节点的childNodes,然后遍历,删除一个往数组里push一个,最后返回这个数组就是我们删掉的所有节点

<div id="empty">
        <div id="e1"></div>
        <div id="e2"></div>
        <div id="e3"></div>
</div>
const nodes = dom.empty(window.empty)
console.log(nodes)

运行一下

最后有一个undefined!!!!!!!!

childNodes.length是实时变化的,每删掉一个节点,length就会减一,所以i不能++,换成while循环试试

empty(node) {
        const { childNodes } = node
        const array = []
        let x = node.firstChild
        while (x) {
            array.push(dom.remove(node.firstChild))
            //之前的remove有返回值,所以这里可以直接引用
            x = node.firstChild
        }
        return array
    }

先找到第一个儿子,用x表示

x存在,就删掉第一个childNodespusharray里,再把x指向firstChild,当firstChild移出之后,x指向的是第二个儿子,所以要重新指向一下

最后返回array,看一下运行结果

改:

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

attr(node, name, value) {
        node.setAttribute(name, value)
    }

dom.attr(test, 'title', 'hi')运行一下

js的一个函数是可以接受多种参数的

如果传的参数arguments(所有参数)的长度是3,就设置这个value,如果参数为2,就返回它的name

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

 text(node, string) {       //适配
    if ('innerText' in node) {      //ie
        node.innerText = string
    } else {
        node.textContent = string    //Firefox/Chrome
            }
    }

读:

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

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

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

首先接受一个节点和一个对象,遍历所有的keydom.style(test, { border: '1px solid red', color: 'blue' })

使dom.style(test,'border')是读出test的border属性 或者dom.style(test,'color','red')三个参数来修改

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')
               return node.style[name]
           } else if (name instanceof Object) {
               const object = name
               for (let key in object) {
                   node.style[key] = object[key]     //key是个变量
               }
           }
       }
   }

修改class

  • 增加dom.class.add(node,'blue')
  • 删除classdom.class.remove(node,'blue')
  • 查看是否存在dom.class.has(node,'blue')

事件监听

  • 添加事件监听dom.on(node,'click',fn)
on(node, eventName, fn) {
        node.addEventListener(eventName, fn)
    }
  • 删除事件监听dom.off(node,'click',fn)
off(node, eventName, fn) {
        node.removeEventListener(eventName, fn)
    }

查:

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

find(selector, scope) {
        return (scope || document).querySelectorAll(selector)
    }

有scope从scope范围内找,没有就在document找

  • find返回的是数组,使用find方法一定要加下标

获取父元素 dom.parent(node)


parent(node) {
        return node.parentNode
    }

获取子元素 dom.children(node)

children(node) {
        return node.children
    }

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

siblings(node) {
        return Array.from(node.parentNode.children).filter(n => n !== node)
    }

这个要先找到他的爸爸,再找到他爸爸的所有儿子,使用Array.from把伪数组变成真数组,组成新的数组return回来

filter()方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

获取弟弟 dom.next(node)

next(node) {
        let x = node.nextSibling
        while (x && x.nodeType === 3) {
            x = x.nextSibling
        }
        return x
    }

下一个节点很可能是回车或者空格等文本节点,使用while循环一直找到不是文本节点的下一个节点

获取哥哥 dom.previous(node)

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

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

each(nodeList, fn) {
        for (let i = 0; i < nodeList.length; i++) {
            fn.call(null, nodeList[i])
        }
    }

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

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