DOM操作和样式操作库的封装

作者: iceman_dev | 来源:发表于2017-03-10 15:49 被阅读629次

    一、DOM常用方法和属性复习

    以下粗略的罗列一下DOM的常用方法和属性,由于不是介绍DOM的基础内容,所以就不一一详细说明各个方法和属性了(学习DOM的封装的,一般都对基础DOM比较熟悉了)。

    1.1、获取页面中元素的方法

    • document.getElementById()

    • context.getElementsByTagName():把指定容器中子孙容器中所有标签名为TAGNAME的都获取到了

    • document.getElementsByName():在IE浏览器中只对表单元素的name起作用

    • document.body || document.documentElement

    • context.querySelector()、context.querySelectorAll():在IE6~8下不兼容,通过这个获取到的节点集合,不存在DOM映射

    1.2、描述节点和节点之间关系的属性

    • childNodes

    • children:在IE6~8下获取的结果和标准浏览器获取的结果不一致

    • parentNode

    • previousSibling/previousElementSibling

    • nextSibling/nextElementSibling

    • lastChild/lastElementChild

    • firstChild/firstElementChild

    <h5>注意:</h5>

    1. 在标准浏览器中会把空格和换行当做文本节点处理

    2. 所有带Element的属性在IE6~8中都不兼容

    1.3、DOM的增删改

    • createElement

    • document.createDocumentFragment()

    • appendChild

    • insertBefore

    • cloneNode(true/false)

    • replaceChild

    • removeChild

    • get/set/removeAttribute

    二、获取元素的方法封装

    2.1、获取某一个容器的元素子节点(children)

    首先获取所有的子节点(childNodes),在所有的子节点中,把元素节点过滤出来(nodeType===1的时候代表的是元素节点)。

    如果该方法传递两个参数,第二个参数是一个标签名的话,我们还要在获取的子元素中把对应标签名进行二次筛选。

    function children(curEle, tagName) {
        var ary = [];
        // --> IE6~8不能使用内置的children属性,我们自己写代码实现
        if(/MSIE (6|7|8)/i.test(navigator.userAgent)) {
            var nodeList = curEle.childNodes;
            for (var i = 0, len = nodeList.length; i < len; i++){
                var curNode = nodeList[i];
                if (curNode.nodeType === 1) {
                    ary[ary.length] = curNode;
                }
            }
            nodeList = null;
        } else {
            // 标准浏览器中,我们直接使用children即可,但是这样获取的是一个元素集合(类数组),为了和IE6~8下保持一致,我们借用数组原型上的slice,实现把类数组转换为数组。
            ary = Array.prototype.slice.call(curEle.children);
        }
        
        // --> 二次筛选
        if (typeof tagName === 'string') {
            for (var k = 0; k < ary.length; k++) { // 数组长度会减小的情况,必须要使用这样的方式判断长度,不能和上面一样
                var curEleNode = ary[k];
                if (curEleNode.nodeName.toLowerCase() !== tagName.toLowerCase()) {
                    // --> 不是我想要的标签
                    ary.splice(k, 1);
                    k --;
                }
            }
        }
        
        return ary;
    }
    

    这个方法中,判断是否是IE6~8用的是正则:/MSIE (6|7|8)/i.test(navigator.userAgent),而在封装DOM库的时候,在很多地方都要判断是否是IE68,我们可以每次都判断一次是不是IE68....不过,如果我们只判断一次,然后在所有的方法中都用,那是不是更好呢?....这就是JavaScript库封装中常用的惰性思想

    2.2、封装库的惰性思想

    我们封装库的方式使用单例的方式,然后将单例放到一个自执行函数中,基本的形式为:

    var utils = (function () {
        function children(curEle, tagName) {
            // ...
        }
        
        return {
            children: children
        }
    })();
    

    这时候我们可以将判断是否是IE6~8的方法,放在自执行函数中,然后在具体的方法中使用:

    var utils = (function () {
        var flag = "getComputedStyle" in window;
        function children(curEle, tagName) {
            if(!flag){
                // IE6~8的情况
            } else {
                // 非IE6~8
            }
        }
        
        return {
            children: children
        }
    })();
    

    在自执行函数中的flag变量不销毁,存储的是判断当前浏览器是否兼容getComputedStyle,兼容的话是标准浏览器,但是如果flag=false,说明当前浏览器是IE6~8。

    惰性思想:惰性思想是JavaScript搞基编程技巧之一,在第一次给utils赋值的时候就已经把兼容处理好了,把最后结果存放在flag变量中,以后在每一个方法中,只要是IE6~8不兼容的,我们不需要重新检测,只需要使用flag的值即可。

    那么用这种方式来改造当前的类库:

    var utils = (function () {
        var flag = "getComputedStyle" in window;
        function children(curEle, tagName) {
            var ary = [];
            if (!flag) {
                var nodeList = curEle.childNodes;
                for (var i = 0, len = nodeList.length; i < len; i++) {
                    var curNode = nodeList[i];
                    curNode.nodeType === 1 ? ary[ary.length] = curNode : null;
                }
                nodeList = null;
            } else {
                ary = this.listToArray(curEle.children);
            }
            if (typeof tagName === "string") {
                for (var k = 0; k < ary.length; k++) {
                    var curEleNode = ary[k];
                    if (curEleNode.nodeName.toLowerCase() !== tagName.toLowerCase()) {
                        ary.splice(k, 1);
                        k--;
                    }
                }
            }
            return ary;
        }
    
        return {
            children: children
        }
    })();
    

    将之前几篇文章中封装的几个工具方法也加入该类库中(文章最后有该类库的下载地址):

    工具方法加入类库.png

    2.3、获取上一个兄弟元素节点(prev)

    首先获取当前元素的上一个哥哥节点,判断是否为元素节点,不是的话基于当前的节点继续找上面的哥哥节点.....一直找到哥哥元素节点为止,如果没有哥哥节点,返回null即可。

    function prev(curEle) {
        if (flag) {
            return curEle.previousElementSibling;
        }
        var pre = curEle.previousSibling;
        while (pre && pre.nodeType !== 1) {
            pre = pre.previousSibling;
        }
        return pre;
    }
    

    2.4、获取下一个兄弟元素节点(next)

    function next(curEle) {
        if (flag) {
            return curEle.nextElementSibling;
        }
        var nex = curEle.nextSibling;
        while (nex && nex.nodeType !== 1) {
            nex = nex.nextSibling;
        }
        return nex;
    }
    

    2.5、获取前面所有的兄弟元素节点(prevAll)

    function prevAll(curEle) {
        var ary = [];
        var pre = this.prev(curEle);
        while (pre) {
            ary.unshift(pre);
            pre = this.prev(pre);
        }
        return ary;
    }
    

    2.6、获取后面所有的兄弟元素节点(nextAll)

    function nextAll(curEle) {
        var ary = [];
        var nex = this.next(curEle);
        while (nex) {
            ary.push(nex);
            nex = this.next(nex);
        }
        return ary;
    }
    

    2.7、获取相邻的两个元素节点(sibling)

    function sibling(curEle) {
        var pre = this.prev(curEle);
        var nex = this.next(curEle);
        var ary = [];
        pre ? ary.push(pre) : null;
        nex ? ary.push(nex) : null;
        return ary;
    }
    

    2.8、获取所有的兄弟元素节点(siblings)

    function siblings(curEle) {
        return this.prevAll(curEle).concat(this.nextAll(curEle));
    }
    

    2.9、获取当前元素的索引(index)

    function index(curEle) {
        return this.prevAll(curEle).length;
    }
    

    2.10、获取第一个元素子节点(firstChild)

    function firstChild(curEle) {
        var chs = this.children(curEle);
        return chs.length > 0 ? chs[0] : null;
    }
    

    2.11、获取最后一个元素子节点(lastChild)

    function lastChild(curEle) {
        var chs = this.children(curEle);
        return chs.length > 0 ? chs[chs.length - 1] : null;
    }
    

    三、操作元素的方法封装

    3.1、向指定容器的末尾追加元素(append)

    function append(newEle, container) {
        container.appendChild(newEle);
    }
    

    3.2、向指定容器的开头追加元素(prepend)

    把新的元素添加到容器中第一个子元素节点的前面,如果一个元素子节点都没有,就放在末尾即可。

    function prepend(newEle, container) {
        var fir = this.firstChild(container);
        if (fir) {
            container.insertBefore(newEle, fir);
            return;
        }
        container.appendChild(newEle);
    }
    

    3.3、把新元素(newEle)追加到指定元素(oldEle)的前面(insertBefore)

    function insertBefore(newEle, oldEle) {
        oldEle.parentNode.insertBefore(newEle, oldEle);
    }
    

    3.4、把新元素(newEle)追加到指定元素(oldEle)的后面(insertAfter)

    相当于追加到oldEle弟弟元素的前面,如果弟弟不存在,也就是当前元素已经是最后一个了,我们把新的元素放在最末尾即可。

    function insertAfter(newEle, oldEle) {
        var nex = this.next(oldEle);
        if (nex) {
            oldEle.parentNode.insertBefore(newEle, nex);
            return;
        }
        oldEle.parentNode.appendChild(newEle);
    }
    

    四、操作样式的方法封装

    4.1、验证当前元素中是否包含className这个样式类名(hasClass)

    function hasClass(curEle, className) {
        var reg = new RegExp("(^| +)" + className + "( +|$)");
        return reg.test(curEle.className);
    }
    

    4.2、给元素增加样式类名(addClass)

    function addClass(curEle, className) {
        var ary = className.replace(/(^ +| +$)/g, "").split(/ +/g);
        for (var i = 0, len = ary.length; i < len; i++) {
            var curName = ary[i];
            if (!this.hasClass(curEle, curName)) {
                curEle.className += " " + curName;
            }
        }
    }
    

    4.3、给元素移除样式类名(removeClass)

    function removeClass(curEle, className) {
        var ary = className.replace(/(^ +| +$)/g, "").split(/ +/g);
        for (var i = 0, len = ary.length; i < len; i++) {
            var curName = ary[i];
            if (this.hasClass(curEle, curName)) {
                var reg = new RegExp("(^| +)" + curName + "( +|$)", "g");
                curEle.className = curEle.className.replace(reg, " ");
            }
        }
    }
    

    4.5、通过元素的样式类名获取一组元素集合(getElementsByClass)

    function getElementsByClass(strClass, context) {
        context = context || document;
        if (flag) {
            return this.listToArray(context.getElementsByClassName(strClass));
        }
        //->IE6~8
        var ary = [], strClassAry = strClass.replace(/(^ +| +$)/g, "").split(/ +/g);
        var nodeList = context.getElementsByTagName("*");
        for (var i = 0, len = nodeList.length; i < len; i++) {
            var curNode = nodeList[i];
            var isOk = true;
            for (var k = 0; k < strClassAry.length; k++) {
                var reg = new RegExp("(^| +)" + strClassAry[k] + "( +|$)");
                if (!reg.test(curNode.className)) {
                    isOk = false;
                    break;
                }
            }
            if (isOk) {
                ary[ary.length] = curNode;
            }
        }
        return ary;
    }
    

    五、样式的设置和获取相关的方法

    5.1、获取元素的样式值(getCss)

    function getCss(attr) {
        var val = null, reg = null;
        if (flag) {
            val = window.getComputedStyle(this, null)[attr];
        } else {
            if (attr === "opacity") {
                val = this.currentStyle["filter"];
                reg = /^alpha\(opacity=(\d+(?:\.\d+)?)\)$/;
                val = reg.test(val) ? reg.exec(val)[1] / 100 : 1;
            } else {
                val = this.currentStyle[attr];
            }
        }
        reg = /^(-?\d+(\.\d+)?)(px|pt|em|rem)?$/;
        return reg.test(val) ? parseFloat(val) : val;
    }
    

    5.2、给当前元素的某一个样式属性设置值(增加在行内样式上的)

    function setCss(attr, value) {
        if (attr === "float") {
            this["style"]["cssFloat"] = value;
            this["style"]["styleFloat"] = value;
            return;
        }
        if (attr === "opacity") {
            this["style"]["opacity"] = value;
            this["style"]["filter"] = "alpha(opacity=" + value * 100 + ")";
            return;
        }
        var reg = /^(width|height|top|bottom|left|right|((margin|padding)(Top|Bottom|Left|Right)?))$/;
        if (reg.test(attr)) {
            if (!isNaN(value)) {
                value += "px";
            }
        }
        this["style"][attr] = value;
    }
    

    5.3、给当前元素批量的设置样式属性值(setGroupCss)

    function setGroupCss(options) {
        for (var key in options) {
            if (options.hasOwnProperty(key)) {
                setCss.call(this, key, options[key]);
            }
        }
    }
    

    5.4、实现了获取、单独设置、批量设置元素的样式值(css)

    function css(curEle) {
        var argTwo = arguments[1], ary = Array.prototype.slice.call(arguments, 1);
        if (typeof argTwo === "string") {
            if (typeof arguments[2] === 'undefined') {
                return getCss.apply(curEle, ary);
            }
            setCss.apply(curEle, ary);
        }
        argTwo = argTwo || 0;
        if (argTwo.toString() === "[object Object]") {
            setGroupCss.apply(curEle, ary);
        }
    }
    

    六、总结

    以上的所有方法中,方法名大部分和jQuery中的类似,getElementsByClass方法在jQuery中没有实现,但是jQuery的一部分选择器也是基于这个方法原理来实现的。

    firstChild、lastChild在jQuery中没有。

    所有代码的下载地址为:https://git.oschina.net/iceman5531/My_-Library.git


    个人公众号(icemanFE):分享更多的前端技术和生活感悟

    个人公众号.png

    相关文章

      网友评论

        本文标题:DOM操作和样式操作库的封装

        本文链接:https://www.haomeiwen.com/subject/zrongttx.html