最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【JS编程接口】手写DOM库

    正文概述 掘金(长街千堆雪)   2021-03-16   488

    DOM提供的api实在是不好用,每个API又臭又长

    源代码链接

    一、一些术语

    • 我们把提供给其他人用的工具代码叫做库
    • 比如jQuery、Underscore
    1. API
    • 库暴露出来的函数或属性叫做API (应用编程接口)
    1. 框架
    • 当你的库变得很大,并且需要学习才能看懂,
    • 那么这个库就叫框架,比如Vue / React
    1. 注意
    • 编程界的术语大部分都很随便,没有固定的解释
    • 所以意会即可

    二、对象风格

    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有点绕,下面配一个图示就能看的容易一些了:
    

    【JS编程接口】手写DOM库

    instanceof

    用typeof可以判断一个输入的变量的数据类型,当判断的东西是对象时,我们还可以用instanceof;

    instanceof 翻译是实例的意思,是一个对象运算符,用来判断一个对象是谁的实例,检测构造函数的prototype属性是否出现在某个实例对象的原型链上。所以也可以用来检测对象数据类型。

    用法:obj1 instanceof obj2

    如果 obj1 是 obj2 的实例的话,就返回true,不是的话,就返回false;

    instanceof 和 typeof 的不同:

    1. 形式不同:(typeof sth) 和 (sth1 instanceof sth2)
    2. typeof 可以判断所有数据类型的变量,返回值是字符串,返回值有:number、string、symbol、bool、null、undefined、function、object,而instanceof仅适用于对象这一数据类型;
    3. typeof 用来判断丰富的对象实例时,只能返回一个'object'字符串。用instanceof用来判断对象,可以返回true或false;
    4. 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
    }
    
    

    起源地下载网 » 【JS编程接口】手写DOM库

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元