new 操作符的过程
js
function newfun(fn, ...args) {
if (typeof fn !== "function") throw new Error("参数应该是个构造函数");
let obj = Object.create(fn.prototype);
// 创建一个空对象,空对象的原型函数的原型对象prototype
let result = fn.apply(obj, args);
// 执行构造函数,并将this指向新创建的对象
return result instanceof Object ? result : obj;
// 执行构造函数的返回值如果是对象,返回该对象。否则返回新创建的对象
}
手写 call 函数
js
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== "function") throw new TypeError("应该由函数调用");
context = context || window;
// 第一个参数为null的话,上下文默认为window
context.fn = this;
// 把函数作为上下文的一个属性调用(函数内的this指向该上下文)
let result = context.fn(...args);
// 通过上下文执行该函数,拿到返回值(重新this后的返回值)
delete context.fn;
// 删除上下文中刚刚新增的属性,防止污染上下文
return result;
};
手写 apply 函数
js
Function.prototype.myApply = function (context, args) {
if (typeof this !== "function") throw new TypeError("应该由函数调用");
context = context || window;
context.fn = this;
let result = Array.isArray(args) ? context.fn(...args) : context.fn();
delete context.fn;
return result;
};
bind 函数
let fun = fn.bind(context,arg1,arg2)的返回值是个函数,由两种调用方式:
作为普通函数调用fun()
此时 fun 中的 this 指向传入的 context 上下文作为构造函数调用new fun()
函数 fun 内的 this 指向新创建的实例对象
js
Function.prototype.myBind = function (context, ...agrs) {
if (typeof this !== "function") new TypeError("应该由函数调用");
context = context || window;
let fn = this;
return function Fn() {
context = this instanceof Fn ? this : context;
//this instanceof Fn:作为构造函数使用了
return fn.apply(context, [...agrs, ...arguments]);
};
};
实现数组方法
实现 flat 方法
js
// 递归实现
function flatArray(arr) {
let result = [];
for (const iterator of arr) {
result = result.concat(
Array.isArray(iterator) ? flatArray(iterator) : iterator
);
}
return result;
}
// reduce + 递归实现
function flatArray(arr) {
return arr.reduce((result, current) => {
return result.concat(Array.isArray(current) ? flatArray(current) : current);
}, []);
}
// 扩展运算符实现
function flatArray(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
// split和toString实现
function flatArray(arr) {
return arr.toString().split(",");
}
// 完整实现数组的flat方法,reduse+递归
function flat(arr, depth) {
if (!Array.isArray(arr) || depth <= 0) return arr;
return arr.reduce((result, current) => {
return result.concat(
Array.isArray(current) ? flat(current, depth - 1) : current
);
}, []);
}
实现数组其他方法
js
// 实现数组的push方法
Array.prototype.push = function () {
for (const iterator of arguments) {
this[this.length] = iterator;
// 遍历参数,依次把参数赋值给数组最后一项
}
return this.length;
};
// 实现数组的filter方法
Array.prototype.filter = function (fn) {
if (typeof fn !== "function") throw new TypeError("参数类型错误");
let result = [];
for (let index = 0; index < this.length; index++) {
const element = array[index];
fn(element, index, this) && result.push(element);
// fn的返回值为true,则把该element添加到返回的数组里
}
return result;
};
// 数组实现map方法
Array.prototype.map = function (fn) {
if (typeof fn !== "function") throw new TypeError("参数类型错误");
let result = [];
for (let index = 0; index < this.length; index++) {
const element = array[index];
result.push(fn(element, index, this));
// 把函数的返回值添加到返回的数组里
}
return result;
};
// 数组实现reduce方法
Array.prototype.reduce = function (fn, initialValue) {
if (typeof fn !== "function") new TypeError("参数类型错误");
let Index = [undefined, null].includes(initialValue) ? 1 : 0;
// 没初始值,则第一项作为初始值,此时Index从1开始,否则从0开始
let result = initialValue ?? this[0];
// ??判空运算符:
for (Index; Index < this.length; Index++) {
result = fn(result, this[Index], Index, this);
}
return result;
};
// 数组乱序输出
function arrayRandom(array) {
for (var index = 0; index < array.length; index++) {
let current = array[index];
let randomIndex =
Math.round(Math.random() * (array.length - 1 - index)) + index;
array[index] = array[randomIndex];
array[randomIndex] = current;
}
return array;
}
数字每千分位用逗号隔开
中国本地默认就是用逗号隔开,可调用:num.toLocalString()。
js
function formatNumber(num) {
let numToString = num.toString();
let index = numToString.indexOf(".");
if (index >= 0) {
// 存在小数,输出:整数+小数部分
return (
numToString.slice(0, index).replace(/(\d)(?=(?:\d{3})+$)/g, "$1,") +
numToString.slice(index)
);
}
return numToString.replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
}
instanceof 代码实现
js
function myInstanceofOne(left, right) {
let proto = Object.getPrototypeOf(left);
let prototype = right.prototype;
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left);
let prototype = right.prototype;
if (!proto) return false;
if (proto === prototype) return true;
return myInstanceof(proto, right);
}
防抖函数
用于短时间频繁触发事件的场景
js
// 释放注释部分,可以改为立即执行的防抖函数
function debounce(fn, wait, immediate = false) {
let timer = null;
return function () {
let context = this;
let args = [...arguments];
if (timer) {
// 存在定时器,则取消之前的定时器,重新记时
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(context, args);
// immediate = false;
// wait=immediate ? 0 : wait
}, wait);
};
}
// 防抖函数可以被requestAnimationFrame API代替,性能和体验更好,使用简单
let cancelId = requestAnimationFrame(timestamp =>
console.log("传入参数为下次执行的时间戳:", timestamp)
);
cancelAnimationFrame(cancelId); // 用于取消requestAnimationFrame API
// 完整 函数防抖
function debounce(method, wait, immediate) {
let timeout;
// debounced函数为返回值
// 使用Async/Await处理异步,如果函数异步执行,等待setTimeout执行完,拿到原函数返回值后将其返回
// args为返回函数调用时传入的参数,传给method
let debounced = function (...args) {
return new Promise(resolve => {
// 用于记录原函数执行结果
let result;
// 将method执行时this的指向设为debounce返回的函数被调用时的this指向
let context = this;
// 如果存在定时器则将其清除
if (timeout) {
clearTimeout(timeout);
}
// 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null
if (immediate) {
// 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null
// 这样确保立即执行后wait毫秒内不会被再次触发
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
// 如果满足上述两个条件,则立即执行并记录其执行结果
if (callNow) {
result = method.apply(context, args);
resolve(result);
}
} else {
// 如果immediate为false,则等待函数执行并记录其执行结果
// 并将Promise状态置为fullfilled,以使函数继续执行
timeout = setTimeout(() => {
// args是一个数组,所以使用fn.apply
// 也可写作method.call(context, ...args)
result = method.apply(context, args);
resolve(result);
}, wait);
}
});
};
// 在返回的debounced函数上添加取消方法
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
节流函数
js
// 定时器版本(默认wait内不得再次执行,wait后才执行)
function throttle(fun, wait) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
// 上移一行,可改为立即执行,
fun.apply(this, [...arguments]);
clearTimeout(timer);
timer = null;
}, wait);
}
};
}
// 节流函数时间戳版本,用于控制函数至少多久才能触发一次
function throttle(fn, delay) {
var preTime = Date.now();
return function () {
let nowTime = Date.now();
if (nowTime - preTime >= delay) {
// 如果两次时间间隔超过了指定时间,则执行函数。
fn.apply(this, [...arguments]);
preTime = nowTime;
}
};
}
sleep 函数
延迟 delay 毫秒执行 使用:await sleep(1000)
js
// sleep函数 延迟delay毫秒执行 使用:await sleep(1000)
async function sleep(delay) {
return new Promise((resolve, reject) => setTimeout(resolve, delay));
}
函数柯里化
主要有 3 个作用:
- 参数复用:参数可以重复使用
- 提前返回:提前返回函数,继续接受剩余参数
- 延迟执行:等参数个数足够时执行
- 高阶函数:返回函数可以继续接受参数
js
function curry(fun, ...args) {
//当传入函数的参数总个数 (args.length) >= 原函数所需参数个数:fn.length
//则执行fun函数;否则返回函数,继续接受参数并递归调用curry函数
return args.length >= fun.length
? fun(...args)
: (..._args) => curry(fun, ...args, ..._args);
}
实现深拷贝
考虑循环应用和 Symbol 作为 key
js
// Set缓存使用过的数据,解决循环应用,提升性能
function deepClone(obj, cache = new Set()) {
if (typeof obj !== "object" || obj === null || cache.has(obj)) return obj;
cache.add(obj);
return [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj),
].reduce(
(result, key) => {
result[key] = deepClone(obj[key], cache);
return result;
},
Array.isArray(obj) ? [] : {}
);
}
js 对象转化为树形结构
js
// 转换前:
source = [
{ id: 1, pid: 0, name: "body" },
{ id: 2, pid: 1, name: "title" },
{ id: 3, pid: 2, name: "div" },
];
// 转换为:
tree = [
{
id: 1,
pid: 0,
name: "body",
children: [
{
id: 2,
pid: 1,
name: "title",
children: [{ id: 3, pid: 1, name: "div" }],
},
],
},
];
/** one 先找出父级,把没有父级的节点(即顶级节点)组成数组返回。
* 利用引用数据类型指针,引用同一个实际储存的地址 */
function jsToTree(arr) {
if (!Array.isArray(arr)) return [];
return arr.filter(item => {
let parent = arr.find(v => v.id === item.pid);
// 找出item的父节点
if (!parent) return true;
// 父节点不存在说明是顶级节点,返回顶级节点
(parent.children || (parent.children = [])).push(item);
// 父节点存在,把当前item放进父节点里
});
}
// two 先找出所有子级,然后返回顶级节点数组,顶级节点的pid===0
function jsToTreeTwo(arr) {
if (!Array.isArray(arr)) return [];
return data.filter(parent => {
let children = data.filter(child => child.pid === parent.id);
// 找出当前节点所有子节点
children.length && (parent.children = children);
// 子节点数组赋值给children属性
return parent.pid === 0; // 返回顶级节点
});
}
// three 先找出所有父节点和子节点,把子节点push到对应的父节点,在递归找到本子节点的子节点,最后返回顶级节点数组
function jsToTreeThree(arr) {
let parentArray = arr.filter(item => item.pid === 0);
// 找出所有的顶级节点
let childrenArray = arr.filter(item => item.pid !== 0);
// 找出不是顶级的节点
function treeData(parentArr, childArr) {
parentArr.forEach(parent => {
childArr.forEach((child, index) => {
//循环遍历父级节点和非父级的节点
if (parent.id === child.pid) {
(parent.children || (parent.children = [])).push(child);
// 把找到的所有子节点添加到父节点内
}
let otherChild = JSON.parse(JSON.stringify(childArr));
// 子节点数组的深拷贝,防止下一步剔除子节点时更改原数组
otherChild.splice(index, 1);
// 把子节点从子节点数组里删除
treeData([child], otherChild);
// 递归找出当前子节点的所有子节点
});
});
}
treeData(parentArray, childrenArray);
return parentArray;
}
类型判断
js
// 判断是否是数字,包括可以转成数字的字符串
function isNumber(params) {
return !isNaN(params) && isFinite(params);
// 不经过parsefloat转换,'1234qweqw'为非数字
// return !isNaN(parseFloat(params)) && isFinite(params)
// 这是jquery中$.isNumeric的源码 '1234qweqw' 先经过
// parseFloat转换成1234,最终判断是数字
}
解析 URL query 为对象
js
let url = 'http://www.domain.com/?user=anonymous&id=123&name=&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
// 转换query为以下对象形式
{ user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
// 方法一 通过正则匹配 ? & = 分隔符,再判断值的类型
function urlToObj(url) {
let paramsString = /.+\?(.+)$/.exec(url)[1];
let paramsArray = paramsString.split('&');
let obj = {};
paramsArray.forEach(item => {
if (/=/.test(item)) {
let [key, value] = item.split('=');
value = decodeURIComponent(value);
value = isNumber(value) ? Number(value) : value;
let objCurrent = obj[key];
objCurrent = objCurrent ? [].concat(objCurrent, value) : value;
} else {
obj[item] = true;
}
});
return obj;
}
// 方法二 利用URL或者URLSearchParams API把query解析成迭代器,并自动调用
// decodeURIComponent()解码 。name=和nabled对应的value,都转成空字符串
function urlToObj(url) {
let search = new URL(url).searchParams;
let map = {};
for (const [key, value] of search) {
let value = !isNaN(value) ? Number(value) : value || true;
// 是数字就转成number,value为true值就返回value,否则设为true
map[key] = map[key] ? [].concat([map[key], value]) : value;
// map已经存在key说明有多个值,新旧值都放进数组里
}
return map;
}
实现简单路由功能
js
class Router {
constructor() {
// 路由对象,保存hash和对应回调函数的映射关系
this.routes = {};
this.hash = ""; // 当前hash
// 初始化监听器
this.init();
}
storeRoute(hash, cb = function () {}) {
// 添加hash和对应的回调函数,该回调函数可用来刷新局部页面
this.routes[hash] = cb;
}
freshRoute = () => {
// 页面加载和hash改变时执行回调函数
this.hash = location.hash.slice(1) || "/";
this.routes[this.hash]();
};
init = () => {
// 监听页面加载和hash改变事件,执行该hash对应的回调函数
window.addEventListener("load", this.freshRoute, false);
window.addEventListener("hashchange", this.freshRoute, false);
};
}
模拟实现 setInterval
js
function mySetInterval(fn, wait) {
let timer = { flag: true };
// 控制器,控制定时器是否执行
function interval() {
timer.flag &&
setTimeout(() => {
fn();
// 循环调用interval,模拟setInterval
interval();
}, wait);
}
interval();
return timer; // 返回控制器,用于取消定时器
}
动态实现 jsonp
js
// 动态实现加载脚本文件
function addScript(url) {
let script = document.createElement("script");
script.src = url;
script.type = "text/javascript";
document.body.appendChild(script);
}
// 本地提前定义函数
function handleRes(res) {
console.log("这是通过jsonp拿到的远程数据:", res);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 远程脚本xxx.js从链接中拿到要执行的函数名称handleRes。加载完成后执行函数
// 并传入数据:handleRes({name:'zhangjinxi'})
是否存在循环引用
js
// Set缓存遍历过的引用类型数据
function isCircleUse(object, parent = new Set()) {
if (typeof object !== "object" || object === null) return false;
parent.add(obj);
for (const value of Object.values(object)) {
if (typeof value === "object") {
if (parent.has(value)) return true;
// 遍历每个value,如果已经被缓存过,说明存在循环引用
parent.add(value);
if (isCircleUse(value, parent)) return true;
// 递归当前引用类型的值,判断他的子属性是否被缓存过
}
}
return false;
// 递归遍历完所有子属性都找不到缓存过的值,则不存在循环引用
}
找出二维数组中,等于指定值的索引
js
function findTarget(array, target) {
let result = [];
for (let row = 0; row < array.length; row++) {
for (let column = 0; column < array[row].length; column++) {
if (array[row][column] === target) result.push([row, column]);
// 等于target值的坐标,放进一个数组里,
}
}
return result;
}
使对象可迭代
数据可迭代,是因为具有 Symbol.iterator 接口,给对象添加该接口即可
js
// 使对象可迭代 方法一 使用迭代器
var obj = { a: 1, b: 2, c: 3 };
obj[Symbol.iterator] = function () {
let keys = Object.keys(this);
let length = keys.length;
let index = 0;
// 提前保存迭代的索引,避免每次迭代时被覆盖
return {
next() {
return index < length
? { value: [keys[index], this[keys[index++]]], done: false }
: { done: true };
},
};
};
// 方法二 利用生成器函数
obj[Symbol.iterator] = function* () {
for (let iterator of Object.entries(this)) {
yield iterator;
}
};
// 方法三 借助其他数据的Symbol.iterator接口
Object.prototype[Symbol.iterator] = function () {
return Object.values(this)[Symbol.iterator]();
};
// 添加了Symbol.iterator接口,就可以像数组一样迭代了
for (var [k, v] of obj) {
console.log(k, v);
}
迭代器和生成器函数
以下类型都具有 Symbol.iterator 接口,可以使用 forOf 或者扩展运算符进行迭代操作
Array
Set
Map
arguments
nodelist
TypeArray
js
// 手写迭代器
function makeIterator(arr) {
let index = 0;
return {
next() {
return index < arr.length
? { value: arr[index++], done: false }
: { done: true };
},
};
}
let iterator = makeIterator([1, 2]);
// 等于 let iterator = [1,2][Symbol.iterator]()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 生成器函数,返回一个迭代器
function* generate() {
let result = yield 1;
try {
yield `返回值是:${result}`;
} catch (error) {
console.log(error);
}
yield 3;
yield* [4, 5, 6]; // 这是增强yield
}
async await
本质上是个使生成器函数自动执行的函数。调用生成器函数的返回值是个 iterator 迭代器
js
// 传入生成器函数
function CO(generateFun) {
return new Promise((resolve, reject) => {
let iterator = generateFun();
// 调用生成器函数返回迭代器
function step(nextFun) {
try {
let { value, done } = nextFun();
// 此时,执行一次迭代
if (done) return resolve(value);
// 迭代完成,则结束迭代,resolve当前值
// 通过Promise.resolve,可以控制在同步任务执行完之后,再执行step函数
Promise.resolve(value).then(
v => step(() => iterator.next(v))
// 迭代未完成则递归执行step函数,继续下次迭代,并把当前值传给
// next(value)函数,value会作为上次yield的返回值
// e => step(() => iterator.throw(e))
);
} catch (error) {
reject(error);
}
}
step(() => iterator.next());
// 用函数包起来,可以控制在需要的时候执行
});
}
CO(generate);
实现 ES5 继承
使用寄生组合式继承的方式实现
js
function Parent(name) {
this.name = name;
}
// 构建父类,给父类自身添加属性
Parent.prototype.sayName = function () {
console.log("name:", this.name);
};
// 父类原型上添加公共属性和方法
function Child(name) {
Parent.call(this, name);
// 调用父类构造函数并绑定this为当前子类:继承父类属性
}
Child.prototype = Object.create(Parent.prototype);
// 子类原型指向父类的原型对象,继承父类的公共属性和方法
Child.prototype.getName = function () {
return this.name;
};
// 可以给子类原型添加属性和方法,可以覆盖同名的原型属性和方法
Child.prototype.constructor = Child;
// 把子类原型上的构造函数重置为自身,防止指向父类构造函数
实现 Object.create()
js
function create(proto) {
// 创造一个新对象,其原型指向参数 proto,并返回新对象
function F() {}
F.prototype = proto;
return new F();
}
实现 JSON 的方法
js
// 实现JSON.parse()。利用了Function构造函数
function jsonParse(jsonStr) {
return new Function("return " + jsonStr)();
}
// 实现JSON.stringify()方法,undefined,function,Symbol默认会被忽略掉,
// null不变。要求undefined,function,Symbol不被忽略掉
function jsonStringify(obj) {
let type = typeof obj;
if (obj === null || type !== "object") {
if (/string/.test(type)) obj = `"${obj}"`; // String类型时,多加一个""
return String(obj); // 其余原始类型和null,function,转为String类型保存
}
let json = [];
let isArray = Array.isArray(obj);
for (let k in obj) {
json.push(`${isArray ? "" : `"${k}":`}${String(jsonStringify(obj[k]))}`);
}
return isArray ? `[${String(json)}]` : `{${String(json)}}`;
}
/**
* 测试对象
*/
let obj = {
name: undefined,
old: null,
age: function () {},
string: "zhangjinxi",
boolean: true,
class: Symbol("12"),
color: { name: 123 },
hehe: [1, 2],
};
let string = jsonStringify(obj);
console.log(string);
/** 输出结果:
* {"name":undefined,"old":null,"age":function(){},"string":"zhangjinxi","boolean":true,"class":Symbol(12),"color":{"name":123},"hehe":[1,2]}
*/
console.log(jsonParse(string));
// 函数经过parse后也可以照常执行
{
name: undefined,
old: null,
age: [Function: age],
string: 'zhangjinxi',
boolean: true,
class: Symbol(12),
color: { name: 123 },
hehe: [ 1, 2 ]
}
图片转为 base64 格式
js
function imageUrlToBase64(imageUrl, fn) {
let image = new Image(); // 一定要设置为let,不然图片不显示
image.setAttribute("crossOrigin", "anonymous"); // 解决跨域问题
image.src = imageUrl;
image.onload = () => {
// image.onload为异步加载
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d");
context.fillStyle = "#fff";
// 解决图片转base64透明部分填充成黑色问题
// context.strokeStyle = '#fff'
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(image, 0, 0, image.width, image.height);
let dataURL = canvas.toDataURL("image/jpeg", (quality = 0.8));
// 借用canvas.toDataURL方法转为base64类型
fn(dataURL);
};
}
大文件分片上传
服务器支持范围请求,即支持 Range 头部
断点续传:在分片上传基础上记录上传失败的片段,重新上传
js
// XMLHttpRequest实现
function resumeUpload(file) {
var fileSize = file.size; // 获取文件大小
var xhr = new XMLHttpRequest(); // 构造XMLHttpRequest对象
xhr.open("POST", "/upload", true);
var start = 0; // 断点续传的起始位置
if (file.lastModified) {
start = file.lastModified;
// 如果支持file.lastModified,可以根据文件的修改时间判断起始位置
}
xhr.setRequestHeader("Range", "bytes=" + start + "-");
// 设置Range头,代表分断上传其中一部分,字节数表示范围
xhr.upload.addEventListener("progress", function (event) {
var progress = Math.round(((event.loaded + start) / fileSize) * 100);
console.log("Upload progress: " + progress + "%");
});
xhr.addEventListener("load", function () {
// 上传完成
console.log("Upload complete");
});
var blob = file.slice(start, fileSize);
xhr.send(blob); // 发送文件数据
}
// fetch实现
async function resumeUpload(file) {
var fileSize = file.size; // 获取文件大小
var start = 0; // 断点续传的起始位置
if (file.lastModified) {
start = file.lastModified;
// 如果支持file.lastModified,可以根据文件的修改时间判断起始位置
}
var request = new Request("/upload", {
// 构造Request对象
method: "POST",
headers: {
Range: "bytes=" + start + "-",
},
body: file.slice(start, fileSize),
});
var response = await fetch(request); // 上传文件
console.log("Upload complete");
}
// axios第三方插件实现
import axios from "axios";
async function resumeUpload(file) {
var fileSize = file.size; // 获取文件大小
var formData = new FormData(); // 构造FormData对象
formData.append("file", file, file.name);
var start = 0; // 断点续传的起始位置
if (file.lastModified) {
start = file.lastModified;
// 如果支持file.lastModified,可以根据文件的修改时间判断起始位置
}
var response = await axios.post("/upload", formData, {
headers: {
"Content-Range": "bytes " + start + "-" + (fileSize - 1) + "/" + fileSize,
},
onUploadProgress: function (progressEvent) {
var progress = Math.round(
((progressEvent.loaded + start) / fileSize) * 100
);
console.log("Upload progress: " + progress + "%");
},
});
console.log("Upload complete", response);
}
控制并发请求
控制并发请求数量,并按照 url 顺序返回结果
js
/**
* @param {Array} urls 请求url数组
* @param {Number} maxnum 最大并发数
* @return {Promise} 请求结果数组
*/
function concurRequest(urls, maxnum) {
return new Promise(resolve => {
let length = urls.length;
if (length == 0) return resolve([]);
//处理边界条件:如果url为0个,没必要下面的操作了
let index = 0; //调用url的指针
let requestCount = 0; //已经请求的数量
let result = []; //要返回的结果数组,包括成功和失败
async function request() {
if (index === length) return;
//处理边界条件:已经请求所有url,则不在请求
let i = index;
//保存当前index,用于储存到指定位置的result中,因为index指针一直在移动
try {
result[i] = await fetch(urls[index]);
} catch (error) {
result[i] = error;
} finally {
if (requestCount++ === length) {
//请求完成的数量等于最大长度时,resolve返回数据,不再请求了
resolve(result);
} else {
index++;
request();
// 当前请求结束,继续下一个请求
}
}
}
for (let i = 0; i < Math.min(maxnum, length); i++) {
//取最小值作为并发请求数量
request();
}
});
}
concurRequest(["www.baidu.com"], 5);
分析版本号
js
/**
* @param {String} string 要比较的字符串
* @param {Array} terminals 分隔符数组
*
*/
function* walk(string, terminals = [".", "-"]) {
let part = "";
for (let index = 0; index < string.length; index++) {
const element = string[index];
if (terminals.includes(element)) {
yield part; //把分隔符前面的part迭代出去
part = "";
} else {
part += element;
}
}
if (part) {
yield part; //把最后一部分迭代出去
}
}
const Aiterator = walk("1.2-alpha.1");
for (const iterator of Aiterator) {
console.log(iterator);
//拿到每一个迭代出来的部分。 1 2 alpha 1
}
fetch 添加超时功能
js
function(url, options,timeout = 5000) {
return new Promise((resolve, reject) => {
const abortSignal = new AbortController();
// 借助AbortController停止控制器
fetch(url, { ...options, signal: abortSignal.signal }).then(resolve, reject);
setTimeout(() => {
abortSignal.abort();
reject(new Error('网络超时'));
}, timeout);
});
};
js 实现函数重载
js
function createOverload() {
const callMap = new Map();
function overload(...args) {
const types = args.map(arg => typeof arg).join(",");
const fn = callMap.get(types);
// 当执行构造函数时,拿到参数类型位置,对应的回调函数
if (fn) return fn.apply(this, args);
throw new Error("传参没有匹配的函数");
}
overload.addImpl = function (...args) {
const fn = args.pop();
if (typeof fn !== "function") return;
const types = args.join(",");
callMap.set(types, fn);
// 注册参数类型、位置字符串,以及对应的回调函数
};
return overload;
}
const getUsers = createOverload();
getUsers.addImpl(() => console.log("无参时执行的函数"));
getUsers.addImpl("number", () => console.log("一个number参数时执行的函数"));
getUsers.addImpl("string,number", () =>
console.log("一个string,一个number参数时执行的函数")
);
getUsers("你好", 123);
优化列表加载
页面加载大列表,优化页面白屏时间,使用 defer 延迟渲染后面的组件
js
import { onMounted, ref } from 'vue';
export default function useDefer(maxCount = 100) {
const frameCount = ref(0);
let cancelId;
function updateFrameCount() {
cancelId = requestAnimationFrame(() => {
frameCount.value++;
// if (frameCount.value >= maxCount) return;
// updateFrameCount();
});
}
updateFrameCount();
onMounted(() => {
cancelAnimationFrame(cancelId);
// 加载完成后取消动画帧
});
return function defer(n) {
return frameCount.value >= n;
};
}
let defer = useDefer()
<Component1 v-if='defer(30)'></Component1>
// 页面刷新30次之后,才开始加载组件
<Component2 v-if='defer(60)'></Component2>
// 越往后,延迟时间越多,防止页面一次加载太多而卡顿