面试之前准备好 hot100 加上以下这些!
实现常见数组实例方法:forEach、map、reduce、filter、entries
Array.prototype.myForEach = function (callback) {
for (let i = 0; i < this.length; i++) {
if (i in this) {
callback(this[i], i, this)
}
}
}
Array.prototype.myMap = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this) {
result.push(callback(this[i], i, this));
}
}
return result;
}
Array.prototype.myReduce = function (callback, initialValue) {
let accumulator = initialValue;
let startIndex = 0;
if (accumulator === undefined) {
while (startIndex < this.length && !(startIndex in this)) {
startIndex++;
}
if (startIndex >= this.length) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = this[startIndex++];
}
for (let i = startIndex; i < this.length; i++) {
if (i in this) {
accumulator = callback(accumulator, this[i], i, this);
}
}
return accumulator;
}
Array.prototype.myFilter = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this && callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
}
// entries 方法一:迭代器(迭代器是生成器的语法糖)
Array.prototype.myEntries1 = function* () {
for (let i = 0; i < this.length; i++) {
if (i in this) {
yield[i, this[i]];
}
}
}
// entries 方法二:生成器
Array.prototype.myEntries2 = function () {
let index = 0;
const arr = this;
return {
next() {
while (index < arr.length) {
if (index in arr){
return { value: [index, arr[index++]], done: false };
}
index++;
}
return { done: true };
},
[Symbol.iterator]() {
return this; // [Symbol.iterator] 使对象成为可迭代对象
}
}
}
其他数组手写:reduce 实现 map 和 filter、扁平化、去重、转树
// 用 reduce 实现 map 和 filter
Array.prototype.reduceToMap = function (callback) {
return this.reduce((acc, cur, index, array) => {
acc.push(callback(cur, index, array));
return acc;
}, [])
}
Array.prototype.reduceToFilter = function (callback) {
return this.reduce((acc, cur, index, array) => {
if (callback(cur, index, array)) {
acc.push(cur);
}
return acc;
}, [])
}
// 数组扁平化
Array.prototype.myFlatten = function(depth = Infinity) {
const flatten = (arr, currentDepth) => {
if (currentDepth >= depth) return arr;
const result = [];
for (const item of arr) {
if (Array.isArray(item) && currentDepth < depth) {
result.push(...flatten(item, currentDepth + 1));
} else {
result.push(item);
}
}
return result;
};
return flatten(this, 0);
};
// 数组去重
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr1 = [...new Set(arr)];
const uniqueArr2 = arr.filter((item, index) => arr.indexOf(item) === index);
const uniqueArr3 = arr.reduce((acc, cur) => {
if (!acc.includes(cur)) {
acc.push(cur);
}
return acc;
}, [])
// 对象数组去重
const objectArr = [
{id: 1, value: 'a'},
{id: 1, value: 'a'},
{id: 2, value: 'b'}
]
const uniqueObjectArr = new Map(objectArr.map(item => [item.id, item])).values();
// 数组转树
function arrToTree(arr) {
const tree = [];
const map = new Map();
arr.forEach(item => map.set(item.id, {...item}));
for (const item of arr) {
const { id, parentId } = item;
if (parentId === null) {
tree.push(map.get(id));
} else {
const parent = map.get(node.parentId);
if (!parent.children) parent.children = [];
parent.children.push(node);
}
}
return tree;
}
// 树转数组
function treeToArr(tree) {
const arr = [];
const queue = tree.map(node => ({...node, parentId: null}));
while (queue.length) {
const {children, ...rest} = queue.shift();
arr.push(rest);
if (children?.length) {
const items = children.map(child => ({...child, parentId: rest.id}))
queue.push(...items);
}
}
return arr;
}
深拷贝:简易版 & 完全版
// 简易版深拷贝,能拷贝数组和对象
function easeDeepClone (obj) {
if (typeof obj !== 'object' && obj !== null) {
return obj;
}
const res = Array.isArray(obj) ? [] : {};
for (const i in obj) {
if (typeof obj[i] !== 'object' && obj !== null) {
res[i] = deepClone(obj[i])
} else {
res[i] = obj[i];
}
}
return res;
}
// 深拷贝
function deepClone(source, clonedMap = new WeakMap()) {
// 基本类型,直接原样返回
if (source === null || typeof source !== 'object') {
return source;
}
// Map 中已有值也直接返回
if (clonedMap.has(source)) return clonedMap.get(source);
// 拷贝的是对象和其他复杂类型
let target;
if (Array.isArray(source)) {
target = [];
} else if (source instanceof Date) {
target = new Date(source);
} else if (source instanceof RegExp) {
target = new RegExp(source.source, source.flags);
} else if (source instanceof Map) {
target = new Map();
clonedMap.set(source, target);
for (const [key, value] of source) {
target.set(deepClone(key, clonedMap), deepClone(value, clonedMap));
}
return target;
} else if (source instanceof Set) {
target = new Set();
clonedMap.set(source, target);
for (const value of source) {
target.add(deepClone(value, clonedMap));
}
return target;
} else {
target = {};
}
clonedMap.set(source, target);
// 拷贝普通属性
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = deepClone(source[key], clonedMap);
}
}
// 拷贝 Symbol 属性
const symbolKeys = Object.getOwnPropertySymbols(source);
for (const symKey of symbolKeys) {
target[symKey] = deepClone(source[symKey], clonedMap)
}
return target;
}
函数实例方法 call、apply、bind & 函数柯里化
// 函数实例方法 call / apply / bind
// call 和 apply 立即调用函数,区别是:call 参数逐个传递,apply 参数以数组形式传递。
// bind() 返回一个绑定了 this 的新函数。
Function.prototype.myCall = function(obj = globalThis, ...args) {
const fnKey = Symbol();
obj[fnKey] = this; // 将当前函数(this)赋值给对象的属性
const result = obj[fnKey](...args);// 此时fnKey作为obj的方法调用,this自然指向obj
delete obj[fnKey]; // delete 操作符仅用于删除对象属性,无法删除由 var/let/const 定义的变量
return result;
}
Function.prototype.myApply = function(obj = globalThis, argsArr = []) {
const fnKey = Symbol();
obj[fnKey] = this;
const result = obj[fnKey](...argsArr);
delete obj[fnKey];
return result;
}
Function.prototype.myBind = function(obj = globalThis, ...args) {
const fn = this;
return function(...newArgs) {
const fnKey = Symbol();
obj[fnKey] = fn;
const result = obj[fnKey](...args, ...newArgs);
delete obj[fnKey];
return result;
}
}
// 函数柯里化(Currying):将一个多参数函数转换为一系列使用一个参数的函数。
// 如果参数没有达到需要的个数,那么返回新的函数;如果达到了就执行函数。
function curry(callback) {
return function curried(...args) {
if (args.length >= callback.length) {
return callback.apply(this, args);
} else {
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs))
}
}
}
}
Promise:all、race、any、allSettled、并发池
// Promise.all()
// 当所有 Promise 都成功时,返回所有结果数组(延迟时间不影响输出顺序);
// 如果有一个 Promise 失败,立即拒绝,并返回第一个失败的原因
function myPromiseAll (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) return resolve([]);
let completed = 0;
const res = Array(promises.length);
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(result => {
res[index] = result;
completed++;
if (completed === promise.length) return resolve(res);
})
.catch(error => reject(error)) // 如果有错误,直接reject
})
})
}
// Promise.race()
// 返回最先完成( resolve 或 reject )的 Promise 结果
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
if (promises.length) return resolve([]);
promises.forEach((promise) => {
Promise.resolve(promise)
.then(res => resolve(res))
.catch(error => reject(error))
})
})
}
// Promise.any()
// 在任意一个 Promise 被兑现时兑现;仅在所有的 Promise 都被拒绝时才会拒绝
function myPromiseAny (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) return reject(new AggregateError([], "All promises were rejected"));
let completed = 0;
const res = Array(promises.length);
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => resolve(res))
.catch(error => {
res[index] = error;
completed++;
if (completed === promise.length) {
return reject(new AggregateError(res, "All promises were rejected"));
}
})
})
})
}
// Promise.allSettled()
// 等待所有 Promise 完成(无论成功或失败),返回所有结果
function myPromiseAllSettled(promises) {
if (promises.length === 0) return resolve([]);
let completed = 0;
const res = Array(promises.length);
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(result => {
res[index] = { status: 'fullfilled', value: result };
})
.catch(result => {
res[index] = { status: 'rejected', value: result };
})
.finally(() => {
completed++;
if (completed === promises.length) return resolve(res);
})
})
})
}
// 限制最大并发数,使异步任务尽快完成并返回结果Promise
function promisePool(capacity, fn, items) {
const executing = [];
const remaining = [...items];
const res = new Array(items.length);
return new Promise((mainResolve, mainReject) => {
const run = () => {
if (remaining.length === 0 && executing.length === 0) {
mainResolve(res);
return;
}
while (executing.length < capacity && remaining.length > 0) {
const item = remaining.shift();
const index = items.indexOf(item);
const task = fn(item);
const p = task
.then(result => {
res[index] = { status: 'fulfilled', value: result };
return result;
})
.catch(error => {
res[index] = { status: 'rejected', value: error };
throw error;
})
.finally(() => {
executing.splice(executing.indexOf(p), 1);
run();
})
executing.push(p);
}
}
run();
})
}
// 捕获每一个then的错误的方法是使用then的第二个参数,可以return新的Promise或者throw错误
const p1 = new Promise((resolve, reject) => {
resolve('error');
})
p1.then(res => {
console.log(res);
return new Promise((resolve) => {
resolve('yes');
})
}, error => {
console.log(error);
return new Promise((resolve) => {
resolve('error');
})
// throw new Error ('error');
})
// 在 Promise 链中无论是 throw、Promise.reject()、运行时错误,还是异步操作失败的任何错误都可以被 catch 捕获
// 错误会沿着 Promise 链向后传递并被 catch 或 then 的第二个参数捕获
防抖 & 节流
// 防抖(debounce):通过延迟执行,确保高频事件在指定时间内只执行一次,如窗口大小调整、文字输入搜索。
// 节流(throttle):通过限制执行频率,确保高频事件在指定时间内只执行一次,如防止重复点击,控制滚动懒加载的触发频率。
function debounce(callback, delay) {
let timer;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
callback.apply(this, args);
}, delay)
}
}
function throttle(callback, delay) {
let lastTime = 0
return function(...args) {
let now = new Date();
if (now - lastTime >= delay) {
lastTime = now;
callback.apply(this, args);
}
}
}
设计模式:发布订阅、观察者、工厂
// 发布订阅模式
// 维护一个对象,以事件名称为键名,对应的值为回调函数数组
class EventCenter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (!this.events[event]) {
return;
}
this.events[event].forEach(callback => callback(data));
}
off(event, callback) {
if (!this.events[event]) {
return;
}
this.events[event] = this.events[event].filter(item => item !== callback);
}
}
// 测试
const center = new EventCenter();
const callback1 = data => console.log('Callback 1:', data);
const callback2 = data => console.log('Callback 2:', data);
center.on('news', callback1);
center.on('news', callback2);
center.emit('news', 'Breaking News!'); // Callback 1: Breaking News! Callback 2: Breaking News!
center.off('news', callback1);
center.emit('news', 'Another News'); // Callback 2: Another News
// 观察者模式
// Subject是被观察者(例如图片和其他DOM),维护一个观察者数组
// 1. 维护观察者列表 this.observers
// 2. 提供 addObserver() / removeObserver()
// 3. 状态变化时调用 notifyObservers()
class Subject {
constructor() {
this.observers = [];
this.isIntersecting = false;
}
addObserver(observer) {
this.observers.push(observer)
}
removeObserver(observer) {
this.observers = this.observers.filter(item => item !== observer)
}
checkIntersection() {
// ...具体逻辑
this.isIntersecting = true;
if (this.isIntersecting) {
console.log('已加载')
this.notifyObservers();
}
}
notifyObservers(message) {
this.observers.forEach(observer => {
observer.update(message)
})
}
}
class Observer {
constructor() {
this.name = '懒加载管理器';
}
update(message) {
console.log(`${this.name} ${message}`)
}
}
const lazyManager = new Observer();
const images = Array.from(document.querySelectorAll('img[data-src]')).map(img => {
const subject = new Subject(img);
subject.addObserver(lazyManager);
return subject;
});
window.addEventListener('scroll', () => {
lazyManager.checkAll(images);
});
lazyManager.checkAll(images);
// 工厂模式
class Admin { /* ... */ }
class Developer { /* ... */ }
function createUser(role, name) {
switch(role) {
case 'admin': return new Admin(name);
case 'developer': return new Developer(name);
default: throw new Error('Unknown role');
}
}
// 使用
const admin = createUser('admin', 'Alice');
懒加载
方法一:现代方法 IntersectionObserver
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>图片懒加载</title>
<style>
img {
width: 300px;
height: 300px;
display: block;
background-color: red;
margin: 50px auto;
}
</style>
</head>
<body>
<img data-src="1.jpg" alt="图片1">
<img data-src="2.jpg" alt="图片2">
<img data-src="3.jpg" alt="图片3">
<script>
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const image = entry.target;
image.src = image.dataset.src; // 使用 dataset 来访问 data-* 开头的自定义属性
observer.unobserve(image);
}
})
})
images.forEach(image => {
observer.observe(image); // 遍历监听数组元素
})
</script>
</body>
</html>
方法二:传统懒加载
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>图片懒加载</title>
<style>
img {
width: 300px;
height: 300px;
display: block;
background-color: red;
margin: 50px auto;
}
</style>
</head>
<body>
<img data-src="1.jpg" alt="图片1">
<img data-src="2.jpg" alt="图片2">
<img data-src="3.jpg" alt="图片3">
<script>
const images = document.querySelectorAll('img[data-src]');
const debounce = (fn, delay = 100) => {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
}
const lazyLoad = () => {
images.forEach(image => {
if (!image.hasAttribute('data-src')) return;
if (image.getBoundingClientRect().top <= window.innerHeight) {
image.src = image.dataset.src;
image.removeAttribute('data-src');
}
})
}
lazyLoad();
window.addEventListener('scroll', debounce(lazyLoad));
window.addEventListener('resize', debounce(lazyLoad));
</script>
</body>
</html>
排序算法
// 快速排序
// 核心思想是分治算法:找一个基准值(pivot),小于基准值的放左边,大于基准值的放右边。
function quickSort(arr, left = 0, right = arr.length - 1) {
if (left >= right) {
return arr;
}
const pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
return arr;
}
function partition(arr, left, right) {
const pivot = arr[right];
let i = left;
for (let j = left; j < right; j++) {
if (arr[j] < pivot) {
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
}
}
[arr[i], arr[right]] = [arr[right], arr[i]];
return i;
}
// 冒泡排序
function bubbleSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let swapped = false;
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
if (!swapped) {
break;
}
}
return arr;
}
其他前端常见手写
版本号排序
const version = ['1.1', '1.25', '3', '6.6.6']
function v(versions) {
const arr = versions.map(item => item.split('.').map(item => parseInt(item)));
arr.sort((a, b) => {
const maxLen = Math.max(a.length, b.length);
for (let i = 0; i < maxLen; i++) {
const num1 = a[i] || 0;
const num2 = b[i] || 0;
if (num1 !== num2) {
return num1 - num2;
}
}
return 0;
})
return arr.map(item => item.join('.'));
}
千分位分割
// 千位数分割
let number = 1234567.89;
// 使用 Number 的 toLocaleString 方法
let formattedNum1 = number.toLocaleString();
// 使用 Intl.NumberFormat 对象
let formater = new Intl.NumberFormat('en-US');
let formattedNum2 = formater.format(number);
// 自定义函数
function formatNumberWithCommas(number) {
let [integerStr, decimalStr] = number.toString().split('.');
let result = '';
let count = 0;
for (let i = integerStr.length - 1; i >= 0; i--) {
count++;
if (count % 3 === 0 && i > 0) {
result = ',' + integerStr[i] + result;
} else {
result = integerStr[i] + result;
}
}
if (decimalStr !== undefined) {
result += '.' + decimalStr;
}
return result;
}
let formattedNum3 = formatNumberWithCommas(number);
下划线和驼峰转换
// 下划线转驼峰
const snackToCamel = (name) => {
return name.replace(/\_(\w)/g, (all, letter) => {
return letter.toUpperCase();
})
}
// 驼峰转下划线
const camelToSnack = (name) => {
return name.replace(/([A-Z])/g, "_$1").toLowerCase()
}
红绿灯
function redLight() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('红灯');
const res = '红灯'
resolve(res);
return;
}, 3000)
})
}
function greenLight() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('绿灯');
resolve();
}, 1000)
})
}
function yellowLight() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('黄灯');
resolve();
}, 2000)
})
}
async function run() {
await redLight();
await greenLight();
await yellowLight();
run();
}
run();