Appearance
全局配置
Appearance
function throttle(func, wait) {
let lastTime = 0;
return function(...args) {
const currentTime = Date.now();
if (currentTime - lastTime >= wait) {
func.apply(this, args);
lastTime = currentTime;
}
};
}function debounce(fn, delay) {
let timer = null
return function(...args) {
let context = this
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn.apply(context,args)
timer = null
},delay)
}
}Function.prototype.myCall = function(context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
const args = [...arguments].slice(1)
const result = context[symbol](...args)
delete context[symbol]
return result
}Function.prototype.myApply = function(context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const symbol = Symbol()
context[symbol] = this
let result
// 处理参数和 call 有区别
if (arguments[1]) {
result = context[symbol](...arguments[1])
} else {
result = context[symbol]()
}
delete context[symbol]
return result
}Function.prototype.myBind = function (context) {
if (typeof context === undefined || typeof context === null) {
context = window
}
const _this = this
const args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
// 这边的 apply 严谨点可以自己实现
return _this.apply(context, args.concat(...arguments))
}
}原型链的向上找,找到原型的最顶端,也就是Object.prototype
function my_instance_of(leftVaule, rightVaule) {
if(typeof leftVaule !== 'object' || leftVaule === null) return false;
let rightProto = rightVaule.prototype,
leftProto = leftVaule.__proto__;
while (true) {
if (leftProto === null) {
return false;
}
if (leftProto === rightProto) {
return true;
}
leftProto = leftProto.__proto__
}
}要点
function _new() {
let obj = {};
let [constructor, ...args] = [...arguments];
obj.__proto__ = constructor.prototype;
let result = constructor.apply(obj, args);
if (result && typeof result === 'function' || typeof result === 'object') {
return result;
}
return obj;
}某个时间后就去执行某个函数,使用Promise封装
function sleep(fn, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(fn);
}, time);
});
}
let saySomething = (name) => console.log(`hello,${name}`)
async function autoPlay() {
let demo = await sleep(saySomething('xxx'),1000)
let demo2 = await sleep(saySomething('xxxx'),1000)
let demo3 = await sleep(saySomething('xxxxx'),1000)
}
autoPlay()function deepClone(obj,hash = new WeakMap()){
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(obj === null || typeof obj !== 'object') return obj;
//循环引用的情况
if(hash.has(obj)){
return hash.get(obj)
}
//new 一个相应的对象
//obj为Array,相当于new Array()
//obj为Object,相当于new Object()
let constr = new obj.constructor();
hash.set(obj,constr);
for(let key in obj){
if(obj.hasOwnProperty(key)){
constr[key] = deepClone(obj[key],hash)
}
}
//考虑symbol的情况
let symbolObj = Object.getOwnPropertySymbols(obj)
for(let i=0;i<symbolObj.length;i++){
if(obj.hasOwnProperty(symbolObj[i])){
constr[symbolObj[i]] = deepClone(obj[symbolObj[i]],hash)
}
}
return constr
}function curry(fn,...args){
let fnLen = fn.length,
argsLen = args.length;
//对比函数的参数和当前传入参数
//若参数不够就继续递归返回curry
//若参数够就调用函数返回相应的值
if(fnLen > argsLen){
return function(...arg2s){
return curry(fn,...args,...arg2s)
}
}else{
return fn(...args)
}
}
function sumFn(a,b,c){return a+ b + c};
let sum = curry(sumFn);
sum(2)(3)(5)//10
sum(2,3)(5)//10let arr = [1,2,[3,4,[5,[6]]]]
console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1//用reduce实现
function fn(arr){
return arr.reduce((prev,cur)=>{
return prev.concat(Array.isArray(cur)?fn(cur):cur)
},[])
}function getRandom(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}let arr = [2,3,454,34,324,32]
arr.sort(randomSort)
function randomSort(a, b) {
return Math.random() > 0.5 ? -1 : 1;
}\d 表示匹配一个数字字符(0-9)。
(?=...) 表示这是一个正向先行断言(positive lookahead),它断言在当前位置后面必须跟着某些特定的字符,但这些字符不会包含在匹配结果中。
(?:\d{3})+ 表示这是一个非捕获组(non-capturing group),它匹配一个或多个连续的三位数字。具体来说:
(?:.\d+|$) 表示这是另一个非捕获组,它匹配小数点后跟一个或多个数字,或者字符串的结尾。具体来说:
// 输入仅支持整数
function thousandthInteger(num) {
return num.toString().replace(/\d(?=(?:\d{3})+$)/g, '$&,');
}
// 输入同时支持整数和小数
function thousandth(num) {
return num.toString().replace(/\d(?=(?:\d{3})+(?:\.\d+|$))/g, '$&,');
}
// 正则表达式 /\d(?=(?:\d{3})+(?:\.\d+|$))/g 中,非捕获组 (?:\d{3})+ 和 (?:\.\d+|$) 用于对匹配进行分组,但不捕获这些组的内容,从而简化了正则表达式的结构,并提高了匹配性能。关于(?:...) 非捕获组 在正则表达式中,(?:...) 表示一个非捕获组(non-capturing group)。非捕获组的主要作用是对正则表达式的匹配进行分组,但不捕获匹配的内容。这与普通的捕获组 (...) 不同,普通的捕获组会捕获匹配的内容,并将其存储在一个编号的捕获组中,可以在后续的正则表达式操作中引用。
使用非捕获组的好处包括:
- 性能优化:非捕获组不会存储匹配的内容,因此在某些情况下可以提高正则表达式的匹配性能。
- 简化引用:在某些复杂的正则表达式中,使用非捕获组可以减少捕获组的数量,从而简化对捕获组的引用。
// 使用捕获组
let regex1 = /(\d{3})-(\d{2})-(\d{4})/;
let match1 = regex1.exec("123-45-6789");
console.log(match1); // 输出 ["123-45-6789", "123", "45", "6789"]
// 在第一个示例中,正则表达式使用了捕获组 (...),因此匹配的结果包含了三个捕获组的内容:"123"、"45" 和 "6789"。
// 使用非捕获组
let regex2 = /(?:\d{3})-(?:\d{2})-(?:\d{4})/;
let match2 = regex2.exec("123-45-6789");
console.log(match2); // 输出 ["123-45-6789"]
// 在第二个示例中,正则表达式使用了非捕获组 (?:...),因此匹配的结果只包含了整个匹配的内容:"123-45-6789",而没有捕获组的内容。其他方法
function numberWithCommas(x) {
// 转为字符串,按照.拆分
let arr = (x + '').split(".");
// 整数部分再拆分
let int = arr[0].split('');
// 保存小数部分
const fraction = arr[1] || '';
// 记录返回的结果
let r = '';
let len = int.length;
// 倒序遍历
int.reverse().forEach((v, i) => {
// 非第一位且位值是3的倍数,添加','
if(i !== 0 && i % 3 === 0) {
r = v + ',' + r;
} else {
r = v + r;
}
})
// 返回整数部分 + 小数部分
return r + (!!fraction ? '.' + fraction : '');
}
// 使用示例
var number = 1234567.89;
var formattedNumber = numberWithCommas(number);
console.log(formattedNumber); // 输出: 1,234,567.89console.log((1234567.8911111).toLocaleString('en-US'));
// 1,234,567.891
console.log((1234567.8911111).toLocaleString('en-US', {
maximumFractionDigits:10
}));
// 1,234,567.8911111function trim(string){
return string.replace(/^\s+|\s+$/g, '')
}var request = new XMLHttpRequest()
request.open('GET', 'index/a/b/c?name=xxx', true);
request.onreadystatechange = function () {
if(request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}};
request.send();function inheritPrototype(Child, Parent) {
// 创建对象,创建父类原型的一个副本
var prototype = Object.create(Parent.prototype);
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
prototype.constructor = Child;
// 指定对象,将新创建的对象赋值给子类的原型
Child.prototype = prototype;
}测试用例
// 父类初始化实例属性和原型属性
function Father(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Father.prototype.sayName = function () {
alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
// 将父类原型指向子类
inheritPrototype(Son, Father);
// 新增子类原型属性
Son.prototype.sayAge = function () {
alert(this.age);
}
var demo1 = new Son("son1", 21);
var demo2 = new Son("son2", 20);
demo1.colors.push("2"); // ["red", "blue", "green", "2"]
demo2.colors.push("3"); // ["red", "blue", "green", "3"]内置方法:Number.prototype.toString(radix) 可以直接将十进制数转换为指定进制的字符串。
const decimal = 255;
// 转换为二进制
console.log(decimal.toString(2)); // 输出: "11111111"
// 转换为八进制
console.log(decimal.toString(8)); // 输出: "377"
// 转换为十六进制
console.log(decimal.toString(16)); // 输出: "ff"
// 通用函数
function decimalToBase(decimal, base) {
if (base < 2 || base > 16) {
throw new Error('基数必须在2~16之间');
}
return decimal.toString(base);
}
console.log(decimalToBase(255, 10)); // 输出: "255"
console.log(decimalToBase(255, 16)); // 输出: "ff"手动实现转换算法
function decimalToBase(decimal, base) {
if (base < 2 || base > 16) {
throw new Error('基数必须在2~16之间');
}
// 处理零的特殊情况
if (decimal === 0) return '0';
// 处理负数
const isNegative = decimal < 0;
decimal = Math.abs(decimal);
// 定义进制字符映射
const digits = '0123456789ABCDEF';
let result = '';
// 核心算法:不断取余和整除
while (decimal > 0) {
const remainder = decimal % base;
result = digits[remainder] + result;
decimal = Math.floor(decimal / base);
}
// 添加符号
return isNegative ? '-' + result : result;
}
// 示例
console.log(decimalToBase(255, 2)); // 输出: "11111111"
console.log(decimalToBase(255, 8)); // 输出: "377"
console.log(decimalToBase(255, 16)); // 输出: "FF"
console.log(decimalToBase(-255, 16)); // 输出: "-FF"let isType = (type) => (obj) => Object.prototype.toString.call(obj) === `[object ${type}]`
// let isArray = isType('Array')
// let isFunction = isType('Function')
// console.log(isArray([1,2,3]),isFunction(Map))function getType(obj) {
// 使用 Object.prototype.toString.call 获取对象类型
const fullType = Object.prototype.toString.call(obj);
// 提取类型名称,slice(8, -1):从索引 8 开始(即跳过 [object 部分),到倒数第一个字符(即 ] 之前)结束
const type = fullType.slice(8, -1);
return type;
}
// 示例
console.log(getType({})); // Object
console.log(getType([])); // Array
console.log(getType(new Date())); // Date
console.log(getType(null)); // Null
console.log(getType(undefined)); // Undefined
console.log(getType(123)); // Number
console.log(getType('abc')); // String
console.log(getType(true)); // Boolean
console.log(getType(function() {})); // Functionlet unique = arr => [...new Set(arr)];Array.prototype.myReduce = function(fn, initVal) {
let result = initVal,
i = 0;
if(typeof initVal === 'undefined'){
result = this[i]
i++;
}
while( i < this.length ){
result = fn(result, this[i])
}
return result
}// tasks数组的每一项是一个Promise对象
function limitRunTask(tasks, n) {
return new Promise((resolve, reject) => {
let index = 0, finish = 0, start = 0, res = [];
function run() {
if (finish == tasks.length) {
resolve(res);
return;
}
while (start < n && index < tasks.length) {
// 每一阶段的任务数量++
start++;
let cur = index;
tasks[index++]().then(v => {
start--;
finish++;
res[cur] = v;
run();
});
}
}
run();
})
}两个信号试着彼此竞争,来影响谁先输出
例如有一个分页列表,快速地切换第二页,第三页; 先后请求 data2 与 data3,分页器显示当前在第三页,并且进入 loading; 但由于网络的不确定性,先发出的请求不一定先响应,所以有可能 data3 比 data2 先返回; 在 data2 最终返回后,分页器指示当前在第三页,但展示的是第二页的数据。
在前端开发中,常见于搜索,分页,选项卡等切换的场景。
当发出新的请求时,取消掉上次请求即可
function cancelablePromise(promiseArg) {
let resolve = null
let reject = null
const wrappedPromise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
promiseArg && promiseArg.then(
val => {
resolve && resolve(val)
},
error => {
reject && reject(error)
}
)
return {
promise: wrappedPromise,
resolve: (value) => {
resolve && resolve(value)
},
reject: (reason) => {
reject && reject(reason)
},
cancel: () => {
resolve = null
reject = null
}
}
}
function onlyResolvesLast(fn) {
// 保存上一个请求的 cancel 方法
let cancelPrevious = null;
const wrappedFn = (...args) => {
// 当前请求执行前,先 cancel 上一个请求
cancelPrevious && cancelPrevious();
// 执行当前请求
const result = fn.apply(this, args);
// 创建指令式的 promise,暴露 cancel 方法并保存
const { promise, cancel } = cancelablePromise(result);
cancelPrevious = cancel;
return promise;
};
return wrappedFn;
}
const fn = (duration) =>
new Promise(r => {
setTimeout(r, duration);
});
const wrappedFn = onlyResolvesLast(fn);
wrappedFn(500).then(() => console.log(1));
wrappedFn(1000).then(() => console.log(2));
wrappedFn(100).then(() => console.log(3));
// 输出 3// 简洁版pLimit
// 异步逻辑并行执行,并控制并行数量
// 一个队列来保存任务,有两个时机考虑触发任务执行:
// 1. 开始的时候一次性执行最大并发数的任务
// 2. 然后每执行完一个启动一个新的
const shortPLimit = (concurrency) => {
// 传入并发数量
if (!((Number.isInteger(concurrency) || concurrency === Infinity) && concurrency > 0)) {
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
}
// 添加的并发任务要进行排队,所以我们准备一个 queue
const queue = [];
// 记录当前在进行中的异步任务
let activeCount = 0;
// 下一步处理自然就是把活跃任务数量减一,然后再跑一个任务
const next = () => {
activeCount--;
if (queue.length > 0) {
// 如果队列中还有任务,就再跑一个
queue.shift()();
}
};
// 计数,运行这个函数,改变最后返回的那个 promise 的状态
const run = async (fn, resolve, ...args) => {
activeCount++;
// 运行传入的异步函数
const result = (async () => fn(...args))();
resolve(result);
try {
// 等待异步函数执行完
await result;
} catch {}
// 执行完之后进行下一步处理
next();
};
// 把一个异步任务添加到 queue 中,并且只要没达到并发上限就再执行一批任务
const enqueue = (fn, resolve, ...args) => {
queue.push(run.bind(null, fn, resolve, ...args));
// 为了保证并发数量能控制准确,要等全部的微任务执行完再拿 activeCount 和 queue.length 来判断
(async () => {
await Promise.resolve();
if (activeCount < concurrency && queue.length > 0) {
// 如果活跃任务数量小于并发数量,就再跑一个
queue.shift()();
}
})();
};
// 返回一个添加并发任务的函数,我们把它叫做 generator。
// 依旧希望返回返回任务函数的promise的结果
const generator = (fn, ...args) =>
new Promise((resolve) => {
enqueue(fn, resolve, ...args);
});
return generator;
};// 完整版pLimit
// 异步逻辑并行执行,并控制并行数量
// 一个队列来保存任务,有两个时机考虑触发任务执行:
// 1. 开始的时候一次性执行最大并发数的任务
// 2. 然后每执行完一个启动一个新的
const pLimit = (concurrency) => {
// 传入并发数量
if (!((Number.isInteger(concurrency) || concurrency === Infinity) && concurrency > 0)) {
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
}
// 添加的并发任务要进行排队,所以我们准备一个 queue
const queue = [];
// 记录当前在进行中的异步任务
let activeCount = 0;
// 下一步处理自然就是把活跃任务数量减一,然后再跑一个任务
const next = () => {
activeCount--;
if (queue.length > 0) {
// 如果队列中还有任务,就再跑一个
queue.shift()();
}
};
// 计数,运行这个函数,改变最后返回的那个 promise 的状态
const run = async (fn, resolve, ...args) => {
activeCount++;
// 运行传入的异步函数
const result = (async () => fn(...args))();
resolve(result);
try {
// 等待异步函数执行完
await result;
} catch {}
// 执行完之后进行下一步处理
next();
};
// 把一个异步任务添加到 queue 中,并且只要没达到并发上限就再执行一批任务
const enqueue = (fn, resolve, ...args) => {
queue.push(run.bind(null, fn, resolve, ...args));
// 为了保证并发数量能控制准确,要等全部的微任务执行完再拿 activeCount 和 queue.length 来判断
(async () => {
await Promise.resolve();
if (activeCount < concurrency && queue.length > 0) {
// 如果活跃任务数量小于并发数量,就再跑一个
queue.shift()();
}
})();
};
// 返回一个添加并发任务的函数,我们把它叫做 generator
const generator = (fn, ...args) =>
new Promise((resolve) => {
enqueue(fn, resolve, ...args);
});
// 为 generator 添加一些属性,方便外部获取当前的状态
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount
},
pendingCount: {
get: () => queue.length
},
// 提供了一个清空任务队列的函数
clearQueue: {
value: () => {
queue.length = 0;
}
}
});
return generator;
};
// 测试代码
const limit = pLimit(2);
function asyncFun(value, delay) {
return new Promise((resolve) => {
console.log('start ' + value);
setTimeout(() => resolve(value), delay);
});
}
(async function () {
const arr = [
limit(() => asyncFun('aaa', 2000)),
limit(() => asyncFun('bbb', 3000)),
limit(() => asyncFun('ccc', 1000)),
limit(() => asyncFun('ccc', 1000)),
limit(() => asyncFun('ccc', 1000))
];
const result = await Promise.all(arr);
console.log(result);
})();Promise/A+规范:
三种状态 pending| fulfilled(resolved) | rejected 当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态 当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。
必须有一个支持链式调用的异步 then 方法,then接受两个参数(onFulfilled 用来接收promise成功的值, onRejected 用来接收promise失败的原因)且必须返回一个promise
// Promise 状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// 初始状态为 pending
this.status = PENDING;
// 存储成功的值或失败的原因
this.value = undefined;
this.reason = undefined;
// 存储成功和失败的回调队列
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 成功回调
const resolve = (value) => {
// 状态只能从 pending 变为 fulfilled
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
// 失败回调
const reject = (reason) => {
// 状态只能从 pending 变为 rejected
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
// 执行 executor 函数
executor(resolve, reject);
} catch (error) {
// 捕获 executor 中的错误并 reject
reject(error);
}
}
then(onFulfilled, onRejected) {
// 处理可选参数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : error => {
throw error;
};
// 创建新的 Promise 用于链式调用
const newPromise = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const x = onFulfilled(this.value);
// Promise 的链式调用依赖于 then 返回新 Promise,并将上一个 Promise 的结果传递给下一个。
// resolvePromise 确保无论上一个 then 返回的是普通值还是 Promise,都能正确处理并传递结果。
// resolvePromise 通过递归等待嵌套 Promise 完成,确保最终结果正确传递。
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
};
const handleRejected = () => {
try {
const x = onRejected(this.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
};
// 根据当前状态处理回调
if (this.status === FULFILLED) {
// 使用 setTimeout 确保异步执行
setTimeout(handleFulfilled, 0);
} else if (this.status === REJECTED) {
setTimeout(handleRejected, 0);
} else {
// 状态为 pending 时,将回调存入队列
this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
}
});
return newPromise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completedCount = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
(value) => {
results[index] = value;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
},
(reason) => {
reject(reason);
}
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
MyPromise.resolve(promise).then(
(value) => {
resolve(value);
},
(reason) => {
reject(reason);
}
);
});
});
}
}
// 解析 Promise 结果的辅助函数
function resolvePromise(promise, x, resolve, reject) {
// 处理循环引用,如果 Promise 直接返回自身,会导致循环调用,例如:
// const p = new Promise((resolve) => resolve(p));
// p.then((val) => console.log(val)); // 无限循环
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// Promise 规范要求 resolve/reject 只能被调用一次,多次调用应被忽略。
let called = false;
// 处理 thenable 对象,JavaScript 允许自定义 Promise 类(只要实现 then 方法),因此需要兼容这些类 Promise。
try {
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
const then = x.then;
if (typeof then === 'function') {
// 使用 then.call(x) 确保 then 方法中的 this 指向正确。
then.call(
x,
(y) => {
if (called) return;
called = true;
// 递归调用 resolvePromise 处理嵌套 Promise(例如 Promise.resolve(Promise.resolve(42)))。
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
}
// 测试示例
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(42);
}, 1000);
});
promise
.then((value) => {
console.log('Resolved:', value); // 输出: Resolved: 42
return value * 2;
})
.then((value) => {
console.log('Chained:', value); // 输出: Chained: 84
})
.catch((error) => {
console.error('Rejected:', error);
});Promise.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。
Promise.resolve(value) {
if (value && value instanceof Promise) {
return value;
} else if (value && typeof value === 'object' && typeof value.then === 'function') {
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。
Promise.reject = function(reason) {
return new Promise((resolve, reject) => reject(reason))
}Promise.all = function(promiseArr) {
let index = 0, result = []
return new Promise((resolve, reject) => {
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
index++
result[i] = val
if (index === promiseArr.length) {
resolve(result)
}
}, err => {
reject(err)
})
})
})
}Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。
Promise.race = function(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(val => {
resolve(val)
}, err => {
reject(err)
})
})
})
}特点:
核心逻辑:
/**
* 自定义实现 Promise.allSettled
* @param {Array<Promise>} promises - Promise 数组
* @returns {Promise<Array<Object>>} - 结果数组,包含每个 Promise 的状态和值
*/
function promiseAllSettled(promises) {
// 转换为数组以支持类数组对象
const promiseArray = Array.from(promises);
// 处理空数组的情况
if (promiseArray.length === 0) {
return Promise.resolve([]);
}
// 存储每个 Promise 的结果
const results = new Array(promiseArray.length);
let completedCount = 0;
// 返回一个新的 Promise
return new Promise((resolve) => {
// 遍历每个 Promise
promiseArray.forEach((promise, index) => {
// 将每个值包装为 Promise,处理非 Promise 值
Promise.resolve(promise)
.then((value) => {
// 成功结果格式
results[index] = {
status: 'fulfilled',
value
};
})
.catch((reason) => {
// 失败结果格式
results[index] = {
status: 'rejected',
reason
};
})
.finally(() => {
// 无论成功或失败,计数器加1
completedCount++;
// 所有 Promise 都已完成,返回结果数组
if (completedCount === promiseArray.length) {
resolve(results);
}
});
});
});
}
// 使用示例
const promises = [
Promise.resolve(1),
Promise.reject(new Error('Error occurred')),
3 // 非 Promise 值会被自动包装
];
promiseAllSettled(promises).then((results) => {
console.log(results);
/* 输出:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: Error: Error occurred },
{ status: 'fulfilled', value: 3 }
]
*/
});冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到 length - 2 的位置。
function bubble(array) {
checkArray(array);
for (let i = array.length - 1; i > 0; i--) {
// 从 0 到 `length - 1` 遍历
for (let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) swap(array, j, j + 1)
}
}
return array;
}快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作。
function quickSort(arr) {
if (arr.length <= 1) return arr;
// 选择基准值(取中间元素)
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
const equal = [];
// 分区操作
for (const num of arr) {
if (num < pivot) {
left.push(num);
} else if (num > pivot) {
right.push(num);
} else {
equal.push(num);
}
}
// 递归排序并合并结果
return [...quickSort(left), ...equal, ...quickSort(right)];
}
// 示例用法
const arr = [3, 6, 8, 10, 1, 2, 1];
console.log(quickSort(arr)); // 输出: [1, 1, 2, 3, 6, 8, 10]function quickSort(arr) {
// 辅助函数:递归排序指定区间
function sort(low, high) {
if (low >= high) return; // 区间为空或只有一个元素时结束
// 分区操作,获取基准值的最终位置
const pivotIndex = partition(low, high);
// 递归排序左右两部分
sort(low, pivotIndex - 1);
sort(pivotIndex + 1, high);
}
// 分区函数:将小于基准值的元素放到左边,大于的放到右边
function partition(low, high) {
const pivotValue = arr[high]; // 选择最后一个元素作为基准值
let storeIndex = low; // 存储位置初始化为区间起始位置
// 遍历区间内所有元素(除基准值外)
for (let i = low; i < high; i++) {
if (arr[i] < pivotValue) {
// 将小于基准值的元素交换到存储位置
[arr[i], arr[storeIndex]] = [arr[storeIndex], arr[i]];
storeIndex++; // 存储位置后移
}
}
// 将基准值放到正确位置
[arr[storeIndex], arr[high]] = [arr[high], arr[storeIndex]];
return storeIndex;
}
// 开始排序整个数组
sort(0, arr.length - 1);
return arr;
}
// 示例用法
const arr = [3, 6, 8, 10, 1, 2, 1];
console.log(quickSort(arr)); // 输出: [1, 1, 2, 3, 6, 8, 10]function mergeSort(arr) {
// 基本情况:数组长度小于等于1时无需排序
if (arr.length <= 1) return arr;
// 分割数组为左右两部分
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
// 递归排序左右两部分
const sortedLeft = mergeSort(left);
const sortedRight = mergeSort(right);
// 合并已排序的两部分
return merge(sortedLeft, sortedRight);
}
// 合并两个已排序的数组为一个新的有序数组
function merge(left, right) {
const result = []; // 存储合并后的有序数组
let leftIndex = 0; // 左子数组的当前索引
let rightIndex = 0; // 右子数组的当前索引
// 比较左右子数组的元素,按升序依次放入结果数组
// 只要左右子数组都还有元素未处理,就继续循环
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
// 当前左子数组元素更小,将其加入结果数组
result.push(left[leftIndex]);
leftIndex++; // 左子数组指针后移
} else {
// 当前右子数组元素更小或相等,将其加入结果数组
// 相等时优先取右子数组元素(稳定排序的关键)
result.push(right[rightIndex]);
rightIndex++; // 右子数组指针后移
}
}
// 处理剩余元素:
// 1. 如果左子数组还有剩余元素,直接追加到结果数组末尾
// 2. 如果右子数组还有剩余元素,直接追加到结果数组末尾
// 由于左右子数组本身已排序,剩余元素无需比较可直接追加
return [...result, ...left.slice(leftIndex), ...right.slice(rightIndex)];
}
// 示例用法
const arr = [38, 27, 43, 3, 9, 82, 10];
console.log(mergeSort(arr)); // 输出: [3, 9, 10, 27, 38, 43, 82]示例 假设我们要合并两个已排序的子数组:
left = [3, 6, 9]
right = [2, 5, 8, 10]合并步骤: 初始状态:
leftIndex = 0, rightIndex = 0
result = []比较 3 和 2:
result = [2]
leftIndex = 0, rightIndex = 12 更小,将 2 加入结果,右指针后移。
比较 3 和 5:
result = [2, 3]
leftIndex = 1, rightIndex = 13 更小,将 3 加入结果,左指针后移。
比较 6 和 5:
result = [2, 3, 5]
leftIndex = 1, rightIndex = 25 更小,将 5 加入结果,右指针后移。
继续比较...:
最终合并为 [2, 3, 5, 6, 8, 9, 10]。
| 特性 | 归并排序 | 快速排序 | 堆排序 |
|---|---|---|---|
| 时间复杂度 | O(n log n)(稳定) | O(n log n)(平均) | O(n log n) |
| 空间复杂度 | O(n) | O(log n)(原地排序) | O(1) |
| 稳定性 | 稳定 | 不稳定 | 不稳定 |
| 适用场景 | 大数据、链表、稳定性要求 | 通用场景、内存敏感 | 大数据排序,内存有限时 |
堆排序的优势在于 原地排序 和 时间复杂度稳定,但实际性能略逊于快速排序(因交换操作更多)。
堆排序算法原理 堆排序基于 二叉堆(Binary Heap) 数据结构,核心是「构建堆」和「提取堆顶元素」两个步骤,整体遵循「选择排序」的思路(每次选最大 / 小值放到对应位置)。
大顶堆:每个父节点的值 ≥ 子节点的值(用于升序排序)。
小顶堆:每个父节点的值 ≤ 子节点的值(用于降序排序)。
堆是完全二叉树,可用数组表示:
索引为 i 的节点,左子节点索引为 2i + 1,右子节点索引为 2i + 2。
最后一个非叶子节点的索引为 Math.floor(n/2) - 1(n 为数组长度)。
算法步骤
从最后一个非叶子节点开始,依次向上调整每个节点,确保整个数组满足大顶堆特性。
例:[3,1,4,1,5,9,2,6] → 构建后为 [9,5,4,6,1,3,2,1](堆顶为最大值 9)。
交换堆顶(最大值)与数组末尾元素,此时末尾元素已排序。
缩小堆的范围(排除已排序的末尾元素),重新调整剩余元素为大顶堆。
重复上述操作,直到所有元素排序完成。
// 堆排序主函数
function heapSort(arr) {
const len = arr.length;
// 1. 构建大顶堆(从最后一个非叶子节点向上调整)
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
heapify(arr, len, i);
}
// 2. 逐步提取堆顶元素并调整堆
for (let i = len - 1; i > 0; i--) {
// 交换堆顶(最大值)和当前未排序部分的末尾
[arr[0], arr[i]] = [arr[i], arr[0]];
// 调整剩余元素为大顶堆(此时未排序部分长度为 i)
heapify(arr, i, 0);
}
return arr;
}
// 堆调整函数:确保以 index 为根的子树符合大顶堆特性
function heapify(arr, heapSize, index) {
let largest = index; // 初始化最大值为根节点
const left = 2 * index + 1; // 左子节点索引
const right = 2 * index + 2; // 右子节点索引
// 比较左子节点与根节点,更新最大值索引
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
// 比较右子节点与当前最大值,更新最大值索引
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是根节点,则交换并递归调整子树
if (largest !== index) {
[arr[index], arr[largest]] = [arr[largest], arr[index]];
heapify(arr, heapSize, largest);
}
}
// 示例用法
const arr = [3, 1, 4, 1, 5, 9, 2, 6];
console.log(heapSort(arr)); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]window.requestAnimationFrame() 会告诉浏览器我们希望执行一个 动画,并且要求浏览器在下次 重绘之前 调用指定的回调函数 更新动画,通常 60Hz,约 16.7ms/帧 执行一次回调函数。
为了提高性能和电池寿命,在大多数浏览器里,当 requestAnimationFrame() 运行在 后台标签页 或 隐藏的
<iframe>里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。浏览器内部以 32 位带符号整数 存储延时,这会导致如果一个延时大于 2147483647 ms(大约 24.8 天) 时会产生溢出,导致定时器将会被 立即执行,这个限制适用于 setInterval() 和 setTimeout()。
此外,还有有很多因素会导致 setTimeout 的 回调函数 执行 比设定的预期值更久: setTimeout 的 嵌套调用达到 5 层、浏览器会在非活动标签中、系统负载过重、页面被隐藏(如
minimize状态)等等
以下方案优势:
function mySetInterval(callback, interval) {
let startTime = performance.now();
function loop() {
const currentTime = performance.now();
if (currentTime - startTime >= interval) {
callback();
startTime = currentTime;
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}function mySetTimeout(callback, delay) {
let startTime = performance.now();
function loop() {
let currentTime = performance.now();
if (currentTime - startTime >= delay) {
callback();
} else {
requestAnimationFrame(loop);
}
}
}合并setInterval和setTimeout实现:
function myTimer(callback, delay, options = {}) {
const { repeat = false } = options;
let startTime = performance.now();
let taskId;
function loop() {
const currentTime = performance.now();
if (currentTime - startTime >= delay) {
callback();
if (repeat) {
startTime = currentTime; // 重置时间,继续循环
taskId = requestAnimationFrame(loop);
}
// 非 repeat 模式下自然结束,不继续请求下一帧
} else {
taskId = requestAnimationFrame(loop);
}
}
taskId = requestAnimationFrame(loop);
return () => cancelAnimationFrame(taskId);
}
// 使用示例
const cancelInterval = myTimer(() => console.log('每秒执行'), 1000, { repeat: true });
const cancelTimeout = myTimer(() => console.log('3秒后执行一次'), 3000);/**
* 完整自定义定时器实现
*/
class Timer {
constructor() {
this.tasks = new Map();
this.taskId = 0;
}
/**
* 实现类似 setTimeout 的功能
* @param {Function} callback - 回调函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {number} - 任务 ID
*/
setTimeout(callback, delay) {
const id = this.taskId++;
const startTime = performance.now();
const executeTask = () => {
const currentTime = performance.now();
if (currentTime - startTime >= delay) {
callback();
this.tasks.delete(id);
} else {
this.tasks.set(id, requestAnimationFrame(executeTask));
}
};
this.tasks.set(id, requestAnimationFrame(executeTask));
return id;
}
/**
* 实现类似 setInterval 的功能
* @param {Function} callback - 回调函数
* @param {number} interval - 间隔时间(毫秒)
* @returns {number} - 任务 ID
*/
setInterval(callback, interval) {
const id = this.taskId++;
let lastTime = performance.now();
const executeTask = () => {
const currentTime = performance.now();
if (currentTime - lastTime >= interval) {
callback();
lastTime = currentTime;
}
this.tasks.set(id, requestAnimationFrame(executeTask));
};
this.tasks.set(id, requestAnimationFrame(executeTask));
return id;
}
/**
* 清除定时器
* @param {number} id - 任务 ID
*/
clearTimer(id) {
if (this.tasks.has(id)) {
cancelAnimationFrame(this.tasks.get(id));
this.tasks.delete(id);
}
}
}
// 使用示例
const timer = new Timer();
// 测试 setTimeout
const timeoutId = timer.setTimeout(() => {
console.log('setTimeout 触发');
}, 2000);
// 测试 setInterval
const intervalId = timer.setInterval(() => {
console.log('setInterval 触发');
}, 1000);
// 取消定时器示例
timer.clearTimer(intervalId);针对一个 CountDown 计时器组件, props 参数
src\components\CountDown\index.vue
<template>
<div class="count-down">
<slot v-bind="currentTime">
<h1>{{ currentTime.format }}</h1>
</slot>
</div>
</template>
<script setup>
import { computed, ref, onMounted } from 'vue'
import useCountDown from './Composable/useCountDown'
const props = defineProps({
time: {
type: Number,
default: 0,
},
format: {
type: String,
default: 'DD:HH:mm:ss:SSS',
},
immediate: {
type: Boolean,
default: true,
},
})
const emits = defineEmits(['finish'])
const { start, currentTime } = useCountDown({
...props,
onFinish: () => emits('finish'),
})
// 判断是否需要立即执行
onMounted(() => {
if (props.immediate) start()
})
// 向外部暴露的内容
defineExpose({
start,
currentTime,
})
</script>src\components\CountDown\composable\useCountDown\index.ts
import { computed, ref } from 'vue'
import { parseTime, formatTime } from '../../utils'
export default (options) => {
// 是否正在倒计时
let counting = false
// 剩余时间
const remain = ref(options.time)
// 结束时间
const endTime = ref(0)
// 格式化输出的日期时间
const currentTime = computed(() => formatTime(options.format, parseTime(remain.value)))
// 获取当前剩余时间
const getCurrentRemain = () => Math.max(endTime.value - Date.now(), 0)
// 设置剩余时间
const setRemain = (value) => {
// 更新剩余时间
remain.value = value
// 倒计时结束
if (value === 0) {
// 触发 Finish 事件
options.onFinish?.()
// 正在倒计时标志为 false
counting = false
}
}
// 倒计时
const tickTime = () => {
requestAnimationFrame(() => {
// 更新剩余时间
setRemain(getCurrentRemain())
// 倒计时没结束,就继续
if (remain.value > 0) {
tickTime()
}
})
}
// 启动
const start = () => {
// 正在倒计时,忽略多次调用 start
if (counting) return
// 正在倒计时标志为 true
counting = true
// 设置结束时间
endTime.value = Date.now() + remain.value
// 开启倒计时
tickTime()
}
return {
currentTime,
start
}
}src\components\CountDown\utils\index.ts
// 常量
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
// 解析时间
export const parseTime = (time) => {
const days = Math.floor(time / DAY)
const hours = Math.floor((time % DAY) / HOUR)
const minutes = Math.floor((time % HOUR) / MINUTE)
const seconds = Math.floor((time % MINUTE) / SECOND)
const milliseconds = Math.floor(time % SECOND)
return {
days,
hours,
minutes,
seconds,
milliseconds,
}
}
// 格式化时间
export const formatTime = (format, time) => {
let { days, hours, minutes, seconds, milliseconds } = time
// 判断是否需要展示 天数,需要则补 0,否则将 天数 降级加到 小时 部分
if (format.includes('DD')) {
format = format.replace('DD', padZero(days))
} else {
hours += days * 24
}
// 判断是否需要展示 小时,需要则补 0,否则将 小时 降级加到 分钟 部分
if (format.includes('HH')) {
format = format.replace('HH', padZero(hours))
} else {
minutes += hours * 60
}
// 判断是否需要展示 分钟,需要则补 0,否则将 分钟 降级加到 秒数 部分
if (format.includes('mm')) {
format = format.replace('mm', padZero(minutes))
} else {
seconds += minutes * 60
}
// 判断是否需要展示 秒数,需要则补 0,否则将 秒数 降级加到 毫秒 部分
if (format.includes('ss')) {
format = format.replace('ss', padZero(seconds))
} else {
milliseconds += seconds * 1000
}
// 默认展示 3位 毫秒数
if (format.includes('SSS')) {
const ms = padZero(milliseconds, 3)
format = format.replace('SSS', ms)
}
// 最终返回格式化的数据
return { format, days, hours, minutes, seconds, milliseconds }
}
// 不足位数用 0 填充
export const padZero = (str, padLength = 2) => {
str += ''
if (str.length < padLength) {
str = '0'.repeat(padLength - str.length) + str
}
return str
}在处理大量数据渲染时,可利用尾递归和requestAnimationFrame实现分时渲染,减轻浏览器负担。
// 将 1000 条数据分批,每次渲染 10 条,通过requestAnimationFrame的尾递归调用,在每一帧重绘前渲染一批数据,直至数据渲染完毕。
const data = Array.from({ length: 1000 }, (_, i) => i);
const ul = document.querySelector('#list - with - big - data');
function renderData(chunk) {
const fragment = document.createDocumentFragment();
chunk.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
ul.appendChild(fragment);
}
function batchRender(data, batchSize) {
if (data.length === 0) {
return;
}
const batch = data.splice(0, batchSize);
renderData(batch);
requestAnimationFrame(() => batchRender(data, batchSize));
}
batchRender(data, 10);async function executeTasks(tasks, retries) {
for (let i = 0; i < tasks.length; i++) {
let currentRetries = 0;
while (currentRetries <= retries) { // 重试循环
try {
await tasks[i](); // 执行任务
break; // 成功则跳出重试循环
} catch (error) {
if (currentRetries === retries) { // 达到最大重试次数
throw error; // 抛出最终错误
}
currentRetries++; // 否则增加重试计数,继续循环
}
}
}
return Promise.resolve();
}
// 示例用法
executeTasks([task1, task2, task3], 2); // 每个任务最多重试2次将模板字符串中的插值(如 {{user.name}})编译为具体的值。例如,输入模板 "Hello, {{user.name}}!" 和数据 {user: {name: "Alice"}},输出 "Hello, Alice!"。
实现思路:
正则匹配插值:使用正则表达式(如 /\{\{([^}]+)\}\}/g)定位模板中的插值表达式,提取变量路径(如 user.name)。
数据路径解析:将变量路径按 . 分割为层级数组(如 ["user", "name"]),逐层访问数据对象的属性。
replace(pattern, replacement)
replacement可以是字符串或函数。
$& 表示匹配的子串。function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) {
return replacement;
}function compile(template) {
// ([^}]+) 匹配 {{}} 中的内容
// 这是一个捕获组,它的作用是将括号内匹配到的内容提取出来。在后续使用 replace 方法时,可以通过回调函数获取到这个捕获组中的内容。
// [^}]:^ 在方括号 [] 内使用时,表示取反的意思。[^}] 表示匹配除了右花括号 } 之外的任意字符。
// +:表示前面的元素(即 [^}])可以出现一次或多次。所以 [^}]+ 表示匹配一个或多个非右花括号的字符。
const regex = /\{\{([^}]+)\}\}/g;
return function(data) {
return template.replace(regex, (match, path) => {
const keys = path.trim().split('.');
// 根据 keys 数组中的路径层级,逐层访问 data 对象的属性,以获取最终的值。若过程中出现属性不存在的情况,会返回空字符串。
return keys.reduce((obj, key) => obj?.[key], data) || '';
});
};
}
// 使用示例
const template = "Hello, {{user.name}}! Age: {{age}}";
const render = compile(template);
console.log(render({ user: { name: "Alice" }, age: 25 }));
// 输出: "Hello, Alice! Age: 25"