JS中的THIS处理及正则表达式 — 2、综合实战:华为商城产品排序
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:下一个livar 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) {// 不是当前点击的AlinkList[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>
复制代码
JS中的THIS处理及正则表达式 — 2、综合实战:华为商城产品排序
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:下一个livar 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) {// 不是当前点击的AlinkList[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>
复制代码