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>
