一、前端加载优化相关工作与实践

1. 常见的优化手段(目标:提升页面加载速度、优化用户体验指标)

优化需以具体指标(如 LCP、FID)为导向,结合工具(如 Google PageSpeed Insights)检测瓶颈,核心手段包括:

 

2. 分析长执行时间的 JS 代码

核心思路:定位耗时函数,通过性能分析工具或代码埋点排查瓶颈。

 

3. 阿里云 OSS 图片转为 webp 格式及注意事项

实现思路:通过 OSS 链接参数指定格式,结合浏览器 webp 支持性判断。

 

javascript
 
运行
 
 
 
 
// 判断浏览器是否支持webp
const supportWebp = async () => {
  try {
    const elem = document.createElement('canvas');
    return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
  } catch (e) {
    return false;
  }
};

// 转换图片为webp(若支持)
const convertToWebp = async (imgUrl) => {
  if (!imgUrl) return imgUrl;
  const isSupport = await supportWebp();
  if (!isSupport) return imgUrl;
  // 阿里云OSS通过参数指定格式:?x-oss-process=image/format,webp
  return imgUrl.includes('?') 
    ? `${imgUrl}&x-oss-process=image/format,webp` 
    : `${imgUrl}?x-oss-process=image/format,webp`;
};
 

 

注意事项:

 

4. 限制巨量图片同时加载的数量(并发控制)

实现思路:使用 Promise 控制并发数,每次仅加载指定数量的图片,完成后再加载下一批。

 

javascript
 
运行
 
 
 
 
// 并发控制函数:限制同时执行的Promise数量
const limitConcurrency = (tasks, limit) => {
  return new Promise((resolve) => {
    let index = 0; // 当前任务索引
    let completed = 0; // 已完成任务数
    const results = []; // 结果存储

    // 执行单个任务
    const run = () => {
      if (index >= tasks.length) return;
      const task = tasks[index];
      index++;
      task()
        .then((res) => {
          results[index - 1] = res;
          completed++;
          if (completed === tasks.length) {
            resolve(results); // 所有任务完成
          } else {
            run(); // 继续执行下一个任务
          }
        })
        .catch((err) => {
          results[index - 1] = err;
          completed++;
          run();
        });
    };

    // 启动初始并发任务
    for (let i = 0; i < limit; i++) {
      run();
    }
  });
};

// 使用示例:加载图片,限制同时加载3张
const loadImages = (urls) => {
  // 生成图片加载任务
  const tasks = urls.map((url) => () => 
    new Promise((resolve) => {
      const img = new Image();
      img.src = url;
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
    })
  );
  // 限制并发数为3
  return limitConcurrency(tasks, 3);
};
 

二、前端内存处理

1. JS 内存管理与内存泄露

2. 实现 sizeOf 函数(计算对象所占字节数)

思路:递归遍历对象,累加各类型值的字节数(粗略估算,JS 无原生 API 直接获取)。

 

javascript
 
运行
 
 
 
 
const sizeOf = (obj) => {
  const type = typeof obj;
  // 基础类型
  if (type === 'number' || type === 'boolean') return 8; // 数字/布尔占8字节
  if (type === 'string') return obj.length * 2; // 字符串每个字符2字节
  if (type === 'undefined') return 0;
  if (obj === null) return 4;

  // 引用类型:对象、数组、函数等
  let bytes = 0;
  const seen = new Set(); // 避免循环引用

  const calculate = (item) => {
    if (seen.has(item)) return; // 已计算过的对象跳过
    seen.add(item);

    if (typeof item === 'object') {
      bytes += 16; // 对象头部开销约16字节
      // 遍历对象属性
      if (Array.isArray(item)) {
        item.forEach(calculate);
      } else {
        Object.keys(item).forEach(key => {
          bytes += key.length * 2; // 键名字节数
          calculate(item[key]); // 递归计算值
        });
      }
    } else if (typeof item === 'function') {
      bytes += item.toString().length * 2; // 函数按字符串估算
    }
  };

  calculate(obj);
  return bytes;
};
 

三、HTTP 请求相关

1. 跨域问题解决方案

跨域指浏览器因同源策略(协议、域名、端口一致)限制不同源的请求,解决方案包括:

 

2. 全局请求处理(以 Axios 为例)

通过拦截器实现请求 / 响应的统一处理:

 

javascript
 
运行
 
 
 
 
import axios from 'axios';

// 创建Axios实例
const instance = axios.create({ baseURL: '/api' });

// 请求拦截器:统一设置登录态
instance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  (err) => Promise.reject(err)
);

// 响应拦截器:统一错误处理
instance.interceptors.response.use(
  (res) => res.data,
  (err) => {
    // 统一弹Toast提示错误
    if (err.response) {
      const { status } = err.response;
      if (status === 401) {
        // 未登录:跳转登录页
        window.location.href = '/login';
      } else {
        alert(`错误:${err.response.data.msg || '请求失败'}`);
      }
    } else {
      alert('网络异常,请重试');
    }
    return Promise.reject(err);
  }
);

export default instance;
 

3. 给 XHR 添加 hook(监控各阶段行为)

重写XMLHttpRequest原型,在生命周期阶段触发自定义回调:

 

javascript
 
运行
 
 
 
 
// 保存原生XHR
const NativeXHR = window.XMLHttpRequest;

// 自定义XHR
window.XMLHttpRequest = function () {
  const xhr = new NativeXHR();
  const hooks = {
    onreadystatechange: [],
    onload: [],
    onerror: [],
    onprogress: []
  };

  // 重写事件处理函数
  xhr.addEventListener = function (type, handler) {
    if (hooks[type]) {
      hooks[type].push(handler);
    } else {
      NativeXHR.prototype.addEventListener.call(xhr, type, handler);
    }
  };

  // 监控readyState变化
  const originalOnReadyStateChange = xhr.onreadystatechange;
  xhr.onreadystatechange = function (...args) {
    // 触发自定义hook
    hooks.onreadystatechange.forEach(handler => handler.apply(xhr, args));
    // 执行原生逻辑
    if (originalOnReadyStateChange) originalOnReadyStateChange.apply(xhr, args);
  };

  // 监控其他事件(onload、onerror等)
  ['onload', 'onerror', 'onprogress'].forEach(event => {
    Object.defineProperty(xhr, event, {
      set(handler) {
        hooks[event].push(handler);
      }
    });
  });

  // 初始化时打印日志
  console.log('XHR初始化', xhr);
  return xhr;
};

// 使用示例:在各阶段打印日志
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.addEventListener('readystatechange', () => {
  console.log('readyState变化:', xhr.readyState);
});
xhr.addEventListener('load', () => {
  console.log('请求完成:', xhr.responseText);
});
xhr.send();
 

四、发布订阅模式(EventEmitter)

1. 事件触发与回调的同步性

发布订阅模式中,事件触发(emit)与回调执行默认是同步的(如 Node 的EventEmitter、Vue 的 Event Bus)。即调用emit后,会立即执行所有订阅的回调函数,执行完成后再继续后续代码。

2. 实现简单的 EventEmitter

javascript
 
运行
 
 
 
 
class EventEmitter {
  constructor() {
    this.events = Object.create(null); // 存储事件:{ eventName: [fn1, fn2] }
  }

  // 订阅事件
  on(eventName, handler) {
    if (!this.events[eventName]) this.events[eventName] = [];
    this.events[eventName].push(handler);
  }

  // 触发事件
  emit(eventName, ...args) {
    if (!this.events[eventName]) return;
    // 复制一份避免执行中删除导致的问题
    [...this.events[eventName]].forEach(handler => handler.apply(this, args));
  }

  // 只订阅一次
  once(eventName, handler) {
    const wrapper = (...args) => {
      handler.apply(this, args);
      this.off(eventName, wrapper); // 执行后取消订阅
    };
    this.on(eventName, wrapper);
  }

  // 取消订阅
  off(eventName, handler) {
    if (!this.events[eventName]) return;
    this.events[eventName] = this.events[eventName].filter(fn => fn !== handler);
  }
}

// 使用示例
const emitter = new EventEmitter();
emitter.on('say', (msg) => console.log('say:', msg));
emitter.once('hello', () => console.log('hello once'));

emitter.emit('say', 'hi'); // 输出:say: hi
emitter.emit('hello'); // 输出:hello once
emitter.emit('hello'); // 无输出(已取消)
 

五、算法:01 背包问题(动态规划)

问题:给定容量为C的背包和n件物品(每件物品重量w[i],价值v[i]),每件物品仅能选一次,求背包能装的最大价值。

 

解法:

 

 

 
const knapsack = (C, weights, values) => {
  const n = weights.length;
  const dp = new Array(C + 1).fill(0); // dp[j]:容量j的最大价值

  for (let i = 0; i < n; i++) {
    // 倒序遍历避免重复使用同一物品
    for (let j = C; j >= weights[i]; j--) {
      dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
    }
  }

  return dp[C];
};

// 示例:背包容量5,物品[(2,3), (3,4), (4,5)]
console.log(knapsack(5, [2, 3, 4], [3, 4, 5])); // 输出:7(选重量2和3的物品,价值3+4=7)