Skip to content

js手写

浅拷贝实现

js
// 浅拷贝的实现;

// 浅拷贝的详细实现
function shallowCopy(object) {
  // 只拷贝对象类型的数据(包括数组和对象),如果不是对象直接返回
  if (!object || typeof object !== "object") return;

  // 判断原对象是数组还是普通对象,创建对应的新对象
  let newObject = Array.isArray(object) ? [] : {};

  // 使用 for...in 遍历对象的所有可枚举属性(包括自有属性和原型链上的属性)
  for (let key in object) {
    // 只拷贝对象自身的属性,不拷贝原型链上的属性
    // hasOwnProperty 是 Object 的一个实例方法,用于判断某个属性是否为对象自身的属性(不是原型链上的属性)
    // 例如:
    // let obj = {a: 1};
    // obj.hasOwnProperty('a'); // true
    // obj.hasOwnProperty('toString'); // false,因为 toString 来自原型链
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }

  return newObject;
}

深拷贝的实现(拷贝自身,而不是原型链上的)

js
// 深拷贝函数实现
function deepCopy(object) {
  // 如果传入的不是对象或为null,直接返回(不进行拷贝)
  if (!object || typeof object !== "object") return;

  // 判断原对象是数组还是普通对象,创建对应的新对象
  let newObject = Array.isArray(object) ? [] : {};

  // 遍历对象的所有可枚举属性(包括自有属性和原型链上的属性)
  for (let key in object) {
    // 只拷贝对象自身的属性,不拷贝原型链上的属性
    if (object.hasOwnProperty(key)) {
      // 如果属性值是对象,则递归调用deepCopy进行深拷贝,否则直接赋值
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  // 返回深拷贝后的新对象
  return newObject;
}

防抖

js
function debounce(func,delay) {
  let timer;
  return function{
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      func()
    },delay)
  }
}

节流

js
// timer 需要被置为 null,是为了在定时器回调执行完毕后,允许下一次触发节流逻辑。
// 如果不将 timer 置为 null,那么 if(!timer) 条件将永远不成立,后续的触发都不会再执行。
// 这样可以保证每隔 delay 毫秒,func 只会被执行一次。

function throttle(func, delay) {
  let timer = null;
  return function() {
    if (!timer) {
      timer = setTimeout(() => {
        func(); // 注意应为 func() 而不是 fn
        timer = null; // 关键:定时器执行完后重置 timer,允许下次触发
      }, delay);
    }
  }
}

手写定时器

html
  <div id="clock"></div>
  <button onclick="start()">Start</button>
  <button onclick="stop()">Stop</button>
js
let timerId;
// 更新页面时间
function updateTime() {
  const now = new Date();
  // innerHTML 是 DOM 元素的一个属性,用于设置或获取元素内部的 HTML 内容(字符串形式),会解析为 HTML 结构。
  // 还有一个类似的 API 叫 textContent,用于设置或获取元素的纯文本内容(不会解析为 HTML 标签,只显示文本)。
  // innerHTML 会解析标签,textContent 只显示文本。
  // toLocaleTimeString() 是 Date 对象的一个方法,用于将当前时间格式化为本地时间字符串(如 "12:34:56"),会根据用户的本地设置自动调整显示格式。
  document.getElementById("clock").innerHTML = now.toLocaleTimeString();
  // document.getElementById("clock").textContent = now.toLocaleTimeString(); // 只显示文本,不解析HTML
}
// 启动定时器(先停止旧的)
function start() {
  stop(); // 避免重复启动
  updateTime(); // 立即显示一次
  timerId = setInterval(updateTime, 1000);
}
// 停止定时器
function stop() {
  clearInterval(timerId);
}

React手写定时器

jsx

手写Promise.all

手写call

js

解析URL

js
/**
 * 解析URL中的hash部分为键值对对象
 * @param {string} url - 需要解析的URL
 * @return {object} 解析后的hash参数对象
 */
function parseHashUrl(url) {
  // 获取hash部分(去除#符号)
  const hashString = (url.split('#')[1] || '');
  
  // 如果hash为空,返回空对象
  if (!hashString) return {};
  
  // 解析hash字符串
  const result = {};
  
  // 处理有&分隔的多参数情况
  const pairs = hashString.split('&');
  
  pairs.forEach(pair => {
    // 处理键值对
    const [key, value] = pair.split('=');
    
    // 如果有值则解码,否则设为true(表示参数存在)
    if (key) {
      result[key] = value ? decodeURIComponent(value) : true;
    }
  });
  
  return result;
}

// 使用示例
const url = "https://example.com/page?query=test#id=123&name=%E5%BC%A0%E4%B8%89&active";
const hashParams = parseHashUrl(url);
console.log(hashParams);
// 输出: { id: "123", name: "张三", active: true }

数组扁平化

  1. 使用ES6 flat方法
js
const arr = [1,[2,[3,4]]]
arr.flat(Infinity)
  1. 使用reduce方法
js
function flatten(arr) {
  return arr.reduce((prev,curr) => {
    // 对于当前元素,检查是否为数组
    // 如果是数组,递归调用flatten函数继续扁平化
    // 如果不是数组,直接使用该元素
    // 最后通过concat方法将结果合并到累加器中
    // 这里用 prev.concat 而不是 curr.concat,是因为 reduce 的累加器 prev 是“已经累加好的结果数组”,
    // 我们要把当前元素(或其扁平化结果)拼接到累加器后面,形成新的累加结果。
    // 如果用 curr.concat(prev),顺序就会反了,而且 curr 可能不是数组,concat 也会报错。
    return prev.concat(Array.isArray(curr) ? flatten(curr) : curr)
  },[])
}
  1. 使用递归
js
function flatten(arr) {
  let res = [];
  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(i)) {
      res = res.concat(flatten(arr[i]))
    } else {
      res.push(arr[i])
    }
  }
  return res;
}

对象扁平化

js
// DOM树扁平化
function flattenDOM(node, result = {}, path = '') {
  // 为每个节点创建唯一id
  const id = path || 'root'  // 为当前节点创建一个唯一标识符,如果没有提供路径,则使用'root'作为标识符
  result[id] = node;  // 将当前节点存储在结果对象中,使用唯一标识符作为键
  // 处理子节点
  if(node.children) {  // 检查当前节点是否有子节点
    Array.from(node.children).forEach((child, index) => {  // 将子节点集合转换为数组并遍历
      flattenDom(child, result, `${id}_${index}`)  // 递归处理每个子节点,并为其创建一个新的路径标识符
                                                   // 新路径由父节点id和子节点索引组成,确保唯一性
    })
  }
  return result;
}

原型链继承

js
// ES6 class继承
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  
  sayName() {
    console.log(this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类构造函数
    this.age = age;
  }
  
  sayAge() {
    console.log(this.age);
  }
}

// 测试
const child = new Child('小明', 16);
child.sayName(); // 输出: 小明
child.sayAge(); // 输出: 16
js
// 组合继承
// 父构造函数
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

// 子构造函数
function Child(name, age) {
  // 继承属性
  Parent.call(this, name); // 第一次调用父构造函数
  this.age = age;
}

// 继承方法
Child.prototype = new Parent(); // 第二次调用父构造函数
Child.prototype.constructor = Child;

Child.prototype.sayAge = function() {
  console.log(this.age);
};

// 测试
const child1 = new Child('张三', 18);
const child2 = new Child('李四', 20);

child1.sayName(); // 输出: 张三
child1.sayAge(); // 输出: 18
child1.colors.push('black');
console.log(child1.colors); // 输出: ["red", "blue", "green", "black"]
console.log(child2.colors); // 输出: ["red", "blue", "green"]

手写单例模式(基于Static

js

class Storage{
  construct(){
    this.data = {}
  }
  static getInstance() {
    if(!Storage.instance()){
      Storage.instance = new Storage()
    }
    return Storage.instance;
  }
  getItem([key]){
    return this.data[key]
  }
  setItem([value,key]){
    this.data[key] = value
    return value
  }
}

class Storage{
  construct(){
    this.data = {}
  }
  static getInstance() {
    if(!Storage.instance) {
      Storage.instance = new Storage()
    }
    // 如果存在,直接返回
    return Storage.instance;
  }
  getItem(key) {
    reurn this.data[key];
  }
  setItem(key,value) {
    this.data[key] = value;
    return value
  }
}

const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();

storage1.setItem('name', 'A')
console.log(storage1.getItem())
// A 

console.log(storage2.getItem())
// A

console.log(storage1 == storage2)
// true

reduce()实现数组求和

js
// 1. 带有初始值的数组求和
const numbers2 = [1, 2, 3, 4, 5];
const sumWithInitial = numbers2.reduce((acc, cur) => acc + cur, 10);
// sumWithInitial = 25

// 2. 处理多维数组求和
const multiArray = [[1, 2], [3, 4], [5, 6]];
const flatSum = multiArray.reduce((acc, cur) => {
  return acc + cur.reduce((a, b) => a + b, 0);
}, 0);
// flatSum = 21

并发控制

js
/**
 * 并发控制器
 * @param {number} maxConcurrent 最大并发数
 */
function asyncPool(maxConcurrent = 3) {
  // 任务队列,存储待执行的任务
  const queue = [];
  // 当前正在执行的任务数量
  let running = 0;
  
  // 执行队列中的任务
  function run() {
    // 如果队列为空或者已达到最大并发数,则不执行任何操作
    if (queue.length === 0 || running >= maxConcurrent) return;
    
    // 增加正在执行的任务计数
    running++;
    // 从队列头部取出一个任务
    const { fn, resolve, reject } = queue.shift();
    
    // 执行任务函数并处理结果
    Promise.resolve(fn())
      .then(resolve)  // 任务成功时,调用外部Promise的resolve
      .catch(reject)  // 任务失败时,调用外部Promise的reject
      .finally(() => {
        // 无论成功失败,任务完成后都要减少计数
        running--;
        // 尝试执行队列中的下一个任务
        run();
      });
  }
  
  // 返回一个函数,用于添加新任务
  return function addTask(fn) {
    // 每个任务都包装成一个Promise
    return new Promise((resolve, reject) => {
      // 将任务及其resolve/reject函数一起推入队列
      queue.push({ fn, resolve, reject });
      // 尝试立即执行任务(如果并发数未达到上限)
      run();
    });
  };
}

// 使用示例
// 创建一个最大并发数为2的控制器
const runTask = asyncPool(2);

// 这是一个使用示例,展示了如何使用并发控制器
// 首先创建一个最大并发数为2的任务控制器

// 创建一个模拟异步任务的函数
// 这个函数返回一个闭包,闭包内部创建一个Promise来模拟异步操作
function createTask(id, delay = 1000) {
  return () => new Promise(resolve => {
    console.log(`任务${id}开始`);
    setTimeout(() => {
      console.log(`任务${id}完成`);
      resolve(`结果${id}`);
    }, delay);
  });
}

// 这段代码是测试用例,用于演示并发控制器的实际效果
// 添加5个任务,但由于并发限制为2,任务会分批执行:
// 先并发执行任务1和2
// 当任务1或2完成后,立即开始执行任务3
// 当另一个任务完成后,开始执行任务4
// 以此类推,直到所有任务完成
for (let i = 1; i <= 5; i++) {
  runTask(createTask(i))
    .then(result => console.log(result));
}

Vue3响应式系统简易实现

js
// 存储当前激活的副作用函数
let activeEffect = null;
// 存储所有响应式对象的依赖关系
const targetMap = new WeakMap();

// 注册副作用函数
function effect(fn) {
  activeEffect = fn;
  // 立即执行一次副作用函数来收集依赖
  fn();
  activeEffect = null;
  return fn;
}

// 收集依赖
function track(target, key) {
  if (!activeEffect) return;
  
  // 获取目标对象的依赖映射
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  
  // 获取特定属性的依赖集合
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  
  // 添加当前激活的副作用函数
  deps.add(activeEffect);
}

// 触发更新
function trigger(target, key) {
  // 获取目标对象的依赖映射
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  
  // 获取特定属性的依赖集合
  const deps = depsMap.get(key);
  if (!deps) return;
  
  // 执行所有相关的副作用函数
  deps.forEach(effect => effect());
}

// 创建响应式对象
function reactive(target) {
  return new Proxy(target, {
    // 拦截属性读取操作
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      // 收集依赖
      track(target, key);
      return result;
    },
    // 拦截属性设置操作
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      // 触发更新
      trigger(target, key);
      return result;
    }
  });
}

// 创建响应式基础类型值
function ref(value) {
  const refObj = {
    get value() {
      track(refObj, 'value');
      return value;
    },
    set value(newValue) {
      value = newValue;
      trigger(refObj, 'value');
    }
  };
  return refObj;
}