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

注意:
  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库封装中常用的惰性思想。
————————————————————————————————

注:

DOM是HTML节点的一种规划方式,三种节点的解析大致如下图(来源于网络)

可以得出:

(1)元素节点:一般是拥有一对开闭合标签的元素整体,例如上图中的<ul>...</ul>这一部分都属于一个元素节点。

(2)属性节点:一般是元素节点的属性,比如上图中的“宽度”属性 也就是width="80 px"这一部分。

(3)文本节点:DOM中用于呈现文本的部分,如上图中的 “点我” 儿子,一般被包含在元素节点的开闭合标签内部。
————————————————————————————————

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
    }
})();

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

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);
    }
}

全部代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,727评论 1 92
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,421评论 1 45
  • 一、样式篇 第1章 初识jQuery (1)环境搭建 进入官方网站获取最新的版本 http://jquery.co...
    凛0_0阅读 3,352评论 0 44
  • 不言摄影于大连七七街,今晨
    不言简书阅读 344评论 2 3
  • 爸爸和他的一个伙计在这边的一个装饰城里面开了一家地毯店,今天,爸爸就带我过去了。 爸爸带我看了店,店比我家大多了,...
    小绘君阅读 286评论 0 2