一、写一个通用的节流函数(冷却)
const throttle = (fn, delay) => {
let timer = null;
return function () {
let context = this;
let args = arguments;
if(timer) return;
fn.apply(context, args); // 立刻执行的节流
timer = setTimeout(()=>{
fn.apply(context, args); // 延迟执行的节流
timer = null;
}, delay);
}
}
// 进阶:用Date对象来计算时间间隔
const throttle = (fn, delay) => {
let pre = 0;
return function () {
let context = this;
let args = arguments;
let now = new Date();
if(now - pre > delay) {
fn.apply(context, args); // 立刻执行的节流
pre = now;
}
}
}
const thr = throttle(fn, 300);
应用场景:重复点击(链接、按钮、抢购),监听页面滚动
二、写一个通用的防抖函数(打断)
const debounce = (fn, time) => {
let timer = null;
return function () {
if(timer) clearTimeout(timer);
let context = this;
let args = arguments;
timer = setTimeout(()=>{
// 不需this
fn.apply(undefined, args); // 这个的this最终会变成window
// 需要this
fn.apply(context, args);
}, time);
}
}
const deb = debounce(fn, 3000);
应用场景:页面尺寸变化(拖动窗口,拖动停止之后再实现某个效果) 解决问题:(节流和防抖)减少不必要的计算和响应,提高性能和避免阻塞。
三、写一个发布订阅模式
const eventHub = {
map: {
// click: [f1, f2],
},
on: (eventName, fn) => {
eventHub.map[eventName] = eventHub.map[eventName] || [];
eventHub.map[eventName].push(fn);
},
emit: (eventName, args) => {
const q = eventHub.map[eventName];
if(!q) return;
q.map(f => f.call(undefined, args));
},
once: (fn, args, _this) => {
let result, executed;
return function() {
if(executed) {
return result;
}
if(!_this) _this = {}
executed = true;
result = fn.apply(_this, args);
fn = null;
return result;
}
}
off: (eventName, fn) => {
const q = eventHub.map[eventName];
if(!q) return; // 先确定有无这类event
const index = q.indexOf(fn);
if(index<0) return; // 再确定这类event有无此函数
q.splice(index,1); // 然后再取消订阅
}
}
// 开始订阅
eventHub.on('click', console.log);
eventHub.on('click', console.error);
//发布订阅
setTimeout(()=>{
eventHub.emit('click', 'Harry');
}, 3000)
class EventHub {
map = {}
on(event, fn){
this.map[event] = this.map[event] || [];
this.map[event].push(fn);
}
emit(event, data){
const fnList = this.map[event] || [];
const index = fnList.indexOf(fn);
if(index < 0) return;
fnList.splice(index, 1);
}
off(event, fn){
const fnList = this.map[event] || [];
const index = fnList.indexOf(fn);
if(index < 0) return;
fnList.splice(index, 1);
}
}
// 使用
const eventHub = new EventHub();
eventHub.on('click', (name)=>{ console.log('hi'+name);});
eventHub.on('click', (name)=>{ console.log('bye'+name);});
setTimeout(()=>{
eventHub.emit('click', 'Harry');
}, 3000)
四、写一个AJAX
const ajax = (method, url, data, success, fail) => {
const xhr = new XMLHttpRequest();
xhr.open(method,url);
xhr.send(data);
xhr.onreadystatechange = function() {
// 注:
/*
* xhr.readyStatus === 0 尚未调用 open 方法
* xhr.readyStatus === 1 已调用open但还未发送请求
* xhr.readyStatus === 2 已发送请求(已调用 send)
* xhr.readyStatus === 3 已经收到请求返回的数据
* xhr.readyStatus === 4 请求已完成
*/
if(xhr.readyStatus === 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)
success(xhr.responseText)
else
fail(xhr.responseText)
}
}
//超时时间单位为毫秒
xhr.timeout = 1000
//当请求超时时,会触发ontimeout方法
xhr.ontimeout = () => console.log('请求超时')
}
五、写一个Promise
六、写一个Promise.all
1. 基本用法
let promiseList = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
Promise.all(promiseList).then(result=>{console.log(result)}, reason=>{console.log(reason)})
// [1,2,3]
2. 手写实现
Promise.all2 = (promiseList) => {
return new Promise((resolve, reject)=>{
// 定义一个数组保存结果
const result = [];
const length = promiseList.length;
// 执行每一个promise并保存结果
promiseList.forEach( (promise,index) => {
promise.then(value => {
result[index] = value;
// 结果数组的长度等于原本promise数组的长度说明没有rejected
// 直接返回结果数组
if(result.length === length){
resolve(result)
}
}, reason => {
// 有一个rejected直接reject
reject(reason);
})
})
})
}
// 测试代码
let promiseList = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
Promise.all(promiseList).then(result=>{console.log(result)}, reason=>{console.error(reason)})
七、数组去重
1. 使用Map
var arr = [1, '1', 2, 2, '3', 3, 4, 5, 5, 6];
Array.uniq = (arr) => {
const map = new Map();
for (let ele of arr) {
if(!ele) continue;
if(map.has(ele)) continue;
map.set(ele, true);
}
return [...map.keys()];
}
const res = Array.uniq(arr);
console.log(res);
2. 使用对象属性不能重复
Array.prototype.distinct = function (){
let arr = this;
let obj = {};
let result = [];
for(let i = 0; i< arr.length; i++){
//如果能查找到,证明数组元素重复了;
// 找不到(即undefined),就加进数组内
if(!obj[arr[i]]){
obj[arr[i]] = true;
result.push(arr[i]);
}
}
return result;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1];
var b = a.distinct();
console.log(b); //[1,2,3,4,5,6,56]
3. ES5的filter
function distinctArray(arr) {
let res = arr.filter((val, ind, arr) => {
let bool = arr.indexOf(val) === ind;
// console.log(bool);
return bool;
});
return res;
}
4. ES6的set
function dedup(array){
return Array.from(new Set(array));
}
let arr = [1,2,3,3];
let resultarr = [...new Set(arr)];
八、写一个深拷贝
使用递归
function deepClone(x,cache){
// 缓存不能全局创建,最好第一次创建并递归传递
if(!cache) {
cache = new Map();
}
// 使用map来避免拷贝环形引用(如:x.self = x)的递归栈溢出
if(cache.has(x)){
return cache.get(x);
}
// 声明result变量
let result
// 先判断x是普通类型还是引用类型
if(x instanceof Object){
// 如果是引用类型还要判断是哪一种引用类型,再来初始化result
if(x instanceof Function){
// 判断是普通函数还是箭头函数
if(x.prototype) { // 普通函数才有prototype
result = function() {return x.apply(this, arguments)};
} else {
result = (...args) => {return x.apply(undefined, args)}; // 注:context是undefined
}
} else if (Array.isArray(x)){
result = [];
} else if (x instanceof Date) {
result = new Date(x-0); // 使用了时间戳
} else if (x instanceof RegExp) {
result = new RegExp(x.source, x.flags);
} else { // 排除以上情况,就是普通对象
result = {}
}
// 记录被拷贝的数据(x)和拷贝的结果(result)
cache.set(x, result);
// 开始递归拷贝
for(let key in x){
if(x.hasOwnProperty(key)){
result[key] = deepClone(x[key], cache);
}
}
return result;
} else {
// 普通类型直接返回就好
return x;
}
}