1、使用XHR发起对 /api 发起GET请求 与 POST 请求:
XHR: 创、开、配、达、错、发
回调函数写法:
let xhr = new XMLHTTPRequest();
xhr.open("GET", url, true);
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
let state = xhr.readyState;
if(state === 4) {
if(this.status === 200 || this.status === 304) callback(this.response);
else console.log("error"+this.statusText);
}
};
xhr.onerror = function() {
console.log(this.statusText);
};
xhr.send();
Promise写法:
function ajax(url) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHTTPRequest();
xhr.open("GET", url, true);
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
let state = xhr.readyState;
if(state === 4) {
if(this.status===200 || this.status===304) resolve(this.response);
else reject(this.statusText);
}
}
xhr.onerror = function() {
reject(this.statusText);
}
xhr.send();
});
}
2、引用类型的拷贝
(1)浅拷贝
let arr1 = [];
let arr2 = arr1;
(2)仅适合一维数组、一维对象的深拷贝
let arr1 = [1, 2, 3];
let arr2 = arr1.slice();
let arr3 = Object.assign([], arr1);
let arr4 = [].concat(arr1);
let arr5 = [...arr1];
(3)多维数组、多维对象的深拷贝
JSON.parse(JSON.stringify( obj ));
是一种有损的方法,当要拷贝的对象中有 Date对象、Error对象、函数、undefined、NaN、infinity、循环引用 等,是不能使用的,那我们索性就不用吧。
常用的地方有 LocalStorage 和 SessionStorage 需要用到。
function deepClone(obj) {
if(obj===null || typeof obj!=="function" && typeof obj!=="object") return obj;
let res = Array.isArray(obj) ? [] : {};
for(let i of Object.keys(obj)) {
res[i] = deepClone(obj[i]);
}
return res;
}
循环引用,相互引用就是这个有向图存在环,用带visited数组的dfs就可以搞定了。
function deepclone(obj) {
let map = new Map();
function dfs(obj) {
if(obj === null) return obj;
if(typeof obj!=="object" && typeof obj!=="function") return obj;
if(map.has(obj)) return map.get(obj);
let res = Array.isArray(obj) ? [] : {};
map.set(obj, res);
for(let i of Object.keys(obj)) res[i] = dfs(obj[i]);
return res;
}
let res = dfs(obj);
return res;
}
3、debounce与throttle
先是概念,防抖的概念是,某事件触发后,需要等待n秒再执行,如果在等待时间内被触发了,那就需要重新等待n秒再执行,以此类推
适用场景:监听窗口大小调整resize事件、屏幕滚动scroll事件、鼠标移动事件mousemove
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) {
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
节流的概念是,将时间分段,每段n秒,每段时间内只能最多响应一次事件,至于响应第几次可以自定义。
监听input输入
可以相应第一次,最后一次如何响应暂时不知。
function throttle(fn, ms) {
let pre = Date.now();
let isfirst = true;
return function() {
let ctx = this;
let args = arguments;
let cur = Date.now();
if(isfirst) {
isfirst = false;
return fn.call(ctx, args);
}
if(cur-pre >= ms) {
pre = Date.now();
return fn.call(ctx, args);
}
}
}
4、浏览器事件循环算法
事件循环中至少涉及以下角色:执行栈、宏任务队列,微任务队列、异步代码、同步代码。
script文件会按顺序执行,遇到同步代码直接入栈执行,执行完后出栈
遇到异步代码会先触发执行,然后挂起,直到收到成功或者失败事件后,将回调加入宏任务队列和微任务队列
像 setTimeout、setInterval 的回调就是加入宏任务队列
像 Promise.then 的回调就是加入微任务队列
执行栈处理两种回调任务的算法是这样的:
如果执行栈为空,先检查微任务队列是否为空,如果不为空,则一个个的入栈执行,执行完出栈,直到微任务队列为空
再取一个宏任务来执行,执行完后,再检查微任务队列是否为空,为空清空,不为空则再取一个宏任务入栈执行,以此类推。
因此,我们可以得出一个应用:
如果不紧急的常规任务,我们可以将他设置为宏任务来处理。
如果是紧急任务,我们就可以给他设置成微任务A,加在微任务队列的队尾,A将在之前的微任务之后执行,在所有的宏任务之前执行。
Lazyman
5、setTimeout 与 setInterval 互相实现
实现setTimeout
function _setTimeout(fn, ms) {
let timer = setInterval(function() {
fn();
clearInterval(timer);
}, ms);
}
实现setInterval (递归setTimeout)
function _setInterval(fn, ms) {
setTimeout(function run() {
fn();
setTimeout(run, ms);
}, ms);
}
递归setTimeout 与 直接使用 setInerval 的区别是什么呢?
当我们fn可能耗时过长,超出我们设定的时长时,我们可以使用递归setTimeout,递归setTimeout不仅能保证每次运行的间隔一样,还能每轮递归将间隔时间按一定的规律变化,而setInterval的间隔时间是把fn的执行时间给包含进去的。
6、图片懒加载
图片懒加载的原理实际上可以抽象成两个矩形是否重叠的问题。
有两个矩形,一个是viewport,一个是要懒加载的图片盒子
那么,当两个矩形重叠时,将 img 的 src 用自定义属性 data-src 来替换即可完成加载。
其实,想要用户体验好些的话,可以预留距离 Δd 来进行图片加载。