JavaScript 核心功能手写实现指南 🔥
本文整理了前端开发中常见的 JavaScript 手写实现,包含数组方法、函数方法、防抖节流等核心概念的代码实现。建议收藏实践!
目录
- 数组相关
- 函数方法
- 工具函数
- 异步编程
- DOM 相关
- 设计模式
1. 数组操作大全 📦
1.1 数组扁平化
数组扁平化是指将多维数组转化为一维数组,这在处理复杂数据结构时非常有用。
示例:
js
const arr = [1, [2, [3, [4, 5]]], 6];
// 期望输出: [1, 2, 3, 4, 5, 6]实现方式一: flat()
js
const res1 = arr.flat(Infinity);实现方式二: reduce
js
const flatten = arr => {
return arr.reduce((prev, curr) => {
return prev.concat(Array.isArray(curr) ? flatten(curr) : curr);
}, []);
}实现方式三: 递归
js
function flatten(arr) {
let result = [];
arr.forEach(item => {
if(Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
});
return result;
}💡 最佳实践建议:
- 如果不考虑兼容性,推荐使用
flat()方法 - 处理大数据时注意性能,可以考虑迭代而非递归
- 注意数组元素的类型,某些情况可能需要特殊处理
1.2 数组去重
数组去重是开发中的高频需求,有多种实现方式各有特点。
示例:
js
const arr = [1, 1, '1', 17, true, true, false, false, 'true', {}, {}];
// 期望输出: [1, '1', 17, true, false, 'true', {}, {}]实现方式一: Set (推荐)
js
const unique = arr => Array.from(new Set(arr));实现方式二: Map
js
function unique(arr) {
const map = new Map();
const result = [];
arr.forEach(item => {
// 使用 Map 结构判断是否存在重复值
if(!map.has(item)) {
map.set(item, true);
result.push(item);
}
});
return result;
}💡 注意事项:
- Set 方法无法去重对象类型
- 注意 NaN 的特殊处理
- 考虑数据类型转换的影响
1.3 数组排序实现 🔄
冒泡排序
js
function bubbleSort(arr) {
const len = arr.length;
for(let i = 0; i < len - 1; i++) {
for(let j = 0; j < len - 1 - i; j++) {
if(arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]; // ES6解构赋值交换
}
}
}
return arr;
}快速排序
js
function quickSort(arr) {
if(arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = [], right = [], center = [];
arr.forEach(item => {
if(item < pivot) left.push(item);
else if(item > pivot) right.push(item);
else center.push(item);
});
return [...quickSort(left), ...center, ...quickSort(right)];
}💡 排序算法比较:
- 冒泡排序: 时间复杂度 O(n²), 空间复杂度 O(1)
- 快速排序: 时间复杂度 O(nlogn), 空间复杂度 O(logn)
1.4 数组方法实现 ⚙️
手写 map 方法
js
Array.prototype.myMap = function(callback, thisArg) {
if(typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
const result = [];
const arr = this;
for(let i = 0; i < arr.length; i++) {
// 处理稀疏数组的情况
if(i in arr) {
result[i] = callback.call(thisArg, arr[i], i, arr);
}
}
return result;
}手写 filter 方法
js
Array.prototype.myFilter = function(callback, thisArg) {
if(typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
const result = [];
const arr = this;
for(let i = 0; i < arr.length; i++) {
if(i in arr && callback.call(thisArg, arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}手写 reduce 方法
js
Array.prototype.myReduce = function(callback, initialValue) {
if(typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
const arr = this;
let accumulator = initialValue;
let startIndex = 0;
if(initialValue === undefined) {
// 没有初始值时,取数组第一个非空元素作为初始值
while(startIndex < arr.length && !(startIndex in arr)) {
startIndex++;
}
if(startIndex >= arr.length) {
throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = arr[startIndex++];
}
for(let i = startIndex; i < arr.length; i++) {
if(i in arr) {
accumulator = callback(accumulator, arr[i], i, arr);
}
}
return accumulator;
}2. 函数方法实现 🎯
2.1 防抖与节流
防抖 (debounce)
js
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
}
// 立即执行版本
function debounceImmediate(fn, delay) {
let timer = null;
return function(...args) {
if(timer) clearTimeout(timer);
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
if(callNow) fn.apply(this, args);
}
}节流 (throttle)
js
// 时间戳版本
function throttle(fn, delay) {
let previous = 0;
return function(...args) {
const now = Date.now();
if(now - previous >= delay) {
fn.apply(this, args);
previous = now;
}
}
}
// 定时器版本
function throttleTimer(fn, delay) {
let timer = null;
return function(...args) {
if(!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
}
}💡 使用建议:
- 防抖: 适用于输入框搜索、窗口调整等高频触发场景
- 节流: 适用于滚动事件处理、按钮点击等需要限制执行频率的场景
- 根据具体场景选择是否需要立即执行版本
- 注意 this 指向和参数传递问题
3. Promise 相关实现 🤝
3.1 手写 Promise
js
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = undefined;
this.callbacks = [];
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
this._reject(error);
}
}
_resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
setTimeout(() => {
this.callbacks.forEach(callback => {
callback.onFulfilled(value);
});
});
}
}
_reject(reason) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.value = reason;
setTimeout(() => {
this.callbacks.forEach(callback => {
callback.onRejected(reason);
});
});
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = value => value;
}
if (typeof onRejected !== 'function') {
onRejected = reason => { throw reason };
}
return new MyPromise((resolve, reject) => {
if (this.status === MyPromise.PENDING) {
this.callbacks.push({
onFulfilled: value => {
try {
let result = onFulfilled(value);
resolve(result);
} catch (error) {
reject(error);
}
},
onRejected: reason => {
try {
let result = onRejected(reason);
resolve(result);
} catch (error) {
reject(error);
}
}
});
}
});
}
}3.2 Promise.all 实现
js
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError('promises must be an array');
}
let result = [];
let count = 0;
if (promises.length === 0) {
resolve(result);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
result[index] = value;
count++;
if (count === promises.length) {
resolve(result);
}
},
reason => {
reject(reason);
}
);
});
});
};3.3 Promise.race 实现
js
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError('promises must be an array');
}
promises.forEach(promise => {
Promise.resolve(promise).then(resolve, reject);
});
});
};💡 Promise 使用注意事项:
- 注意处理异常情况
- 合理使用 Promise.all 和 Promise.race
- 避免 Promise 嵌套
- 注意内存泄漏问题
4. 工具函数实现 🛠️
4.1 深拷贝
js
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
Reflect.ownKeys(obj).forEach(key => {
cloneObj[key] = deepClone(obj[key], hash);
});
return cloneObj;
}
// 使用示例
const obj = {
name: '测试',
info: {
age: 18,
hobby: ['reading', 'music']
},
date: new Date(),
regexp: /\w+/,
fn: function() {},
[Symbol('key')]: 'symbol value'
};4.2 事件总线 (EventBus)
js
class EventBus {
constructor() {
this.events = new Map();
}
on(type, fn) {
const handlers = this.events.get(type) || [];
handlers.push(fn);
this.events.set(type, handlers);
}
off(type, fn) {
const handlers = this.events.get(type);
if (!handlers) return;
if (!fn) {
this.events.delete(type);
} else {
const index = handlers.indexOf(fn);
handlers.splice(index, 1);
if (handlers.length === 0) {
this.events.delete(type);
}
}
}
emit(type, ...args) {
const handlers = this.events.get(type) || [];
handlers.forEach(fn => {
fn.apply(this, args);
});
}
once(type, fn) {
const wrapper = (...args) => {
fn.apply(this, args);
this.off(type, wrapper);
};
this.on(type, wrapper);
}
}
// 使用示例
const eventBus = new EventBus();
eventBus.on('test', data => {
console.log('test event:', data);
});
eventBus.emit('test', 'hello world');💡 工具函数使用建议:
- 深拷贝注意处理循环引用
- 事件总线适合组件间通信
- 注意内存管理,及时解除事件监听
- 考虑性能优化
5. 设计模式实现 🎨
5.1 单例模式
js
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
// ES6 实现
const SingletonProxy = (function() {
let instance;
return new Proxy(Singleton, {
construct(target, args) {
if (!instance) {
instance = new target(...args);
}
return instance;
}
});
})();5.2 观察者模式
js
class Subject {
constructor() {
this.observers = [];
this.state = '';
}
attach(observer) {
this.observers.push(observer);
}
detach(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify() {
this.observers.forEach(observer => observer.update(this.state));
}
setState(state) {
this.state = state;
this.notify();
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(state) {
console.log(`${this.name} received state update: ${state}`);
}
}5.3 发布订阅模式
js
class PubSub {
constructor() {
this.subscribers = {};
}
subscribe(topic, callback) {
if (!this.subscribers[topic]) {
this.subscribers[topic] = [];
}
this.subscribers[topic].push(callback);
// 返回取消订阅的函数
return () => {
this.unsubscribe(topic, callback);
};
}
unsubscribe(topic, callback) {
if (!this.subscribers[topic]) return;
this.subscribers[topic] = this.subscribers[topic]
.filter(cb => cb !== callback);
}
publish(topic, data) {
if (!this.subscribers[topic]) return;
this.subscribers[topic].forEach(callback => {
callback(data);
});
}
}
// 使用示例
const pubsub = new PubSub();
const unsubscribe = pubsub.subscribe('news', data => {
console.log('Received news:', data);
});
pubsub.publish('news', { title: 'Breaking News!' });
unsubscribe(); // 取消订阅6. DOM 操作相关 🌳
6.1 虚拟DOM实现
js
class VNode {
constructor(tag, props, children) {
this.tag = tag;
this.props = props;
this.children = children;
}
}
function createElement(tag, props, ...children) {
return new VNode(tag, props, children);
}
function render(vnode) {
// 文本节点直接返回
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
// 创建元素
const element = document.createElement(vnode.tag);
// 设置属性
if (vnode.props) {
Object.entries(vnode.props).forEach(([key, value]) => {
element.setAttribute(key, value);
});
}
// 递归处理子节点
vnode.children.forEach(child => {
element.appendChild(render(child));
});
return element;
}6.2 事件委托实现
js
class EventDelegate {
constructor(element) {
this.element = element;
this.handlers = {};
}
on(eventType, selector, handler) {
if (!this.handlers[eventType]) {
this.handlers[eventType] = [];
this.element.addEventListener(eventType, this.handleEvent.bind(this));
}
this.handlers[eventType].push({
selector,
handler
});
}
handleEvent(event) {
const handlers = this.handlers[event.type];
handlers.forEach(({ selector, handler }) => {
// 检查事件源是否匹配选择器
if (event.target.matches(selector)) {
handler.call(event.target, event);
}
});
}
}
// 使用示例
const delegate = new EventDelegate(document.body);
delegate.on('click', '.btn', function(event) {
console.log('Button clicked:', this.textContent);
});💡 DOM操作最佳实践:
- 优先使用事件委托处理大量元素事件
- 注意DOM操作的性能开销
- 合理使用虚拟DOM优化渲染性能
- 避免频繁的DOM操作,可以使用DocumentFragment
7. 性能优化相关 🚀
7.1 图片懒加载
js
class LazyLoad {
constructor(images) {
this.images = Array.from(images);
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver(this.handleIntersect.bind(this));
this.images.forEach(img => this.observer.observe(img));
} else {
this.loadImages();
}
}
handleIntersect(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}
loadImage(img) {
const src = img.dataset.src;
if (src) {
img.src = src;
img.removeAttribute('data-src');
}
}
loadImages() {
this.images.forEach(img => this.loadImage(img));
}
}
// 使用示例
const images = document.querySelectorAll('img[data-src]');
new LazyLoad(images);7.2 无限滚动实现 🔄
基于 Intersection Observer API 实现的无限滚动:
javascript
class InfiniteScroll {
constructor(options = {}) {
this.options = {
target: null, // 触发加载的目标元素
callback: null, // 加载回调函数
threshold: 0.1, // 触发阈值
...options
};
this.init();
}
init() {
if (!this.options.target || !this.options.callback) {
throw new Error('target & callback is required');
}
this.observer = new IntersectionObserver(
this.handleIntersect.bind(this),
{
threshold: this.options.threshold
}
);
this.observer.observe(this.options.target);
}
handleIntersect(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.options.callback();
}
});
}
destroy() {
this.observer.disconnect();
}
}
// 使用示例
const loader = document.querySelector('.loading');
const scroll = new InfiniteScroll({
target: loader,
callback: async () => {
// 加载数据
const data = await fetchData();
renderList(data);
}
});7.3 虚拟列表实现 📊
高性能长列表渲染方案:
javascript
class VirtualList {
constructor(options = {}) {
this.options = {
container: null, // 容器元素
itemHeight: 50, // 每项高度
amount: 10, // 显示数量
data: [], // 数据源
render: () => {}, // 渲染函数
...options
};
this.state = {
startIndex: 0,
endIndex: this.options.amount,
scrollTop: 0
};
this.init();
}
init() {
const { container, itemHeight, data } = this.options;
// 设置容器高度和定位
container.style.height = `${this.options.amount * itemHeight}px`;
container.style.position = 'relative';
container.style.overflow = 'auto';
// 创建内容容器
this.content = document.createElement('div');
this.content.style.position = 'absolute';
this.content.style.top = '0';
this.content.style.left = '0';
this.content.style.width = '100%';
container.appendChild(this.content);
// 监听滚动
container.addEventListener('scroll', this.handleScroll.bind(this));
this.render();
}
handleScroll(e) {
const { scrollTop } = e.target;
const { itemHeight, amount } = this.options;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = startIndex + amount;
this.setState({
startIndex,
endIndex,
scrollTop
});
}
setState(newState) {
this.state = {
...this.state,
...newState
};
this.render();
}
render() {
const { data, itemHeight, render } = this.options;
const { startIndex, endIndex, scrollTop } = this.state;
const visibleData = data.slice(startIndex, endIndex);
this.content.style.transform = `translateY(${startIndex * itemHeight}px)`;
this.content.innerHTML = visibleData
.map((item, index) => render(item, startIndex + index))
.join('');
}
}
// 使用示例
const list = new VirtualList({
container: document.querySelector('.list-container'),
itemHeight: 50,
amount: 10,
data: Array.from({ length: 1000 }, (_, i) => ({
id: i,
text: `Item ${i}`
})),
render: (item) => `
<div style="height: 50px; border-bottom: 1px solid #eee;">
${item.text}
</div>
`
});7.4 性能优化建议 🚀
- 资源加载优化:
- 使用懒加载和预加载
- 合理使用缓存策略
- 压缩资源文件
- 使用 CDN
- 渲染性能优化:
- 避免重排重绘
- 使用性能更好的 CSS 选择器
- 合理使用虚拟列表
- 优化动画性能
- 代码层面优化:
- 防抖节流
- 及时销毁事件监听
- 避免内存泄漏
- 使用 Web Workers 处理密集计算
- 构建优化:
- 代码分割
- Tree Shaking
- 按需加载
- 合理使用缓存
总结 📝
本文介绍的手写实现涵盖了:
- 基础工具函数
- 数组/对象处理
- 异步编程
- DOM 操作
- 性能优化
- 框架原理
关键点:
- 理解核心原理
- 处理边界情况
- 考虑性能影响
- 保持代码简洁
- 持续学习更新