Skip to content

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 性能优化建议 🚀 ​

  1. 资源加载优化:
  • 使用懒加载和预加载
  • 合理使用缓存策略
  • 压缩资源文件
  • 使用 CDN
  1. 渲染性能优化:
  • 避免重排重绘
  • 使用性能更好的 CSS 选择器
  • 合理使用虚拟列表
  • 优化动画性能
  1. 代码层面优化:
  • 防抖节流
  • 及时销毁事件监听
  • 避免内存泄漏
  • 使用 Web Workers 处理密集计算
  1. 构建优化:
  • 代码分割
  • Tree Shaking
  • 按需加载
  • 合理使用缓存

总结 📝 ​

本文介绍的手写实现涵盖了:

  1. 基础工具函数
  2. 数组/对象处理
  3. 异步编程
  4. DOM 操作
  5. 性能优化
  6. 框架原理

关键点:

  • 理解核心原理
  • 处理边界情况
  • 考虑性能影响
  • 保持代码简洁
  • 持续学习更新