JS中的THIS处理及正则表达式 — 2、综合实战:华为商城产品排序

259 阅读18分钟

1、整体页面结构

需要用到ajax、json处理、es6模板字符串拼接、sort排序原理、升降序切换(涉及到很多编程思想编程技巧)

目录

2、AJAX获取数据和ES6模板字符串拼接

index.js

// 一、获取数据,然后动态展示在页面中
// =>获取数据
var xhr = new XMLHttpRequest();
xhr.open('get','json/product.json',false);
xhr.onreadystatechange = function () {
    if(xhr.readyState === 4 && xhr.status === 200) {
        var result = xhr.responseText;//json格式的字符串
        window.result = utils.toJSON(result);//把当前结果暴露到全局,外面才可以使用
        // window.result = result;
    }
};
xhr.send(null);

// =>数据绑定:ES6中的模板字符串(原理:传统的字符串拼接)
var listBox = document.getElementById('list');
var str = ``;//这不是单引号而是撇
for (var i = 0; i < result.length; i++) {
    var item = result[i];
    // 在数据绑定的时候,我们把价格、热度、上架时间等信息存储在当前LI的自定义属性上
    // (设置自定义属性的名字最好是data-xxx),后续的某些操作中国如果需要用到这些值,
    // 直接的从自定义属性中获取即可
    str += `<li data-price="${item.price}" data-hot="${item.hot}" data-time="${item.time}">
                <a href="javascript:;">
                    <img src="${item.img}" alt="">
                    <p>${item.title}</p>
                    <span>¥${item.price}</span>
                </a>
            </li>`;
}
listBox.innerHTML = str;
}();

3、SORT排序的原理

SORT实现的原理

每一次拿出数组中的当前项和后一项,每一次这样的操作都会让传递的匿名函数执行一次,不仅执行,

而且还给这个匿名函数传递了两个实参:

a => 本次拿出的当前项

b => 本次拿出的后一项

在匿名函数中,如果我return的结果是一个>0的数,让a和b交换位置;反之返回<=0的值,a和b的位置不变;

knowledge-sort.js、1-sort.html

var ary = [12, 13, 24, 11, 16, 28, 10];
ary.sort(function (a, b) {
    console.log(a, b);
    // return a - b;
    return 1;//<=>ary.reverse 把整个数组倒过来排列
});
console.log(ary);

面试题1:把一个数组随机打乱

knowledge-sort.js、1-sort.html

var ary = [12, 13, 24, 11, 16, 28, 10];
ary.sort(function () {
    // 每一次返回一个随机创建的大于零或者小于零的数即可
    return Math.round(Math.random() * (10) - 5);
});
console.log(ary);

面试题2:把数组按照年龄升序排序、按照姓名排序

knowledge-sort.js、1-sort.html

var ary = [
    {
        name:"唐元帅",
        age:48
    },
    {
        name:"卢勇勇",
        age:29
    },
    {
        name:"陈景光",
        age:63
    }
];

// 把数组按照年龄升序排序
ary.sort(function (a, b) {
    return a.age - b.age;
});
console.log(ary);

// 按照姓名排序
ary.sort(function (a, b) {
    var curName = a.name;
        nextName = b.name;
    return curName.localeCompare(nextName);
    // localeCompare字符串的方法,可以用来比较两个字符串的大小
});
console.log(ary);

4、简单实现按照价格的升序排列

思想:在数据绑定时把后面要用到的数据先提前存放到它的自定义属性上(自定义属性思想);

排序:获取所有的li元素,元素集合是类数组需要转换成数组调用sort方法进行排序,排序的过程中, 从之前存储的自定义属性中获取价格按价格升序排序,循环排序后的数组添加到容器中实现页面排序的效果

接 index.js

~function () {
    var listBox = document.getElementById('list'),
        oList = listBox.getElementsByTagName('li');
        //类数组不能直接用sort方法,先转化成数组
        oList = utils.toArray(oList);
        // console.log(oList);
        oList.sort(function (a, b) {
            // a:当前这个li
            // b:下一个li
            var curPrice = a.getAttribute('data-price'),
                nextPrice = b.getAttribute('data-price');
            return curPrice - nextPrice;
        });
        // console.log(oList);
        // 按最新的数组顺序循环,把每次循环的结构依次添加到容器末尾
        for (var i = 0; i < oList.length; i++) {
            listBox.appendChild(oList[i]);//由于DOM的映射机制,我们在JS中把某一个LI元素
            // 对象(和页面中的LI标签一一映射)增加的容器的末尾,相当于把页面中的映射的标签挪到
            // 容器的末尾,所以不是新增而是位置的改变(如果当前这个元素对象是新创建的,页面中并
            // 没有这个标签,这时候用appendChild就是新增。如果当前这个元素对象是页面当中已经有
            // 了的,用appendChild就是挪位置,这是dom映射决定的)
        }
}();

5、DOM的映射机制

映射原理:浏览器在渲染页面的时候给每一个元素都设置了很多内置属性(包含样式的),当我们在JS中把堆内存中的某一个内置属性的值修改了,大部分情况下,浏览器都会监听到你的修改,然后按照最新修改的值重新渲染页面中的元素

knowledge-dom.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOM映射</title>
</head>
<body>

<!-- 
    DOM映射机制:
        在js中获取到的元素对象或者元素集合一般都和页面中的HTML结构存在映射关系(一个变另外一个也会跟着变) 
-->
<div id="box">哈哈哈哈</div>
<div>呵呵呵呵</div>
<script>
    // var oBox = document.getElementById('box');
    // // oBox是JS中的一个对象
    // oBox.style.backgroundColor = 'orange';//修改oBox中的style中的backgroundColor属性值为'orange'(把oBox
    // // 堆内存中的某些东西改了) 但是这样操作完成后:页面中的DIV背景颜色修改为橙色。(通过document.getElementBy
    // // Id('box')获得到dom元素和页面中id="box"的div这个结构标签存在一一对应的映射关系,浏览器会做一个监听,监听
    // // 当前在js当中获取到的dom对象,如果把这个对象堆内存当中的某一个属性改了,浏览器会根据改变值把页面结构从新渲染和解析)

    // //---------------------------------------------------------------------------------------------------

    // 映射原理:浏览器在渲染页面的时候给每一个元素都设置了很多内置属性(包含样式的),当我们在JS中把堆内存中的某一
    // 个内置属性的值修改了,大部分情况下,浏览器都会监听到你的修改,然后按照最新修改的值重新渲染页面中的元素

    // //---------------------------------------------------------------------------------------------------

    // var oList = document.getElementsByTagName('div');
    // console.log(oList.length);//2
    // // 当我们把页面中的html结构通过某些操作修改了(删除或者增加),我们无需重新获取元素集合,oList中的内容会自动跟着修改
    // // 删掉一个后
    // console.log(oList.length);//1

    // //---------------------------------------------------------------------------------------------------

    var oList = document.querySelectorAll("div");
    // 通过querySelectorAll获取到的元素集合(节点集合)不存在DOM的映射机制,因为获取到的集合不是标准的NodeList,
    // 而是属于StaticNodeList(静态的集合)

    // 操作真实的DOM在项目中是比较消耗性能的(尽量减少)

</script>
</body>
</html>

6、DOM的重绘回流以及文档碎片

knowledge-reflow.html

我们操作DOM或者修改DOM,基本上就是触发它的重绘和回流机制

重绘: 当一个元素的样式(特点:只有那些不修改元素位置的样式)发生改变的时候,浏览器会把当前元素 重新的进行渲染(DOM性能消耗低)

回流: 当一个元素的位置发生改变,浏览器会重新把整个页面的DOM结构进行计算,计算出所有元素的最新位置, 然后再渲染(DOM性能消耗非常大)

1、新增或者删除一些元素

2、把现有元素的位置改变

...

listBox.appendChild(oList[i])时每一次添加都会从新挪动li的位置,一共有oList.length个li,相当于在当前listBox盒子当中从新挪了oList.length次,触发oList.length次回流,这样操作性能不好,所以我们使用文档碎片,把每一个li先追加到文档碎片当中,最后把当前文档碎片中的内容统一一次性添加到页面中(只触发一次DOM回流):如图

面试官可能会问: 想像当前容器中再追加十万个元素,为了考虑很大性能的消耗和浏览器渲染问题怎么做?

答: 文档碎片这种方式也没有那么快,因为有十几万个元素,最好的方式就是先分批,不一次追加十万个,先追加一千个分一百次追加,一千个追加量也很大,考虑到dom的回流问题,就可以用文档碎片来处理或者用字符串拼接来处理。但是字符串是用innerhtml+=xxx,是把之前的拿出来和现在拼起然后再从新放进去,性能消耗更大,所以我们还是用文档碎片来解决。(文档碎片在项目中处理大量渲染的时候还是有优化的作用)

7、实现单列升降序切换

通过 linkList[1].myMethod = -1; 这个自定义属性来做升级(另外一种思路:可以通过一个标识判断来替换升序和降序)

8、关于this的处理

或者通过箭头函数

9、实现多列的升降序切换

根据索引找到对应的属性值进行循环判断

10、细节优化以及扩展

代码汇总

index.css

*{padding: 0;margin: 0;list-style: none;}
body{background: gainsboro;}
.container{width: 1200px;margin: 0 auto;padding: 20px 0;}
.container .header{height: 50px;line-height: 50px; width: 100%;background: #ffffff;}
.container .header span{padding: 0 15px;margin-right: 15px;}
.container .header a{color: #333333;text-decoration: none;position: relative;padding-right: 12px;margin-right: 10px;}
.container .header a:hover{color:red;}
.container .header .up{
    width: 0;height: 0;border-left:3px solid transparent;border-right: 3px solid transparent;border-top: 6px solid #333333;line-height: 0;
    position: absolute;right: 0;bottom: 3px;
}
.container .header .down{
    width: 0;height: 0;border-left:3px solid transparent;border-right: 3px solid transparent;border-bottom: 6px solid #333333;line-height: 0;
    position: absolute;right: 0;top: 3px;
}

.container .header i.up.bg{border-top-color:red;}
.container .header i.down.bg{border-bottom-color:red;}


.container .list{margin-left: -10px; margin-top: 10px; overflow: hidden;}
.container .list li{width: 186px;height: 260px;margin-left: 10px;margin-top: 10px; float: left;
    background: #ffffff;border: 3px solid #ffffff;padding: 20px;}
.container .list li:hover{border-color: red;}
.container .list a{color: gray;text-decoration: none;}
.container .list img{margin: 20px auto;display: block;width: 140px;height: 140px;}
.container .list p{height: 40px;line-height:20px;overflow: hidden;}
.container .list span{color: #333333;line-height: 40px;}

index.js

"use strict";

// 一、获取数据,然后动态展示在页面中--------------------------------------------------
~function () {
    // =>获取数据
    var xhr = new XMLHttpRequest();
    xhr.open('get', 'json/product.json', false);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var result = xhr.responseText;//json格式的字符串
            window.result = utils.toJSON(result);//把当前结果暴露到全局,外面才可以使用
            // window.result = result;
        }
    };
    xhr.send(null);

    // =>数据绑定:ES6中的模板字符串(原理:传统的字符串拼接)
    var listBox = document.getElementById('list');
    var str = ``;//这不是单引号而是撇
    for (var i = 0; i < result.length; i++) {
        var item = result[i];
        // 在数据绑定的时候,我们把价格、热度、上架时间等信息存储在当前LI的自定义属性上
        // (设置自定义属性的名字最好是data-xxx),后续的某些操作中国如果需要用到这些值,
        // 直接的从自定义属性中获取即可
        str += `<li data-price="${item.price}" data-hot="${item.hot}" data-time="${item.time}">
                <a href="javascript:;">
                    <img src="${item.img}" alt="">
                    <p>${item.title}</p>
                    <span>¥${item.price}</span>
                </a>
            </li>`;
    }
    listBox.innerHTML = str;
}();


// 二、实现按照价格升序排序--------------------------------------------------

// 思想:在数据绑定时把后面要用到的数据先提前存放到它的自定义属性上(自定义属性思想);
// 排序,获取所有的li元素,元素集合是类数组需要转换成数组调用sort方法进行排序,排序的过程中,
// 从之前存储的自定义属性中获取价格按价格升序排序,循环最新数组添加到容器中实现页面排序的效果

~function () {
    var listBox = document.getElementById('list'),
        oList = listBox.getElementsByTagName('li');

    var headerBox = document.getElementById('header'),
        linkList = headerBox.getElementsByTagName('a');

    for (var i = 0; i < linkList.length; i++) {
        linkList[i].myMethod = -1;
        linkList[i].myIndex = i;//根据索引判断点击的是第几列
        linkList[i].onclick = function () {
            // this:点击的这个A标签 => linkList[1]
            this.myMethod *= -1;//可以让每一次点击的时候,当前A标签存储的自定义属性从1~-1之间来回切换

            // changePosition();
            // 让changePosition()方法中的this指向linkList[1] => 
            // changePosition.call(linkList[1]);
            changePosition.call(this);
        };
    }



    function changePosition() {
        // this:linkList[1]
        var _this = this;

        // 细节优化:点击当前A,我们需要把其他A的myMethod回归初始值,这样保证下一次在点击其它的A标签还是从升序开始的
        for (var k = 0; k < linkList.length; k++) {
            if (k !== this.myIndex) {
                // 不是当前点击的A
                linkList[k].myMethod = -1;
            }
        }

        //类数组不能直接用sort方法,先转化成数组
        oList = utils.toArray(oList);
        // console.log(oList);
        oList.sort(function (a, b) {
            // this:window (回调函数中的this一般都是window,把一个函数作为值传给另外一个方法就是回调函数)

            // 按照点击的不同列来实现排序(需要知道当前点击的是第几列)
            // 通过索引计算出按照哪一列进行排序(if else和三元运算符都可以进行判断)
            var index = _this.myIndex,
                attr = '';
            switch (index) {
                case 0:
                    attr = 'data-time';
                    break;
                case 1:
                    attr = 'data-price';
                    break;
                case 2:
                    attr = 'data-hot';
                    break;
            }


            //=============================================================

            // // a:当前这个li
            // // b:下一个li
            // var curPrice = a.getAttribute('data-price'),
            //     nextPrice = b.getAttribute('data-price');
            // // return (curPrice - nextPrice) * linkList[1].myMethod;
            // return (curPrice - nextPrice) * _this.myMethod;

            //=============================================================

            // 按照不同的排序表示获取对应的自定义属性值,然后进行升降序排序
            var cur = a.getAttribute(attr),
                next = b.getAttribute(attr);
            if (index === 0) {
                // 获取的日期值需要特殊的处理(把日期-去掉,去掉之后日期就是数字可以相减,
                // 也可以吧日期换算成毫秒再相减)
                cur = cur.replace(/-/g, "");
                next = next.replace(/-/g, "");
            }
            return (cur - next) * _this.myMethod;


        });

        //创建一个文档碎片(文档碎片:一个临时存储DOM元素的容器)
        var frg = document.createDocumentFragment();

        // console.log(oList);
        // 按最新的数组顺序循环,把每次循环的结构依次添加到容器末尾
        for (var i = 0; i < oList.length; i++) {
            // listBox.appendChild(oList[i]);//由于DOM的映射机制,我们在JS中把某一个LI元素
            // // 对象(和页面中的LI标签一一映射)增加的容器的末尾,相当于把页面中的映射的标签挪到
            // // 容器的末尾,所以不是新增而是位置的改变(如果当前这个元素对象是新创建的,页面中并
            // // 没有这个标签,这时候用appendChild就是新增。如果当前这个元素对象是页面当中已经有
            // // 了的,用appendChild就是挪位置,这是dom映射决定的)

            frg.appendChild(oList[i]);//每一次循环把每一个li先追加到文档碎片中
        }
        listBox.appendChild(frg);//循环完成后,把当前文档碎片中的内容统一一次性添加到页面中(只触发一次DOM回流)
        frg = null;//只是一个临时存储的容器,用完之后把开辟的空间小销毁掉

    }
}();

~function(){
    var nav = document.getElementById("header"),
        Onav = nav.getElementsByTagName("a");
        
    for (var i = 0; i < Onav.length; i++) {

        Onav[i].onOff = true;
        Onav[i].onclick = function () {
            
            var up = this.getElementsByClassName("up")[0],
                down = this.getElementsByClassName("down")[0];
            up.className = 'up';
            down.className = 'down';
            if(this.onOff == true){
                up.className += ' bg';
                this.onOff = false;
            }else if(this.onOff == false){
                down.className += ' bg';
                this.onOff = true;
            }

        };
    }


}();

knowledge-sort.js

var ary = [12, 13, 24, 11, 16, 28, 10];
// 每一次拿出数组中的当前项和后一项,每一次这样的操作都会让传递的匿名函数执行一次,不仅执行,
// 而且还给这个匿名函数传递了两个实参:
// a => 本次拿出的当前项
// b => 本次拿出的后一项
// 在匿名函数中,如果我return的结果是一个>0的数,让a和b交换位置;反之返回<=0的值,a和b的位置不变;
ary.sort(function (a, b) {
    console.log(a, b);
    // return a - b;
    return 1;//<=>ary.reverse 把整个数组倒过来排列
});
console.log(ary);


//--------------------------------------------------

// 随机打乱数组
var ary = [12, 13, 24, 11, 16, 28, 10];
ary.sort(function () {
    // 每一次返回一个随机创建的大于零或者小于零的数即可
    return Math.round(Math.random() * (10) - 5);
});
console.log(ary);

//--------------------------------------------------


var ary = [
    {
        name:"唐元帅",
        age:48
    },
    {
        name:"卢勇勇",
        age:29
    },
    {
        name:"陈景光",
        age:63
    }
];
// 把数组按照年龄升序排序
ary.sort(function (a, b) {
    return a.age - b.age;
});
console.log(ary);
// 按照姓名排序
ary.sort(function (a, b) {
    var curName = a.name;
        nextName = b.name;
    return curName.localeCompare(nextName);
    // localeCompare字符串的方法,可以用来比较两个字符串的大小
});
console.log(ary);

utils.js


var utils = (function () {
    // =>把类数组转换为数组(兼容所有浏览器的)
    function toArray(classAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(classAry);
        } catch (e) {
            for (var i = 0; i < classAry.length; i++) {
                ary[ary.length] = classAry[i];
            }
        }
        return ary;
    }

    // =>把JSON格式的字符串转换为JSON格式的对象
    function toJSON(str) {
        //JSON.parse()不兼容的原因是因为window下没有JSON这个属性
        return "JSON" in window ? JSON.parse(str) : eval('('+ str +')');
    }

    return {
        toArray: toArray,
        toJSON:toJSON
    }

})();

product.json

[
    {
        "id":1,
        "title":"HUAWEI Mate 10 4GB+64G全网通版(亮黑色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/1.webp.png"
    },
    {
        "id":2,
        "title":"HUAWEI 麦芒6 4GB+64G 全网通版(纯白色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/2.webp.png"
    },
    {
        "id":3,
        "title":"HUAWEI nova 青春版 4GB+64G",
        "price":1799,
        "time":"2017-02-19",
        "hot":400,
        "img":"img/3.webp.png"
    },
    {
        "id":4,
        "title":"HUAWEI 麦芒6 4GB+64G 全网通版(纯白色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":193338,
        "img":"img/4.webp.png"
    },
    {
        "id":5,
        "title":"HUAWEI Mate 10 4GB+64G全网通版(亮黑色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/1.webp.png"
    },
    {
        "id":6,
        "title":"HUAWEI 麦芒6 4GB+64G 全网通版(纯白色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/2.webp.png"
    },
    {
        "id":7,
        "title":"HUAWEI nova 青春版 4GB+64G",
        "price":1799,
        "time":"2017-02-19",
        "hot":400,
        "img":"img/3.webp.png"
    },
    {
        "id":8,
        "title":"HUAWEI 麦芒6 4GB+64G 全网通版(纯白色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":193338,
        "img":"img/4.webp.png"
    },{
        "id":9,
        "title":"HUAWEI Mate 10 4GB+64G全网通版(亮黑色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/1.webp.png"
    },
    {
        "id":10,
        "title":"HUAWEI 麦芒6 4GB+64G 全网通版(纯白色)",
        "price":3899,
        "time":"2017-03-15",
        "hot":198,
        "img":"img/2.webp.png"
    },
    {
        "id":11,
        "title":"HUAWEI nova 青春版 4GB+64G",
        "price":1799,
        "time":"2017-02-19",
        "hot":400,
        "img":"img/3.webp.png"
    }
]

1-sort.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sort实现原理</title>
</head>
<body>
<script src="js/knowledge-sort.js"></script>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>华为商城产品排序</title>
    <link rel="stylesheet" href="css/index.css" type="text/css"/>
</head>
<body>
    <div class="container">
        <!-- HEADER -->
        <div class="header clear" id="header">
            <span>排序:</span>
            <a href="javascript:;">上架时间<i class="up bg"></i><i class="down"></i></a>
            <a href="javascript:;">价格<i class="up"></i><i class="down"></i></a>
            <a href="javascript:;">热度<i class="up"></i><i class="down"></i></a>
        </div>
        <!-- LIST -->
        <ul class="list clear" id="list">
            <li>
                <a href="javascript:;">
                    <img src="img/1.webp" alt="">
                    <p>HUAWEI Mate 10 4GB+64GB 全网通版(亮黑色)</p>
                    <span>¥3899</span>
                </a>
            </li>
        </ul>
    </div>
<!-- import javascript -->
<script src="js/utils.js"></script>
<script src="js/index.js"></script>
</body>
</html>

knowledge-dom.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOM映射</title>
</head>
<body>

<!-- 
    DOM映射机制:
        在js中获取到的元素对象或者元素集合一般都和页面中的HTML结构存在映射关系(一个变另外一个也会跟着变) 
-->
<div id="box">哈哈哈哈</div>
<div>呵呵呵呵</div>
<script>
    // var oBox = document.getElementById('box');
    // // oBox是JS中的一个对象
    // oBox.style.backgroundColor = 'orange';//修改oBox中的style中的backgroundColor属性值为'orange'(把oBox
    // // 堆内存中的某些东西改了) 但是这样操作完成后:页面中的DIV背景颜色修改为橙色。(通过document.getElementBy
    // // Id('box')获得到dom元素和页面中id="box"的div这个结构标签存在一一对应的映射关系,浏览器会做一个监听,监听
    // // 当前在js当中获取到的dom对象,如果把这个对象堆内存当中的某一个属性改了,浏览器会根据改变值把页面结构从新渲染和解析)

    // //---------------------------------------------------------------------------------------------------

    // 映射原理:浏览器在渲染页面的时候给每一个元素都设置了很多内置属性(包含样式的),当我们在JS中把堆内存中的某一
    // 个内置属性的值修改了,大部分情况下,浏览器都会监听到你的修改,然后按照最新修改的值重新渲染页面中的元素

    // //---------------------------------------------------------------------------------------------------

    // var oList = document.getElementsByTagName('div');
    // console.log(oList.length);//2
    // // 当我们把页面中的html结构通过某些操作修改了(删除或者增加),我们无需重新获取元素集合,oList中的内容会自动跟着修改
    // // 删掉一个后
    // console.log(oList.length);//1

    // //---------------------------------------------------------------------------------------------------

    var oList = document.querySelectorAll("div");
    // 通过querySelectorAll获取到的元素集合(节点集合)不存在DOM的映射机制,因为获取到的集合不是标准的NodeList,
    // 而是属于StaticNodeList(静态的集合)

    // 操作真实的DOM在项目中是比较消耗性能的(尽量减少)

</script>

</body>
</html>

knowledge-reflow.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sort实现原理</title>
</head>
<body>
<!-- 
    我们操作DOM或者修改DOM,基本上就是触发它的重绘和回流机制
        重绘:当一个元素的样式(特点:只有那些不修改元素位置的样式)发生改变的时候,浏览器会把当前元素
    重新的进行渲染(DOM性能消耗低)

        回流:当一个元素的位置发生改变,浏览器会重新把整个页面的DOM结构进行计算,计算出所有元素的最新位置,
    然后再渲染(DOM性能消耗非常大)
        1、新增或者删除一些元素
        2、把现有元素的位置改变
        ... 
-->
</body>
</html>