普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月9日首页

Vue 项目必备:10 个高频实用自定义指令,直接复制即用(Vue2 / Vue3 通用)

作者 前端Hardy
2026年4月9日 16:43

在实际开发中,很多重复逻辑(权限控制、防抖点击、图片懒加载、文本复制等)用自定义指令来做最优雅,不污染组件、不写冗余代码、复用性极强。

今天整理了 10 个企业级最常用的 Vue 自定义指令,Vue2 / Vue3 都能跑,复制到项目里直接用,建议收藏进你的工具库。


1. v-permission 按钮权限控制(后台系统必用)

根据权限码控制按钮显隐,后端返回权限列表直接用。

// directives/permission.js
import { useUserStore } from '@/stores/user'

export default {
  mounted(el, binding) {
    const { permissions } = useUserStore()
    const value = binding.value
    if (!value) return
    // 无权限则移除元素
    if (!permissions.includes(value)) {
      el.parentNode?.removeChild(el)
    }
  }
}

使用:

<button v-permission="'user:add'">添加用户</button>

2. v-debounce 防抖点击(搜索/提交防重复)

// directives/debounce.js
export default {
  mounted(el, binding) {
    const { func, delay = 300 } = binding.value
    let timer = null
    el.addEventListener('click', () => {
      clearTimeout(timer)
      timer = setTimeout(() => func(), delay)
    })
  }
}

使用:

<button v-debounce="{ func: handleSearch, delay: 500 }">搜索</button>

3. v-throttle 节流指令(滚动/防狂点)

// directives/throttle.js
export default {
  mounted(el, binding) {
    const { func, delay = 500 } = binding.value
    let lastTime = 0
    el.addEventListener('click', () => {
      const now = Date.now()
      if (now - lastTime >= delay) {
        func()
        lastTime = now
      }
    })
  }
}

4. v-copy 一键复制文本

// directives/copy.js
export default {
  mounted(el, binding) {
    el.addEventListener('click', () => {
      const text = binding.value
      navigator.clipboard.writeText(text).then(() => {
        ElMessage.success('复制成功')
      })
    })
  }
}

使用:

<span v-copy="orderNo">复制订单号</span>

5. v-longpress 长按指令

// directives/longpress.js
export default {
  mounted(el, binding) {
    const { func, time = 1000 } = binding.value
    let timer = null
    el.addEventListener('mousedown', () => {
      timer = setTimeout(() => func(), time)
    })
    el.addEventListener('mouseup mouseleave', () => clearTimeout(timer))
  }
}

6. v-input-number 仅允许输入数字(支持小数)

// directives/number.js
export default {
  mounted(el) {
    const input = el.tagName === 'INPUT' ? el : el.querySelector('input')
    input.addEventListener('input', () => {
      input.value = input.value.replace(/[^\d.]/g, '')
      const arr = input.value.split('.')
      if (arr.length > 2) input.value = arr[0] + '.' + arr[1]
    })
  }
}

使用:

<el-input v-input-number v-model="num" />

7. v-lazy 图片懒加载(性能优化)

// directives/lazy.js
export default {
  mounted(el, binding) {
    const observer = new IntersectionObserver(([{ isIntersecting }]) => {
      if (isIntersecting) {
        el.src = binding.value
        observer.unobserve(el)
      }
    })
    observer.observe(el)
  }
}

使用:

<img v-lazy="imgUrl" alt="" />

8. v-draggable 元素拖拽

// directives/drag.js
export default {
  mounted(el) {
    el.style.cssText += ';position:fixed;cursor:move;'
    el.addEventListener('mousedown', (e) => {
      const x = e.clientX - el.offsetLeft
      const y = e.clientY - el.offsetTop
      const move = (e) => {
        el.style.left = e.clientX - x + 'px'
        el.style.top = e.clientY - y + 'px'
      }
      document.addEventListener('mousemove', move)
      document.addEventListener('mouseup', () => {
        document.removeEventListener('mousemove', move)
      }, { once: true })
    })
  }
}

9. v-watermark 页面水印(防截图)

// directives/watermark.js
export default {
  mounted(el, binding) {
    const text = binding.value || '内部资料'
    const canvas = document.createElement('canvas')
    canvas.width = 200
    canvas.height = 150
    const ctx = canvas.getContext('2d')
    ctx.font = '14px Arial'
    ctx.fillStyle = 'rgba(0,0,0,0.1)'
    ctx.rotate(-0.2)
    ctx.fillText(text, 20, 50)
    el.style.background = `url(${canvas.toDataURL()}) repeat`
  }
}

10. v-auto-height 自适应高度(表格/弹窗常用)

自动计算高度,避免滚动条错乱

// directives/autoHeight.js
export default {
  mounted(el) {
    const resize = () => {
      const top = el.getBoundingClientRect().top
      el.style.height = window.innerHeight - top - 20 + 'px'
    }
    resize()
    window.addEventListener('resize', resize)
    el._resize = resize
  },
  unmounted(el) {
    window.removeEventListener('resize', el._resize)
  }
}

统一注册(Vue3)

directives/index.js 统一导出:

import permission from './permission'
import debounce from './debounce'
// ...其他

export default {
  install(app) {
    app.directive('permission', permission)
    app.directive('debounce', debounce)
  }
}

main.js 引入:

import directives from '@/directives'
app.use(directives)

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

前端开发效率翻倍:15个超级实用的工具函数,直接复制进项目(建议收藏)

作者 前端Hardy
2026年4月9日 16:42

大家好,今天给大家整理了一套前端日常开发高频用到的工具函数

没有复杂算法,也没有花里胡哨的封装,全是业务里真正常用的:时间格式化、手机号脱敏、防抖节流、深拷贝、URL参数解析……全部即插即用,复制到项目里就能跑,非常适合放进自己的 utils 工具库。


1. 时间格式化(最常用)

把时间戳 / Date 对象转成 YYYY-MM-DD HH:mm:ss

function formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
  if (!date) return ''
  date = date instanceof Date ? date : new Date(date)

  const o = {
    'M+': date.getMonth() + 1,
    'D+': date.getDate(),
    'H+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  }

  if (/(Y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, date.getFullYear().toString().slice(4 - RegExp.$1.length))
  }

  for (const k in o) {
    if (new RegExp(`(${k})`).test(fmt)) {
      fmt = fmt.replace(
        RegExp.$1,
        RegExp.$1.length === 1 ? o[k] : o[k].toString().padStart(2, '0')
      )
    }
  }
  return fmt
}

// 使用
formatDate(new Date()) // 2026-04-09 15:30:20

2. 防抖(输入搜索专用)

function debounce(fn, delay = 300) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

3. 节流(滚动/点击防重复)

function throttle(fn, interval = 500) {
  let last = 0
  return function (...args) {
    const now = Date.now()
    if (now - last >= interval) {
      last = now
      fn.apply(this, args)
    }
  }
}

4. 深拷贝(处理对象/数组)

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj
  if (hash.has(obj)) return hash.get(obj)

  const clone = Array.isArray(obj) ? [] : {}
  hash.set(obj, clone)

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash)
    }
  }
  return clone
}

5. 获取 URL 参数

function getQueryParams(url = location.href) {
  const params = {}
  new URL(url).searchParams.forEach((v, k) => (params[k] = v))
  return params
}

// 使用
getQueryParams('https://xxx.com?id=1&name=test') // { id: '1', name: 'test' }

6. 手机号脱敏

function maskPhone(phone) {
  if (!phone || phone.length !== 11) return phone
  return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}

// 13812345678 → 138****5678

7. 姓名脱敏

function maskName(name) {
  if (!name) return ''
  if (name.length === 1) return name
  return name[0] + '*'.repeat(name.length - 1)
}

// 张三 → 张*
// 张三丰 → 张**

8. 数字千分位格式化

function formatMoney(num) {
  if (isNaN(num)) return '0'
  return Number(num).toLocaleString()
}

// 1234567 → 1,234,567

9. 存储操作(localStorage 封装)

const storage = {
  set(key, val) {
    localStorage.setItem(key, JSON.stringify(val))
  },
  get(key) {
    const val = localStorage.getItem(key)
    if (!val) return null
    try {
      return JSON.parse(val)
    } catch {
      return val
    }
  },
  remove(key) {
    localStorage.removeItem(key)
  },
  clear() {
    localStorage.clear()
  }
}

10. 判断数据类型

function getType(val) {
  return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
}

// getType([]) → 'array'
// getType({}) → 'object'
// getType(null) → 'null'

11. 数组去重

function uniqueArr(arr) {
  return [...new Set(arr)]
}

12. 数组扁平化

function flatten(arr) {
  return arr.flat(Infinity)
}

13. 生成随机字符串(ID)

function randomStr(len = 8) {
  const str = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
  let res = ''
  for (let i = 0; i < len; i++) {
    res += str[Math.floor(Math.random() * str.length)]
  }
  return res
}

14. 防抖立即执行版(提交按钮专用)

function debounceImmediate(fn, delay = 500) {
  let timer = null
  return function (...args) {
    const first = !timer
    clearTimeout(timer)
    timer = setTimeout(() => (timer = null), delay)
    if (first) fn.apply(this, args)
  }
}

15. 滚动到顶部(平滑)

function scrollToTop() {
  window.scrollTo({ top: 0, behavior: 'smooth' })
}

最后

这 15 个工具函数基本覆盖了80% 前端业务场景,建议直接新建一个 utils.js 全部存起来,以后开发至少快一倍。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

前端面试通关指南:30个高频手写JS算法,吃透就能拿高薪(附完整代码)

作者 前端Hardy
2026年4月9日 16:18

聊前端面试,算法永远是绕不开的坎。很多小伙伴项目经验很丰富,框架用得溜,但一上面试场就被手写算法题卡住,直接导致面试失利。

我整理了一份30个高频手写JS算法清单,覆盖ES6语法、数组操作、链表、二叉树、动态规划等核心考点,从简单到进阶,每道题都附完整可复制代码+考点解析,跟着敲一遍,面试时直接手到擒来!

一、基础语法与数组变换(必拿分,入门级)

这类题考察基础语法功底,难度低、频率高,面试时先搞定这类题,稳拿基础分,给面试官留好第一印象。

1. 深度克隆(Deep Clone)

考察点:引用类型传址、循环引用、基本类型与引用类型区别

function deepClone(obj, map = new WeakMap()) {
  // 基本类型直接返回(null也是基本类型范畴)
  if (obj === null || typeof obj !== 'object') return obj;
  // 处理循环引用(避免无限递归)
  if (map.has(obj)) return map.get(obj);
  
  // 区分数组和对象,创建对应克隆容器
  const clone = Array.isArray(obj) ? [] : {};
  map.set(obj, clone); // 存入map,标记已克隆
  
  // 遍历对象/数组,递归克隆每一项
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) { // 只克隆自身属性,不克隆原型链属性
      clone[key] = deepClone(obj[key], map);
    }
  }
  return clone;
}

// 测试
const obj = { a: 1, b: { c: 2 }, d: [3, 4] };
const cloneObj = deepClone(obj);
obj.b.c = 100;
console.log(cloneObj.b.c); // 2(克隆后互不影响)

2. 数组扁平化(Flatten)

考察点:递归、数组方法(forEach、concat)、ES6新特性

// 解法1:递归(兼容性好,易理解)
function flatten(arr) {
  let result = [];
  arr.forEach(item => {
    // 若当前项是数组,递归扁平化,否则直接加入结果
    result = result.concat(Array.isArray(item) ? flatten(item) : item);
  });
  return result;
}

// 解法2:ES6 flat方法(简洁,实际开发常用)
const flatten = arr => arr.flat(Infinity); // Infinity表示无限层级扁平化

// 解法3:reduce实现(更简洁,面试加分)
const flatten = arr => arr.reduce((prev, curr) => {
  return prev.concat(Array.isArray(curr) ? flatten(curr) : curr);
}, []);

// 测试
console.log(flatten([1, [2, [3, 4], 5]])); // [1,2,3,4,5]

3. 防抖(Debounce)

考察点:高频事件控制、定时器、this指向

// 核心:频繁触发时,只在最后一次触发后延迟执行
function debounce(fn, delay = 500) {
  let timer = null; // 定时器标识,闭包保存
  return function(...args) {
    clearTimeout(timer); // 每次触发,清除上一次定时器
    // 重新设置定时器,延迟执行目标函数
    timer = setTimeout(() => {
      fn.apply(this, args); // 绑定this和参数,适配实际场景
    }, delay);
  };
}

// 用法(搜索框输入示例)
const handleSearch = debounce((val) => {
  console.log('请求搜索接口:', val);
}, 500);

4. 节流(Throttle)

考察点:高频事件控制、时间戳/定时器、性能优化

// 解法1:时间戳版(触发时立即执行,之后固定时间内不执行)
function throttle(fn, interval = 1000) {
  let lastTime = 0; // 上一次执行时间
  return function(...args) {
    const nowTime = Date.now(); // 当前时间
    // 若当前时间 - 上一次执行时间 > 间隔,执行函数
    if (nowTime - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = nowTime; // 更新上一次执行时间
    }
  };
}

// 解法2:定时器版(触发后延迟执行,固定时间内只执行一次)
function throttle2(fn, interval = 1000) {
  let timer = null;
  return function(...args) {
    if (!timer) { // 若定时器不存在,执行函数
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null; // 执行后清空定时器,允许下次执行
      }, interval);
    }
  };
}

// 用法(滚动加载示例)
window.addEventListener('scroll', throttle(() => {
  console.log('滚动加载更多');
}, 1000));

5. 数组去重(Unique)

考察点:数组方法、Set数据结构、兼容性

// 解法1:Set实现(最简洁,ES6+常用)
const unique = arr => [...new Set(arr)];

// 解法2:indexOf实现(兼容性好,适合旧项目)
function unique(arr) {
  const result = [];
  arr.forEach(item => {
    // 若结果数组中没有当前项,加入结果
    if (result.indexOf(item) === -1) {
      result.push(item);
    }
  });
  return result;
}

// 解法3:filter+indexOf(简洁,面试常用)
const unique = arr => arr.filter((item, index) => {
  // 只保留第一次出现的元素(indexOf返回第一个匹配的索引)
  return arr.indexOf(item) === index;
});

// 测试
console.log(unique([1, 2, 2, 3, 3, 3])); // [1,2,3]

6. 数组排序(冒泡排序)

考察点:排序原理、循环逻辑、基础算法思维

// 冒泡排序:相邻元素对比,大的往后移,每次循环确定一个最大值
function bubbleSort(arr) {
  const len = arr.length;
  // 外层循环:控制排序轮次(共len-1轮)
  for (let i = 0; i < len - 1; i++) {
    let flag = false; // 优化:标记是否发生交换,若无则排序完成
    // 内层循环:对比相邻元素,交换位置
    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]];
        flag = true;
      }
    }
    if (!flag) break; // 无交换,直接退出循环
  }
  return arr;
}

// 测试
console.log(bubbleSort([3, 1, 4, 1, 5, 9])); // [1,1,3,4,5,9]

7. 数组排序(快速排序)

考察点:分治思想、递归、时间复杂度优化(面试高频)

// 快速排序:分治思想,选一个基准值,将数组分成两部分,递归排序
function quickSort(arr) {
  // 终止条件:数组长度<=1,直接返回
  if (arr.length <= 1) return arr;
  // 选基准值(中间项,避免极端情况)
  const pivot = arr[Math.floor(arr.length / 2)];
  // 分治:小于基准值的放左边,等于的放中间,大于的放右边
  const left = arr.filter(x => x < pivot);
  const middle = arr.filter(x => x === pivot);
  const right = arr.filter(x => x > pivot);
  // 递归排序左右两部分,拼接结果
  return [...quickSort(left), ...middle, ...quickSort(right)];
}

// 测试
console.log(quickSort([3, 1, 4, 1, 5, 9])); // [1,1,3,4,5,9]

8. 实现数组forEach方法

考察点:数组方法原理、this绑定、回调函数

// 模拟数组forEach,接收回调函数和this指向
Array.prototype.myForEach = function(callback, thisArg) {
  // 边界判断:回调必须是函数
  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }
  // 遍历当前数组(this指向调用myForEach的数组)
  for (let i = 0; i < this.length; i++) {
    // 执行回调,传入三个参数:当前项、索引、原数组,绑定thisArg
    callback.call(thisArg, this[i], i, this);
  }
};

// 用法
[1, 2, 3].myForEach((item, index) => {
  console.log(item, index); // 1 0 | 2 1 | 3 2
});

9. 实现数组map方法

考察点:数组方法原理、返回值处理、回调函数

// 模拟数组map,返回新数组,新数组元素是回调函数的返回值
Array.prototype.myMap = function(callback, thisArg) {
  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }
  const result = []; // 存储结果的新数组
  for (let i = 0; i < this.length; i++) {
    // 执行回调,将返回值加入结果数组
    result.push(callback.call(thisArg, this[i], i, this));
  }
  return result;
};

// 用法
const newArr = [1, 2, 3].myMap(item => item * 2);
console.log(newArr); // [2,4,6]

10. 实现数组filter方法

考察点:数组方法原理、条件判断、返回值处理

// 模拟数组filter,返回满足条件的元素组成的新数组
Array.prototype.myFilter = function(callback, thisArg) {
  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }
  const result = [];
  for (let i = 0; i < this.length; i++) {
    // 回调返回true,将当前项加入结果数组
    if (callback.call(thisArg, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

// 用法
const evenArr = [1, 2, 3, 4].myFilter(item => item % 2 === 0);
console.log(evenArr); // [2,4]

二、原型与作用域(进阶必问,中层前端考点)

这类题考察对JS底层原理的理解,是区分初级和中级前端的关键,面试时高频出现,必须吃透。

11. 实现new关键字

考察点:原型链、构造函数、this绑定、返回值判断

// myNew:模拟new关键字的作用
function myNew(fn, ...args) {
  // 1. 创建一个空对象,让其原型指向构造函数的prototype
  const obj = Object.create(fn.prototype);
  // 2. 执行构造函数,将this绑定到新创建的对象上
  const result = fn.apply(obj, args);
  // 3. 判断构造函数的返回值:若为对象(非null),则返回该对象;否则返回新创建的obj
  return result instanceof Object ? result : obj;
}

// 测试
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const person = myNew(Person, '张三', 25);
console.log(person.name); // 张三
console.log(person instanceof Person); // true

12. 手写Promise(简易版,支持then链式调用)

考察点:异步编程、状态机、回调队列、链式调用

class MyPromise {
  constructor(exector) {
    // 初始化状态:pending(等待)、fulfilled(成功)、rejected(失败)
    this.status = 'pending';
    this.value = null; // 成功时的返回值
    this.reason = null; // 失败时的原因
    // 存储成功/失败的回调队列(支持多个then绑定)
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    // 成功回调:改变状态,保存值,执行所有成功回调
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.value = value;
        // 执行所有缓存的成功回调
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    // 失败回调:改变状态,保存原因,执行所有失败回调
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.reason = reason;
        // 执行所有缓存的失败回调
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    // 执行 executor,捕获异常,异常时调用reject
    try {
      exector(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  // then方法:支持链式调用,返回新的Promise
  then(onFulfilled, onRejected) {
    // 兼容:若then未传回调,默认透传值/原因
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    // 返回新Promise,实现链式调用
    return new MyPromise((resolve, reject) => {
      // 状态为成功时,执行成功回调
      if (this.status === 'fulfilled') {
        try {
          const result = onFulfilled(this.value);
          // 回调返回值,传递给下一个then的成功回调
          resolve(result);
        } catch (err) {
          // 回调执行失败,传递给下一个then的失败回调
          reject(err);
        }
      }

      // 状态为失败时,执行失败回调
      if (this.status === 'rejected') {
        try {
          const result = onRejected(this.reason);
          resolve(result);
        } catch (err) {
          reject(err);
        }
      }

      // 状态为等待时,缓存回调
      if (this.status === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            const result = onRejected(this.reason);
            resolve(result);
          } catch (err) {
            reject(err);
          }
        });
      }
    });
  }
}

// 测试
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  }, 1000);
}).then(res => {
  console.log(res); // 成功
  return '下一个then';
}).then(res => {
  console.log(res); // 下一个then
});

13. 实现Promise.all方法

考察点:Promise并发控制、数组遍历、状态判断

// Promise.all:接收一个Promise数组,所有Promise成功才成功,有一个失败则失败
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    // 边界判断:若传入的不是数组,直接reject
    if (!Array.isArray(promises)) {
      return reject(new TypeError('arguments must be an array'));
    }

    const result = []; // 存储所有Promise的成功结果
    let count = 0; // 记录已完成的Promise数量

    // 若数组为空,直接resolve空数组
    if (promises.length === 0) return resolve(result);

    // 遍历每个Promise
    promises.forEach((promise, index) => {
      // 兼容非Promise值(直接视为成功)
      Promise.resolve(promise).then(res => {
        result[index] = res; // 按原顺序存储结果
        count++;
        // 所有Promise都完成,resolve结果数组
        if (count === promises.length) {
          resolve(result);
        }
      }).catch(err => {
        // 有一个失败,直接reject该错误
        reject(err);
      });
    });
  });
};

// 测试
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.myAll([p1, p2, p3]).then(res => {
  console.log(res); // [1,2,3]
});

14. 实现Promise.race方法

考察点:Promise并发控制、第一个完成的状态优先

// Promise.race:接收一个Promise数组,第一个完成(成功/失败)的结果作为最终结果
Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('arguments must be an array'));
    }

    // 遍历每个Promise,第一个完成的直接改变状态
    promises.forEach(promise => {
      Promise.resolve(promise).then(res => {
        resolve(res); // 第一个成功,直接resolve
      }).catch(err => {
        reject(err); // 第一个失败,直接reject
      });
    });
  });
};

// 测试
const p1 = new Promise((resolve) => setTimeout(() => resolve(1), 1000));
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('失败'), 500));
Promise.myRace([p1, p2]).catch(err => {
  console.log(err); // 失败(p2先完成,且失败)
});

15. 函数柯里化(Currying)

考察点:闭包、参数复用、函数式编程

// 柯里化:将多参数函数,转化为单参数函数的链式调用
function curry(fn) {
  // 闭包保存已传入的参数
  return function curried(...args) {
    // 若传入的参数数量 >= 原函数的参数数量,执行原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      // 否则,返回一个新函数,接收剩余参数,递归调用curried
      return function(...args2) {
        return curried.apply(this, [...args, ...args2]);
      };
    }
  };
}

// 用法
const add = (a, b, c) => a + b + c; // 原函数,3个参数
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6(链式调用)
console.log(curriedAdd(1, 2)(3)); // 6(支持部分参数)
console.log(curriedAdd(1, 2, 3)); // 6(支持完整参数)

16. 实现call方法

考察点:this绑定、函数执行、参数传递

// 模拟Function.prototype.call:改变函数this指向,立即执行函数
Function.prototype.myCall = function(context, ...args) {
  // 边界判断:若context为null/undefined,指向window(浏览器环境)
  context = context || window;
  // 给context添加一个临时属性,指向当前函数(this就是调用myCall的函数)
  const fnKey = Symbol('tempFn'); // 用Symbol避免属性冲突
  context[fnKey] = this;
  // 执行函数,传入参数,获取返回值
  const result = context[fnKey](...args);
  // 删除临时属性,避免污染context
  delete context[fnKey];
  // 返回函数执行结果
  return result;
};

// 测试
const obj = { name: '张三' };
function sayHello(age) {
  console.log(`我是${this.name},年龄${age}`);
}
sayHello.myCall(obj, 25); // 我是张三,年龄25

17. 实现apply方法

考察点:this绑定、函数执行、参数传递(与call的区别:参数是数组)

// 模拟Function.prototype.apply:改变this指向,参数以数组形式传递,立即执行
Function.prototype.myApply = function(context, args = []) {
  context = context || window;
  const fnKey = Symbol('tempFn');
  context[fnKey] = this;
  // 执行函数,args是数组,用扩展运算符展开
  const result = context[fnKey](...args);
  delete context[fnKey];
  return result;
};

// 测试
const obj = { name: '李四' };
function sayHello(age, gender) {
  console.log(`我是${this.name},年龄${age},性别${gender}`);
}
sayHello.myApply(obj, [28, '男']); // 我是李四,年龄28,性别男

18. 实现bind方法

考察点:this绑定、闭包、函数柯里化、构造函数兼容

// 模拟Function.prototype.bind:改变this指向,返回一个新函数,不立即执行
Function.prototype.myBind = function(context, ...args1) {
  const fn = this; // 保存当前函数(this就是调用myBind的函数)
  // 返回新函数
  const boundFn = function(...args2) {
    // 兼容构造函数:若新函数被new调用,this指向实例,否则指向context
    const isNew = this instanceof boundFn;
    const targetContext = isNew ? this : context;
    // 合并参数,执行原函数
    return fn.apply(targetContext, [...args1, ...args2]);
  };
  // 继承原函数的原型,确保new调用时,实例能访问原函数原型上的属性
  boundFn.prototype = Object.create(fn.prototype);
  return boundFn;
};

// 测试1:普通调用
const obj = { name: '王五' };
function sayHello(age) {
  console.log(`我是${this.name},年龄${age}`);
}
const boundSay = sayHello.myBind(obj, 30);
boundSay(); // 我是王五,年龄30

// 测试2:new调用(构造函数兼容)
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const BoundPerson = Person.myBind(null, '赵六');
const person = new BoundPerson(35);
console.log(person.name); // 赵六
console.log(person.age); // 35

19. 实现防抖+立即执行版

考察点:防抖原理、立即执行逻辑、定时器控制

// 立即执行版防抖:第一次触发立即执行,之后频繁触发不执行,延迟后可再次触发
function debounceImmediate(fn, delay = 500) {
  let timer = null;
  return function(...args) {
    // 若定时器存在,清除定时器(取消延迟执行)
    if (timer) clearTimeout(timer);
    // 判断是否是第一次触发(timer为null)
    const isImmediate = !timer;
    if (isImmediate) {
      fn.apply(this, args); // 立即执行
    }
    // 重置定时器,延迟后清空timer,允许下次立即执行
    timer = setTimeout(() => {
      timer = null;
    }, delay);
  };
}

// 用法(按钮提交示例,避免重复提交,第一次点击立即执行)
const handleSubmit = debounceImmediate(() => {
  console.log('提交表单');
}, 1000);

20. 实现节流+立即执行/延迟执行可选版

考察点:节流原理、参数配置、灵活性优化

// 可选版节流:可配置立即执行(leading)和延迟执行(trailing)
function throttleOpt(fn, interval = 1000, options = { leading: true, trailing: false }) {
  const { leading, trailing } = options;
  let lastTime = 0;
  let timer = null;

  return function(...args) {
    const nowTime = Date.now();
    // 若不允许立即执行,且是第一次触发,重置lastTime
    if (!leading && !lastTime) {
      lastTime = nowTime;
    }

    // 计算剩余时间
    const remainingTime = interval - (nowTime - lastTime);
    // 剩余时间<=0,执行函数
    if (remainingTime <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = nowTime;
    } else if (trailing && !timer) {
      // 允许延迟执行,且无定时器,设置延迟执行
      timer = setTimeout(() => {
        timer = null;
        lastTime = Date.now();
        fn.apply(this, args);
      }, remainingTime);
    }
  };
}

// 用法
// 立即执行,不延迟执行(默认)
const throttle1 = throttleOpt(() => console.log('立即执行'), 1000);
// 不立即执行,延迟执行
const throttle2 = throttleOpt(() => console.log('延迟执行'), 1000, { leading: false, trailing: true });

三、数据结构与算法(大厂高频,高级前端考点)

这类题考察数据结构基础和算法思维,是大厂面试的重点,也是拉开薪资差距的关键,建议重点练习。

21. 两数之和(Two Sum)

考察点:哈希表、时间复杂度优化(从O(n²)优化到O(n))

// 题目:给定一个整数数组和一个目标值,找出数组中和为目标值的两个整数的索引
function twoSum(nums, target) {
  const map = new Map(); // 用Map存储{数值: 索引},快速查找
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i]; // 互补值
    // 若互补值存在于Map中,返回两个索引
    if (map.has(complement)) {
      return [map.get(complement), i];
    }
    // 否则,将当前数值和索引存入Map
    map.set(nums[i], i);
  }
  return []; // 无匹配项,返回空数组
}

// 测试
console.log(twoSum([2, 7, 11, 15], 9)); // [0,1]

22. LRU缓存机制

考察点:哈希表+双向链表、缓存淘汰策略(Vue3响应式缓存底层类似)

// LRU:最近最少使用,超出容量时,删除最久未使用的元素
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity; // 缓存容量
    this.cache = new Map(); // Map特性:插入顺序就是访问顺序,可快速获取最久未使用的元素
  }

  // 获取元素:访问后,将元素移到最近使用的位置
  get(key) {
    if (!this.cache.has(key)) return -1; // 无该元素,返回-1
    const value = this.cache.get(key);
    this.cache.delete(key); // 删除旧位置
    this.cache.set(key, value); // 重新插入,移到末尾(最近使用)
    return value;
  }

  // 存入元素:超出容量时,删除最久未使用的元素(Map的第一个键)
  put(key, value) {
    // 若元素已存在,先删除(避免覆盖后,位置不变)
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }
    // 超出容量,删除最久未使用的元素
    if (this.cache.size >= this.capacity) {
      const oldestKey = this.cache.keys().next().value; // Map的第一个键是最久未使用的
      this.cache.delete(oldestKey);
    }
    // 存入新元素,移到最近使用的位置
    this.cache.set(key, value);
  }
}

// 测试
const lru = new LRUCache(2);
lru.put(1, 1);
lru.put(2, 2);
console.log(lru.get(1)); // 1(访问后,1变为最近使用)
lru.put(3, 3); // 超出容量,删除最久未使用的2
console.log(lru.get(2)); // -1(已被删除)

23. 发布-订阅模式(EventBus)

考察点:设计模式、组件通信、回调函数管理(Vue EventBus底层原理)

// 发布-订阅模式:实现组件间通信,解耦
class EventEmitter {
  constructor() {
    this.events = new Map(); // 存储事件:{事件名: [回调函数数组]}
  }

  // 订阅事件:绑定回调函数
  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, []); // 若事件不存在,初始化回调数组
    }
    this.events.get(event).push(callback); // 加入回调数组
  }

  // 发布事件:触发该事件的所有回调函数
  emit(event, ...args) {
    const callbacks = this.events.get(event);
    if (callbacks) {
      // 执行所有回调,传入参数
      callbacks.forEach(cb => cb(...args));
    }
  }

  // 取消订阅:移除指定事件的指定回调
  off(event, callback) {
    const callbacks = this.events.get(event);
    if (callbacks) {
      // 过滤掉要取消的回调,保留其他回调
      this.events.set(event, callbacks.filter(cb => cb !== callback));
      // 若回调数组为空,删除该事件
      if (this.events.get(event).length === 0) {
        this.events.delete(event);
      }
    }
  }

  // 一次性订阅:触发一次后,自动取消订阅
  once(event, callback) {
    // 包装回调,执行后取消订阅
    const wrapCallback = (...args) => {
      callback(...args);
      this.off(event, wrapCallback);
    };
    this.on(event, wrapCallback);
  }
}

// 测试
const bus = new EventEmitter();
const callback = (msg) => console.log('收到消息:', msg);

bus.on('message', callback);
bus.emit('message', 'Hello World'); // 收到消息:Hello World

bus.off('message', callback);
bus.emit('message', 'Hello Again'); // 无输出(已取消订阅)

bus.once('onceEvent', () => console.log('一次性事件'));
bus.emit('onceEvent'); // 一次性事件
bus.emit('onceEvent'); // 无输出(已自动取消)

24. 实现链表反转(单链表)

考察点:链表数据结构、指针操作、递归/迭代思维

// 1. 定义单链表节点
class ListNode {
  constructor(val = 0, next = null) {
    this.val = val;
    this.next = next;
  }
}

// 解法1:迭代法(推荐,空间复杂度O(1))
function reverseList(head) {
  let prev = null; // 前驱节点
  let curr = head; // 当前节点
  while (curr !== null) {
    const next = curr.next; // 保存下一个节点
    curr.next = prev; // 反转当前节点的指针
    prev = curr; // 前驱节点后移
    curr = next; // 当前节点后移
  }
  return prev; // 反转后,prev是新的头节点
}

// 解法2:递归法(易理解,空间复杂度O(n))
function reverseListRecursive(head) {
  // 终止条件:空节点或只有一个节点,直接返回
  if (head === null || head.next === null) return head;
  // 递归反转后续节点
  const newHead = reverseListRecursive(head.next);
  // 反转当前节点和下一个节点的指针
  head.next.next = head;
  head.next = null; // 避免循环
  return newHead;
}

// 测试
const head = new ListNode(1, new ListNode(2, new ListNode(3)));
const reversedHead = reverseList(head);
// 遍历反转后的链表:3 -> 2 -> 1
let curr = reversedHead;
while (curr) {
  console.log(curr.val); // 3 2 1
  curr = curr.next;
}

25. 判断回文链表

考察点:链表操作、双指针、回文判断

// 题目:判断一个单链表是否是回文(正读和反读一样)
// 步骤:1. 找到链表中点;2. 反转后半部分;3. 对比前半部分和反转后的后半部分
function isPalindrome(head) {
  if (head === null || head.next === null) return true; // 空链表或单个节点,是回文

  // 1. 找到链表中点(慢指针走1步,快指针走2步,快指针到末尾时,慢指针到中点)
  let slow = head;
  let fast = head;
  while (fast.next !== null && fast.next.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
  }

  // 2. 反转后半部分链表(从slow.next开始)
  let prev = null;
  let curr = slow.next;
  while (curr !== null) {
    const next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  slow.next = prev; // 反转后的后半部分链表,头节点是prev

  // 3. 对比前半部分和反转后的后半部分
  let left = head;
  let right = prev;
  while (right !== null) {
    if (left.val !== right.val) return false; // 不相等,不是回文
    left = left.next;
    right = right.next;
  }
  return true; // 全部相等,是回文
}

// 测试
const head1 = new ListNode(1, new ListNode(2, new ListNode(1)));
console.log(isPalindrome(head1)); // true

const head2 = new ListNode(1, new ListNode(2, new ListNode(3)));
console.log(isPalindrome(head2)); // false

26. 二叉树的前序遍历(递归+迭代)

考察点:二叉树数据结构、遍历算法、递归/迭代思维

// 1. 定义二叉树节点
class TreeNode {
  constructor(val = 0, left = null, right = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

// 解法1:递归法(简洁,易理解)
function preorderTraversalRecursive(root, result = []) {
  if (root === null) return result;
  result.push(root.val); // 根节点
  preorderTraversalRecursive(root.left, result); // 左子树
  preorderTraversalRecursive(root.right, result); // 右子树
  return result;
}

// 解法2:迭代法(面试常考,避免递归栈溢出)
function preorderTraversalIterative(root) {
  const result = [];
  if (root === null) return result;
  const stack = [root]; // 用栈存储节点
  while (stack.length > 0) {
    const node = stack.pop(); // 弹出栈顶节点(根节点)
    result.push(node.val);
    // 注意:栈是先进后出,所以先压右子树,再压左子树
    if (node.right !== null) stack.push(node.right);
    if (node.left !== null) stack.push(node.left);
  }
  return result;
}

// 测试
const root = new TreeNode(1, null, new TreeNode(2, new TreeNode(3)));
console.log(preorderTraversalRecursive(root)); // [1,2,3]
console.log(preorderTraversalIterative(root)); // [1,2,3]

27. 二叉树的中序遍历(递归+迭代)

考察点:二叉树遍历、栈的应用

// 解法1:递归法
function inorderTraversalRecursive(root, result = []) {
  if (root === null) return result;
  inorderTraversalRecursive(root.left, result); // 左子树
  result.push(root.val); // 根节点
  inorderTraversalRecursive(root.right, result); // 右子树
  return result;
}

// 解法2:迭代法
function inorderTraversalIterative(root) {
  const result = [];
  const stack = [];
  let curr = root;
  while (curr !== null || stack.length > 0) {
    // 先遍历左子树,所有左节点入栈
    while (curr !== null) {
      stack.push(curr);
      curr = curr.left;
    }
    // 弹出栈顶节点(左子树最底层节点),加入结果
    curr = stack.pop();
    result.push(curr.val);
    // 遍历右子树
    curr = curr.right;
  }
  return result;
}

// 测试
const root = new TreeNode(1, null, new TreeNode(2, new TreeNode(3)));
console.log(inorderTraversalRecursive(root)); // [1,3,2]
console.log(inorderTraversalIterative(root)); // [1,3,2]

28. 斐波那契数列(递归+迭代+优化)

考察点:递归、动态规划、时间/空间复杂度优化

// 题目:求第n个斐波那契数(F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2))

// 解法1:递归法(简单但效率低,时间复杂度O(2ⁿ),有重复计算)
function fibRecursive(n) {
  if (n <= 1) return n;
  return fibRecursive(n - 1) + fibRecursive(n - 2);
}

// 解法2:迭代法(推荐,时间复杂度O(n),空间复杂度O(1))
function fibIterative(n) {
  if (n <= 1) return n;
  let prevPrev = 0; // F(n-2)
  let prev = 1; // F(n-1)
  let curr = 0;
  for (let i = 2; i <= n; i++) {
    curr = prevPrev + prev; // F(n) = F(n-2) + F(n-1)
    prevPrev = prev;
    prev = curr;
  }
  return curr;
}

// 解法3:动态规划(空间复杂度O(n),适合需要保存所有斐波那契数的场景)
function fibDP(n) {
  if (n <= 1) return n;
  const dp = new Array(n + 1);
  dp[0] = 0;
  dp[1] = 1;
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}

// 测试
console.log(fibIterative(10)); // 55

29. 最长公共前缀

考察点:字符串操作、遍历对比、边界处理

// 题目:编写一个函数,找出字符串数组中的最长公共前缀
function longestCommonPrefix(strs) {
  // 边界判断:数组为空,返回空字符串
  if (strs.length === 0) return '';
  // 以第一个字符串为基准,逐个字符对比
  let prefix = strs[0];
  for (let i = 1; i < strs.length; i++) {
    // 循环对比当前字符串和基准字符串,直到找到公共前缀
    while (strs[i].indexOf(prefix) !== 0) {
      // 若不匹配,缩短基准字符串(去掉最后一个字符)
      prefix = prefix.slice(0, prefix.length - 1);
      // 若基准字符串为空,说明没有公共前缀,直接返回
      if (prefix === '') return '';
    }
  }
  return prefix;
}

// 测试
console.log(longestCommonPrefix(["flower","flow","flight"])); // "fl"
console.log(longestCommonPrefix(["dog","racecar","car"])); // ""

30. 验证回文串

考察点:字符串处理、正则表达式、双指针

// 题目:验证一个字符串是否是回文串(只考虑字母和数字,忽略大小写)
function isPalindromeStr(s) {
  // 1. 过滤无效字符(只保留字母和数字),并转为小写
  const validStr = s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
  // 2. 双指针:左指针从开头,右指针从末尾,逐步对比
  let left = 0;
  let right = validStr.length - 1;
  while (left < right) {
    if (validStr[left] !== validStr[right]) {
      return false; // 不相等,不是回文串
    }
    left++;
    right--;
  }
  return true; // 全部相等,是回文串
}

// 测试
console.log(isPalindromeStr("A man, a plan, a canal: Panama")); // true
console.log(isPalindromeStr("race a car")); // false

写在最后

这30个手写JS算法,覆盖了前端面试从基础到进阶的所有高频考点——基础语法、数组操作、原型作用域、数据结构、算法思维,每道题都能直接复制到编辑器调试,吃透这30道题,面试时再遇到手写算法题,就能从容应对。

很多前端同学觉得算法难,其实是没找对方法:不用刷上千道题,重点吃透这些高频题,搞懂每道题的原理和考点,比盲目刷题更有效。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

前端性能优化实战:从3秒到1秒,我只做了这5件事(全网通用)

作者 前端Hardy
2026年4月9日 16:16

做前端的都知道,页面加载速度就是生命线

一个项目做完,代码写得再漂亮、框架选得再先进,只要打开慢个1-2秒,用户流失率直接翻倍。最近我接手了一个老项目,首屏加载要 3.5秒,通过一系列针对性优化,最终压到了 0.8秒

今天把这套通用型性能优化方案分享给大家,不限Vue、React、小程序,只要是前端项目,复制粘贴就能提升加载速度,收藏这一篇,面试、工作都能用!


一、现状复盘:为什么你的页面那么卡?

先别急着写代码,先搞清楚瓶颈在哪里。建议在开发环境打开 Chrome 开发者工具 -> Lighthouse 跑一次测评。

通常前端加载慢,无非逃不过这3点:

  1. 体积过大:打包后的JS/CSS体积太大,网络传输慢;
  2. 请求过多:首屏加载了无关的接口、图片,造成网络拥堵;
  3. 渲染阻塞:JS没加载完,页面就是一片空白,用户体验极差。

下面的5步优化,就是针对这三大痛点,由浅入深,解决80%的性能问题。

二、核心干货:5个必做优化步骤(直接复制)

1. 路由懒加载:只加载当前需要的代码

这是性价比最高的优化!默认情况下,Webpack会把所有路由打包成一个巨大的 app.js。首屏不管去哪个页面,都要把所有代码下载下来。

解决思路: 按路由拆分,只加载当前页面的代码。

Vue3 / Vite 写法

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    // 直接引入,首屏会加载
    component: () => import('@/views/Home.vue') 
  },
  {
    path: '/about',
    name: 'About',
    // 关键:使用箭头函数+import,实现路由懒加载
    // 访问该路由时才会加载对应的Chunk文件
    component: () => import('@/views/About.vue') 
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

React / React Router 写法

// 同样适用React,只需在路由配置处改一下
const About = React.lazy(() => import('@/views/About.vue')); 
// 配合Suspense显示加载中
import { Suspense } from 'react';

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

2. 图片优化:去重+压缩,体积减半

图片通常是项目体积最大的资源。不要直接把UI给的原图丢上去。

3招搞定图片优化:

  1. 使用现代格式:把PNG/JPG转为 WebPAVIF,体积可缩小50%以上,兼容性极好。
  2. 压缩图片:使用 TinyPNG 或 Webpack 插件(如image-webpack-loader)自动压缩。
  3. 懒加载(Lazy Loading):给图片加上 loading="lazy" 属性,滚动到可视区域再加载,首屏请求瞬间减少。

原生写法(所有框架通用)

<!-- 只需添加这个属性,自动实现懒加载 -->
<img src="image.webp" loading="lazy" alt="优化后" />

Vue/React 中使用

在你的UI库(Element/Ant Design)中使用图片组件时,直接添加属性即可:

<el-image 
  src="image.webp" 
  loading="lazy" 
  fallback="fallback.png" <!-- 降级处理 -->
/>

3. 移除console和注释:清掉“垃圾代码”

打包发布时,千万别把 console.logdebugger 和大量注释打包进去。这些不仅增加体积,还会暴露前端代码,存在安全风险。

解决方案: 在Vite/Webpack配置中一键清除。

Vite 配置(vue.config.js 或 vite.config.js)

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 生产环境清除console
    minify: 'ter', // 使用terser进行压缩
    terserOptions: {
      compress: {
        drop_console: true, // 移除所有console
        drop_debugger: true // 移除debugger
      }
    }
  }
})

4. 资源压缩:Gzip / Brotli 终极武器

这一步是服务器端的,但只要是前端开发,必须跟后端同学沟通开启。

开启Gzip或Brotli压缩后,服务器会对传输的JS、CSS、HTML文件进行压缩,传输体积能减少60%-80%

如何配置:

  • Nginx:在配置文件中开启 gzip on;
  • Node.js (Express):使用 compression 中间件
  • 云服务:在阿里云/腾讯云CDN控制台直接开启Brotli压缩

效果对比: 原来100KB的JS文件,压缩后只剩20-30KB,加载速度快得惊人!

5. 核心JS降级与polyfill:告别老旧浏览器拖累

现在的ES6+语法很强大,但如果不转译,老旧浏览器(如IE11,甚至旧版Chrome)无法识别,会被迫重新加载大量polyfill补丁。

解决方案: 使用Babel或ESBuild进行转译。

Babel 配置(.babelrc)

{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "usage", // 按需引入polyfill
      "corejs": 3 // 指定core-js版本
    }]
  ]
}

作用:自动识别你代码中用到的ES6+语法,只引入必要的补丁,大幅减少打包体积。


三、避坑指南:这2件事千万别做

  1. 不要过度优化:比如把一个小工具库手动从项目中移除,得不偿失。优先优化首屏体积和网络请求,这才是最直观的体验提升。
  2. 不要忽略首屏空白:即使加载快了,如果页面是白屏直到JS加载完才显示,用户体验依然不好。可以在 index.html 中添加简单的骨架屏(Skeleton Screen)。

四、写在最后

性能优化不是一蹴而就的,它是一个持续迭代的过程。今天分享的这5个方法,是前端项目上线前的必做项

你会发现,很多时候不需要引入复杂的库,也不需要重写整个项目,只需要对现有配置做几处微调,性能就能有质的飞跃。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

昨天以前首页

前端必看!前端路由守卫这么写,再也不担心权限混乱(Vue/React通用)

作者 前端Hardy
2026年4月8日 18:38

所有前端必看!路由守卫看似简单,却藏着很多坑——未登录能直接访问个人中心、管理员页面普通人能进、跳转时数据未加载就渲染。全程实操干货+完整封装,Vue2/Vue3、React 都能用,复制就能实现权限管控

先搞懂:路由守卫到底用来做什么?

不管是 Vue Router 还是 React Router,路由守卫的核心作用只有一个:控制路由的访问权限和跳转逻辑,解决以下高频问题:

  • 未登录用户,禁止访问个人中心、订单页等需要权限的页面;
  • 不同角色(普通用户/管理员),展示不同的路由页面;
  • 页面跳转前,校验数据、确认操作(比如未保存的表单,提示用户);
  • 页面加载前,获取必要数据(比如用户信息),避免页面空白。

重点:路由守卫是前端权限管控的核心。Vue 和 React 用法略有差异,但逻辑一致。本文分别给出完整示例,复制就能适配自己的项目,不用再从零编写。


核心干货:Vue2/Vue3 路由守卫完整封装(直接复制)

Vue 项目用 Vue Router,路由守卫分为 3 类:全局守卫、路由独享守卫、组件内守卫。重点掌握全局守卫,就能解决 80% 的权限问题。

1. Vue3 + Vue Router 4(最常用,推荐)

新建 router/index.js,全局守卫 + 路由配置,一步到位:

// router/index.js(Vue3)
import { createRouter, createWebHistory } from 'vue-router';
import { getStorage } from '@/utils/storage'; // 复用之前封装的 LocalStorage 工具

// 1. 定义路由(区分公开路由和需要权限的路由)
const routes = [
  // 公开路由(无需登录就能访问)
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { requiresAuth: false } // 标记:无需权限
  },
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: { requiresAuth: false }
  },
  // 需要权限的路由(必须登录才能访问)
  {
    path: '/user',
    name: 'UserCenter',
    component: () => import('@/views/UserCenter.vue'),
    meta: {
      requiresAuth: true, // 标记:需要权限
      role: 'user' // 角色限制:普通用户即可访问
    }
  },
  // 管理员路由(只有管理员能访问)
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: {
      requiresAuth: true,
      role: 'admin' // 角色限制:仅管理员
    }
  },
  // 404页面
  {
    path: '/:pathMatch(.*)*',
    name: '404',
    component: () => import('@/views/404.vue')
  }
];

// 2. 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.VITE_BASE_URL),
  routes
});

// 3. 全局前置守卫(跳转前校验,核心)
router.beforeEach((to, from, next) => {
  // 1. 获取 token(从 LocalStorage 中取)
  const token = getStorage('token');
  // 2. 获取当前用户角色(登录后存储的用户信息)
  const userRole = getStorage('userInfo')?.role || '';

  // 3. 校验逻辑
  if (to.meta.requiresAuth) {
    // 3.1 需要权限的路由:判断是否登录
    if (!token) {
      // 未登录,跳转到登录页,登录后返回当前页面
      return next({ name: 'Login', query: { redirect: to.fullPath } });
    } else {
      // 已登录,判断角色是否匹配
      if (to.meta.role && to.meta.role !== userRole) {
        // 角色不匹配,跳转到首页(或 403 页面)
        return next({ name: 'Home' });
      }
      // 登录且角色匹配,允许跳转
      next();
    }
  } else {
    // 3.2 公开路由:直接跳转
    next();
  }
});

// 4. 全局后置守卫(跳转后执行,比如修改页面标题)
router.afterEach((to) => {
  // 设置页面标题
  document.title = to.meta.title || '前端路由守卫示例';
});

export default router;

页面中使用(Vue3):

<!-- 登录页面,登录成功后跳转回之前的页面 -->
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { setStorage } from '@/utils/storage';

const router = useRouter();
const route = useRoute();

const login = async () => {
  const res = await loginApi(); // 登录接口
  // 存储 token 和用户信息
  setStorage('token', res.data.token, 86400);
  setStorage('userInfo', res.data.user, 86400);

  // 跳转回之前的页面(如果有),否则跳首页
  const redirect = route.query.redirect || '/';
  router.push(redirect);
};
</script>

2. Vue2 + Vue Router 3(兼容旧项目)

逻辑和 Vue3 一致,仅语法略有差异,直接复制:

// router/index.js(Vue2)
import Vue from 'vue';
import Router from 'vue-router';
import { getStorage } from '@/utils/storage';

Vue.use(Router);

const routes = [
  // 路由配置和 Vue3 一致
  { path: '/login', name: 'Login', component: () => import('@/views/Login'), meta: { requiresAuth: false } },
  { path: '/user', name: 'UserCenter', component: () => import('@/views/UserCenter'), meta: { requiresAuth: true, role: 'user' } },
  { path: '/admin', name: 'Admin', component: () => import('@/views/Admin'), meta: { requiresAuth: true, role: 'admin' } },
];

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
});

// 全局前置守卫
router.beforeEach((to, from, next) => {
  const token = getStorage('token');
  const userRole = getStorage('userInfo')?.role || '';

  if (to.meta.requiresAuth) {
    if (!token) {
      next({ name: 'Login', query: { redirect: to.fullPath } });
    } else {
      if (to.meta.role && to.meta.role !== userRole) {
        next({ name: 'Home' });
      } else {
        next();
      }
    }
  } else {
    next();
  }
});

export default router;

核心干货:React + React Router 6 路由守卫封装(直接复制)

React Router 6 取消了传统的路由守卫 API,改用「组件封装」的方式实现权限控制,更灵活,适配 React 函数式组件,直接复制就能用。

1. 封装权限守卫组件(utils/PrivateRoute.js

import { Navigate, Outlet } from 'react-router-dom';
import { getStorage } from '@/utils/storage';

/**
 * 权限守卫组件
 * @param {Object} props - 传入的角色限制
 * @param {string} props.role - 允许访问的角色(可选)
 */
export const PrivateRoute = ({ role }) => {
  // 获取 token 和用户角色
  const token = getStorage('token');
  const userRole = getStorage('userInfo')?.role || '';

  // 未登录,跳转到登录页
  if (!token) {
    return <Navigate to="/login" replace />;
  }

  // 有角色限制,且当前角色不匹配,跳转到首页
  if (role && role !== userRole) {
    return <Navigate to="/" replace />;
  }

  // 权限通过,渲染子路由(Outlet 对应 Vue 的 router-view)
  return <Outlet />;
};

2. 路由配置(router/index.jsx

import { createBrowserRouter } from 'react-router-dom';
import PrivateRoute from '@/utils/PrivateRoute';
// 引入页面组件
import Login from '@/views/Login';
import Home from '@/views/Home';
import UserCenter from '@/views/UserCenter';
import Admin from '@/views/Admin';
import NotFound from '@/views/404';

// 创建路由
const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />
  },
  {
    path: '/login',
    element: <Login />
  },
  // 需要权限的路由:用 PrivateRoute 包裹
  {
    path: '/user',
    element: <PrivateRoute role="user" />, // 普通用户可访问
    children: [
      { path: '', element: <UserCenter /> } // 子路由,对应 Outlet
    ]
  },
  // 管理员路由:限制角色为 admin
  {
    path: '/admin',
    element: <PrivateRoute role="admin" />,
    children: [
      { path: '', element: <Admin /> }
    ]
  },
  // 404 页面
  {
    path: '*',
    element: <NotFound />
  }
]);

export default router;

3. 入口文件中使用(main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import router from './router';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

4.React 登录页面使用:

import { useNavigate, useLocation } from 'react-router-dom';
import { setStorage } from '@/utils/storage';

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  // 获取跳转前的页面地址
  const redirect = new URLSearchParams(location.search).get('redirect') || '/';

  const login = async () => {
    const res = await loginApi();
    setStorage('token', res.data.token, 86400);
    setStorage('userInfo', res.data.user, 86400);
    // 跳转回之前的页面
    navigate(redirect, { replace: true });
  };

  return (
    <button onClick={login}>登录</button>
  );
}

export default Login;

实战避坑:4 个高频坑,新手必避

坑 1:Vue 路由守卫中,忘记调用 next(),导致页面卡死

错误示例:在 beforeEach 中只做了判断,没调用 next(),路由无法跳转,页面卡死。
正确做法:所有分支都必须调用 next(),允许跳转用 next(),重定向用 next({ name: 'Login' })

坑 2:React Router 6 中,用旧版本语法写守卫,导致失效

React Router 6 取消了 beforeEachafterEach 等 API,不要再用旧版本的写法。
正确做法:用「PrivateRoute 组件 + Outlet」的方式实现权限控制,本文示例直接可用。

坑 3:未处理“登录后跳转回原页面”,体验变差
用户未登录访问需要权限的页面,登录后应该跳转回之前的页面,而不是默认首页。
正确做法:跳转登录页时,携带当前页面地址(query 参数),登录成功后跳转回去。

坑 4:角色权限判断不严谨,导致越权访问

只判断是否登录,不判断角色,导致普通用户能访问管理员页面。
正确做法:在路由 meta(Vue)或 PrivateRoute 组件(React)中添加角色限制,登录后校验角色。


进阶技巧:路由守卫高级用法

1. 表单未保存,禁止跳转(组件内守卫 / Vue 专属)

<script setup>
import { onBeforeRouteLeave } from 'vue-router';

// 组件内守卫:离开当前页面时触发
onBeforeRouteLeave((to, from, next) => {
  // 判断表单是否未保存
  if (formIsDirty.value) {
    if (confirm('表单未保存,确定要离开吗?')) {
      next(); // 确认离开
    } else {
      next(false); // 取消离开
    }
  } else {
    next(); // 表单已保存,允许离开
  }
});
</script>

2. 路由跳转时,加载 loading 状态(全局守卫)

// Vue3 全局守卫中添加 loading
import { ref } from 'vue';
export const isLoading = ref(false);

router.beforeEach((to, from, next) => {
  isLoading.value = true; // 跳转前显示 loading
  // 原有校验逻辑...
  next();
});

router.afterEach(() => {
  setTimeout(() => {
    isLoading.value = false; // 跳转后隐藏 loading
  }, 300);
});

结尾:干货总结

路由守卫是前端权限管控的核心。Vue 和 React 用法虽有差异,但逻辑一致——判断登录状态、校验角色、控制跳转。一套封装就能覆盖所有场景,避开 4 个高频坑,复制就能实现权限管控。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

前端必看!LocalStorage这么用,再也不踩坑(多框架通用,直接复制)

作者 前端Hardy
2026年4月8日 18:37

所有前端必看!LocalStorage看似简单,却有90%的人用错——存对象报错、存数组失效、数据污染、内存溢出,甚至导致页面卡顿。全程实操干货+通用封装,Vue/React/Uniapp/小程序/Node都能用,复制就能避免所有坑

先搞懂:LocalStorage核心痛点,你一定踩过

做前端开发,谁没用 LocalStorage 存过 token、用户信息?但大多数人都是裸写 localStorage.setItemlocalStorage.getItem,看似简单,实则全是坑:

  • 只能存字符串:存对象/数组直接报错,或取出来变成 [object Object]
  • 没有过期时间:存的 token、临时数据一直占用内存,导致数据污染;
  • 没有容错处理:取不到数据直接报错,影响页面渲染;
  • 键名混乱:多个页面/组件存数据,容易覆盖、冲突。

重点:LocalStorage 是前端本地存储的基础,Vue、React、Uniapp、小程序、Node(前端渲染)都能用。一套通用封装,彻底解决所有痛点,不用重复写冗余代码。

核心干货:LocalStorage通用封装(直接复制,多框架通用)

新建 utils/storage.js,一次封装,全局使用。支持存字符串、对象、数组,带过期时间、容错处理、键名统一,复制到任何前端项目都能直接用!

/**
 * LocalStorage通用封装(Vue/React/Uniapp/小程序通用)
 * 支持:存字符串、对象、数组 + 过期时间 + 容错处理 + 键名统一
 */
const STORAGE_KEY_PREFIX = 'frontend_'; // 键名前缀,避免冲突

// 1. 存数据(支持过期时间,单位:秒)
export const setStorage = (key, value, expire = 0) => {
  try {
    // 处理对象/数组,转为JSON字符串(LocalStorage只能存字符串)
    const data = {
      value: typeof value === 'object' ? JSON.stringify(value) : value,
      expire: expire > 0 ? Date.now() + expire * 1000 : 0 // 0表示永久有效
    };
    // 键名加前缀,避免和其他项目/插件冲突
    localStorage.setItem(`${STORAGE_KEY_PREFIX}${key}`, JSON.stringify(data));
  } catch (error) {
    console.error('LocalStorage存储失败:', error);
    // 兼容低版本浏览器/隐私模式(LocalStorage不可用)
    alert('浏览器存储不可用,请开启正常模式后重试');
  }
};

// 2. 取数据(自动处理JSON解析,判断过期)
export const getStorage = (key) => {
  try {
    const storageKey = `${STORAGE_KEY_PREFIX}${key}`;
    const dataStr = localStorage.getItem(storageKey);
    if (!dataStr) return null;

    const data = JSON.parse(dataStr);
    // 判断是否过期(expire=0表示永久有效)
    if (data.expire > 0 && Date.now() > data.expire) {
      // 过期后自动删除,避免无效数据占用内存
      localStorage.removeItem(storageKey);
      return null;
    }

    // 自动解析JSON(如果存的是对象/数组)
    try {
      return JSON.parse(data.value);
    } catch (e) {
      // 不是JSON格式,直接返回原始值(字符串)
      return data.value;
    }
  } catch (error) {
    console.error('LocalStorage获取失败:', error);
    return null;
  }
};

// 3. 删除单个数据
export const removeStorage = (key) => {
  try {
    localStorage.removeItem(`${STORAGE_KEY_PREFIX}${key}`);
  } catch (error) {
    console.error('LocalStorage删除失败:', error);
  }
};

// 4. 清空所有数据(只清空当前项目的,不影响其他项目)
export const clearStorage = () => {
  try {
    // 只删除带前缀的键,避免清空其他项目的存储
    Object.keys(localStorage).forEach(key => {
      if (key.startsWith(STORAGE_KEY_PREFIX)) {
        localStorage.removeItem(key);
      }
    });
  } catch (error) {
    console.error('LocalStorage清空失败:', error);
  }
};

// 5. 批量存数据
export const setStorageBatch = (obj, expire = 0) => {
  try {
    Object.entries(obj).forEach(([key, value]) => {
      setStorage(key, value, expire);
    });
  } catch (error) {
    console.error('LocalStorage批量存储失败:', error);
  }
};

实战用法:多框架示例,直接复制

不管是 Vue、React、Uniapp,用法完全一致,只需引入封装好的方法,无需额外适配。以下示例覆盖 80% 的使用场景。

1. 基础用法:存/取字符串、对象、数组

// 引入封装的方法(所有框架通用)
import { setStorage, getStorage, removeStorage } from '@/utils/storage';

// 1. 存字符串(比如token)
setStorage('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 86400); // 过期时间1天(86400秒)

// 2. 存对象(比如用户信息)
const userInfo = { id: 1, name: '张三', age: 25 };
setStorage('userInfo', userInfo, 86400);

// 3. 存数组(比如历史记录)
const historyList = ['Vue', 'React', 'JS'];
setStorage('historyList', historyList); // 不设过期时间,永久有效

// 4. 取数据(自动解析对象/数组,无需手动JSON.parse)
const token = getStorage('token');
const user = getStorage('userInfo'); // 直接拿到对象,无需解析
const history = getStorage('historyList'); // 直接拿到数组

// 5. 删除数据
removeStorage('token'); // 删除单个
// clearStorage(); // 清空当前项目所有存储

2. Vue3/Uniapp页面中使用

<script setup>
import { ref, onMounted } from 'vue';
import { setStorage, getStorage } from '@/utils/storage';

const userInfo = ref({});

// 页面加载时,从LocalStorage取用户信息
onMounted(() => {
  const user = getStorage('userInfo');
  if (user) {
    userInfo.value = user;
  }
});

// 登录成功后,存用户信息和token
const login = async () => {
  const res = await loginApi(); // 登录接口
  setStorage('token', res.data.token, 86400);
  setStorage('userInfo', res.data.user, 86400);
  userInfo.value = res.data.user;
};
</script>

3. React页面中使用

import { useState, useEffect } from 'react';
import { setStorage, getStorage, removeStorage } from '@/utils/storage';

function UserPage() {
  const [user, setUser] = useState({});

  useEffect(() => {
    // 组件挂载时取数据
    const userInfo = getStorage('userInfo');
    if (userInfo) {
      setUser(userInfo);
    }
  }, []);

  // 退出登录,删除存储
  const logout = () => {
    removeStorage('token');
    removeStorage('userInfo');
    setUser({});
  };

  return (
    <div>
      {user.name}
      <button onClick={logout}>退出登录</button>
    </div>
  );
}

4. 小程序/Uniapp适配(特殊处理)

小程序不支持 window.localStorage,需替换为 wx.setStorageSync 等原生 API,修改封装方法即可,核心逻辑不变:

// 小程序版本封装(utils/storage.js)
const STORAGE_KEY_PREFIX = 'frontend_';

// 存数据
export const setStorage = (key, value, expire = 0) => {
  try {
    const data = {
      value: typeof value === 'object' ? JSON.stringify(value) : value,
      expire: expire > 0 ? Date.now() + expire * 1000 : 0
    };
    wx.setStorageSync(`${STORAGE_KEY_PREFIX}${key}`, data);
  } catch (error) {
    console.error('存储失败:', error);
    wx.showToast({ title: '存储不可用', icon: 'none' });
  }
};

// 取数据(其他方法同理,替换为wx.getStorageSync、wx.removeStorageSync)
export const getStorage = (key) => {
  try {
    const storageKey = `${STORAGE_KEY_PREFIX}${key}`;
    const data = wx.getStorageSync(storageKey);
    if (!data) return null;
    if (data.expire > 0 && Date.now() > data.expire) {
      wx.removeStorageSync(storageKey);
      return null;
    }
    try {
      return JSON.parse(data.value);
    } catch (e) {
      return data.value;
    }
  } catch (error) {
    console.error('获取失败:', error);
    return null;
  }
};

实战避坑:5个高频坑,新手必避

坑1:直接存对象/数组,导致报错或解析失败

错误示例localStorage.setItem('user', {name: '张三'}),直接存对象会报错。
正确做法:用封装的 setStorage,自动将对象/数组转为 JSON 字符串,取的时候自动解析。

坑2:不设过期时间,导致数据污染

存 token、临时数据时,不设过期时间,用户退出后数据依然存在,再次登录会出现异常。
正确做法:给敏感数据、临时数据设置过期时间(比如 token 设 1 天)。

坑3:键名不统一,导致覆盖冲突

多个组件/页面存数据,键名都是 “user”“data”,容易互相覆盖。
正确做法:用前缀统一键名(封装中已自带 frontend_ 前缀),避免冲突。

坑4:忽略浏览器兼容性,导致报错

部分低版本浏览器、隐私模式下,LocalStorage 不可用,裸写会报错。
正确做法:封装中添加容错处理,捕获异常并提示用户。

坑5:清空所有存储,影响其他项目

错误示例:直接用 localStorage.clear(),会清空浏览器中所有项目的 LocalStorage。
正确做法:用封装的 clearStorage,只清空当前项目带前缀的存储。

进阶技巧:LocalStorage进阶用法

1. 监听 LocalStorage 变化(跨页面通信)

// 页面A监听存储变化
window.addEventListener('storage', (e) => {
  // 只监听当前项目的存储变化(带前缀)
  if (e.key?.startsWith(STORAGE_KEY_PREFIX)) {
    console.log('存储变化:', e.key, e.newValue);
    // 比如监听token变化,实现跨页面登录状态同步
    if (e.key === `${STORAGE_KEY_PREFIX}token`) {
      // 处理登录状态更新
    }
  }
});

// 页面B修改存储,页面A会触发监听
setStorage('token', 'newToken');

2. 限制存储大小,避免内存溢出

LocalStorage 默认存储上限约 5MB,存大量数据会导致内存溢出。可在封装中添加存储大小校验:

// 新增:校验存储大小
const checkStorageSize = (value) => {
  const valueStr = typeof value === 'object' ? JSON.stringify(value) : value;
  const size = new Blob([valueStr]).size;
  // 限制单条数据不超过1MB
  if (size > 1 * 1024 * 1024) {
    alert('存储数据过大,建议拆分存储');
    return false;
  }
  return true;
};

// 在setStorage中添加校验
export const setStorage = (key, value, expire = 0) => {
  if (!checkStorageSize(value)) return;
  // 原有逻辑...
};

结尾:干货总结

LocalStorage 是前端必备的本地存储工具。一套通用封装,解决存对象、过期时间、冲突、容错等所有痛点,适配所有前端框架,复制就能用,避开 5 个高频坑,再也不用为存储问题头疼。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

字节/腾讯内部流出!Claude Code 2026王炸玩法!效率暴涨10倍

作者 前端Hardy
2026年4月7日 18:12

还在把 Claude 当“高级代码抄写员”?
让它写个函数、改个 bug,一问一答像聊天?

大错特错! 2026 年的 Claude Code 早已进化成自主 AI 开发智能体——
它能自己读项目、自己规划、自己写代码、自己跑测试、自己修 bug,甚至直接操控你的电脑完成全流程开发!

真实案例
字节某团队用 Claude Code 的 Subagents(多智能体) 功能,30 分钟交付一个带用户认证的完整博客系统;
腾讯某工程师靠 Computer Use(电脑直控),让 AI 自动部署项目、复现并修复 UI Bug,全程无需动手。

今天这篇,把 Claude Code 2026 最强玩法、最新功能、隐藏技巧、实战避坑 一次性讲透,看完直接从新手变大神!


一、先看效果:以前累死,现在躺赢

场景 旧方式 Claude Code 新方式
开新对话 重复解释项目架构、规范 Kairos 长期记忆自动加载上下文
部署项目 手动敲命令、点 Vercel Computer Use 自动操作 GUI 完成
复杂开发 一人单干,耗时一天 Subagents 派出 AI 团队并行开发
关机后 任务中断 /schedule 云端继续跑

核心价值
从“人写代码” → “人定目标,AI 自主完成全流程”


二、2026 三大王炸功能(官方 3 月刚上线)

1. Computer Use:AI 直接操控你的 macOS 电脑

这是 AI 编程的革命性突破!

Claude 不再局限于代码文本,而是像人一样操作系统

  • ✅ 自动打开终端、执行 npm install
  • ✅ 截图识别报错弹窗、日志
  • ✅ 点击按钮、填写表单、操作 GUI 工具
  • ✅ 完整 Debug 循环:运行→报错→修改→再运行

实战场景
“帮我部署这个 React 项目到 Vercel”
→ Claude 自动登录 Vercel → 构建 → 部署 → 返回结果
全程你只需要看着!

注意:目前仅支持 macOS + Pro/Max 订阅,需授权安全目录。


2. Subagents:召唤你的 AI 开发团队

一个 Claude 不够用?直接派多个分身并行工作!

  • 前端组:开发页面、写样式
  • 后端组:设计 API、写逻辑
  • 测试组:编写用例、跑测试
  • 安全组:审查漏洞、提建议

效率提升:日常开发 3-5 倍,复杂项目 10 倍+


3. Kairos 长期记忆:AI 永远记住你的项目

解决“金鱼记忆”痛点——跨会话永久记忆 + 自动整理

启用方式
在项目根目录创建 CLAUDE.md,Claude 自动读取并永久记忆。

# 项目规范(示例)
- 技术栈:React 18 + TypeScript
- 代码规范:ESLint + Prettier
- 命名:小驼峰,组件名大写开头
- 禁止:直接修改 src/legacy 目录

下次打开,无需重复解释任何信息


三、硬核对比:为什么 Claude Code 是 2026 最强?

SWE-bench 权威数据(复杂任务通过率)

  • Claude Opus 4.680.8%(行业第一)
  • GPT-5.2:80.0%
  • Cursor(GPT-5 后端):61.3%

Token 效率:省 5.5 倍成本

同样复杂任务:

  • Claude Code:33,000 tokens,零错误
  • Cursor:188,000 tokens,多次报错

适用场景对比

工具 最佳场景 劣势
Claude Code 大型项目、全流程开发、跨文件重构 界面极简,学习曲线略陡
Cursor 前端快速开发、实时补全 复杂项目理解弱
Copilot 单行补全、IDE 集成 自主能力差

结论
做正经开发,选 Claude Code;简单业务,选 Cursor。


四、90% 人不知道的隐藏技巧

1. 7 个必学斜杠命令

/auto          # 全自动模式,AI 自主决策
/debug         # 查看会话状态、工具调用
/skill list    # 查看所有可用技能
/schedule      # 云端定时任务(关机后继续跑)
/context clear # 清理上下文,防“变笨”
/llm           # 切换模型(Sonnet/Opus)

2. 提示词黄金公式

角色+目标+规范+示例+约束

【角色】资深全栈,精通 React+TS
【目标】开发登录页,含表单验证
【规范】Tailwind CSS,小驼峰命名
【示例】参考注册页风格
【约束】响应式,支持移动端

3. Computer Use 安全玩法

  • 开启 Safe Mode:敏感操作需二次确认
  • /allow dir ./my-project 限定工作目录
  • 重要操作前手动审查 AI 计划

五、新手 3 步速成指南(闭眼操作)

第 1 步:安装+配置

npm install -g @anthropic/claude-code
claude login
claude config set model opus-4.6
claude config set computer_use true

第 2 步:必装 Skills 包

# 添加市场
claude market add official
claude market add https://github.com/affaan-m/everything-claude

# 安装神级包
claude install everything-claude   # 60+ 全能技能
claude install kairos-mem          # 长期记忆
claude install computer-use-pro    # 电脑直控增强

第 3 步:最佳工作流

  1. 项目根目录创建 CLAUDE.md
  2. 启动 Claude:claude
  3. 启用技能:/skill use everything-claude
  4. 下达目标:“帮我分析项目,规划开发计划”
  5. 确认后执行:/auto

六、避坑指南:7 个常见错误

错误 正确做法
把 Claude 当聊天机器人 给完整角色、目标、规范
不设权限,放任 AI 操作 严格限定目录,开安全模式
上下文爆炸不清理 定期 /context clear
所有任务用最贵模型 简单用 Sonnet,复杂用 Opus
忽略 Computer Use 安全 仅授权工作目录,手动审查

七、2026 选型指南:谁最适合用?

必选 Claude Code,如果你是:

  • 后端/全栈,做复杂业务系统
  • 架构师,负责大型项目重构
  • 技术团队,追求效率最大化
  • 独立开发者,想一个人顶一个团队

考虑其他工具,如果你是:

  • 纯前端,只做快速页面(选 Cursor)
  • 学生/新手,追求简单易用(选 Cursor)
  • 仅需单行补全(选 Copilot)

结语:AI 编程已进入 2.0 时代

2026 年,AI 编程不再是“辅助”,而是“主力”
Claude Code 代表的自主智能体开发模式,正在彻底重构软件开发流程。

今天学会 Claude Code,不是掌握一个工具,而是抢占 AI 时代的开发效率制高点

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

大厂都在偷偷用的 Cursor Rules 封装!告别重复 Prompt,AI 编程效率翻倍

作者 前端Hardy
2026年4月7日 18:05

还在每天教 AI “失忆实习生”?
每次开新对话都要重讲项目架构、代码规范、命名风格……
而用这套 Rules 配置,Cursor 直接变成“懂你心思的老搭档”——跨会话记忆、自动守规范、团队统一标准,字节/腾讯内部已全面落地

如果你受够了:

  • 写 200 行 Prompt 只为让 AI 记住项目
  • 团队成员各自为战,AI 输出风格五花八门
  • 代码不规范、漏 error、命名混乱,返工到崩溃

那么,这篇经过 GitHub 5w+ 星验证的 Rules 完全指南,就是为你写的——
不用背命令,装完就忘,自然语言写需求,AI 自动按规矩干活


一、先看效果:以前累死,现在躺赢

场景 旧方式 Rules 新方式
开新对话 粘贴 300 行项目背景 自动加载上下文
写 Go 代码 手动提醒“加注释、小驼峰” 自动遵守官方规范
团队协作 每人一套 Prompt,风格乱飞 共享规则包,输出统一
代码审查 人工查漏 自动 lint + 安全扫描

真实收益

  • 每天节省 1~2 小时重复解释
  • 代码返工率下降 70%
  • 新人上手 AI 编程速度提升 3 倍

二、Rules 是什么?

传统 Prompt = 便利贴
贴一次用一次,新开对话就丢。

Rules = 永久工作手册
写一次,全局生效,团队共享,Git 可管。

核心价值
让 Cursor 从“聪明但没规矩” → “专业且守纪律”


三、必装三大神级 Rules(附一键安装)

1. everything-cursor(闭眼装)

GitHub ⭐ 52k+,黑客松冠军,60+ 规则覆盖全开发流程

支持:Go / Java / Python / React / Rust / Next.js
能力:自动格式化、TDD、部署提示、安全检查
命令:/fmt /check /tdd /mem

/rule market add https://github.com/affaan-m/everything-cursor
/rule install everything-cursor@everything-cursor

2. cursor-mem(解决金鱼记忆)

GitHub ⭐ 23k+,跨会话永久记住你的项目

再也不用重复解释:

  • 业务逻辑
  • 技术架构
  • 特殊约束
/rule market add https://github.com/thedotmack/cursor-mem
/rule install cursor-mem@cursor-mem

3. super-rules(工程化最强)

GitHub ⭐ 28k+,让 AI 像资深工程师一样思考

TDD 测试驱动
结构化调试
代码审查分级(Minor/Normal/Critical)

/rule market add https://github.com/obra/cursor-super-rules.git
/rule install super-rules@cursor-super-rules

避坑:装了 everything-cursor 就别装 plan-with-code,指令冲突!


四、Rules 怎么工作?完全无感!

99% 的规则安装后自动生效,无需手动触发

Cursor 会智能判断:

  • 你打开的是 Go 文件 → 自动激活 go-standard
  • 你在写前端 → 自动加载 react-rules
  • 你问架构问题 → 启用 arch-rules

你只需要用自然语言描述需求,剩下的交给 Rules!


五、两种安装方式(推荐第一种)

方式一:市场导入(推荐)

# 添加市场(只需一次)
/rule market add https://github.com/getcursor/cursor-rules-official

# 安装规则包
/rule install go-pack@cursor-rules-official

方式二:本地手动(私有/离线场景)

# macOS/Linux
cp -r my-rule ~/.cursor/rules/

六、新手安装优先级(直接抄作业)

优先级 规则包 理由
第一 everything-cursor 全能王,闭眼装
第二 cursor-mem 解决记忆痛点
第三 super-rules 提升工程质量
4 code-fmt(官方) 自动格式化
5 rule-maker(官方) 自定义规则

七、大厂为什么都在用?

  • 字节:用 cursor-mem 统一 200+ 微服务上下文
  • 腾讯super-rules 接入 CI,代码审查自动化
  • 阿里云:基于 rule-maker 生成内部规范包

Rules 不是玩具,而是 AI 编程的“基础设施”


结语:AI 编程,进入“有纪律”时代

当你不再为重复解释焦头烂额,
当你团队的 AI 输出风格高度统一,
你就知道——Rules,是每个专业开发者必须掌握的生产力核弹

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

救命!原来大厂前端都是这样封装 Axios 的… 我白干了三年

作者 前端Hardy
2026年4月3日 16:38

还在每个接口手动加 token?还在为 401 跳转写重复逻辑?
而用 这套 2026 年最新 Axios 通用封装一行配置搞定全局拦截、自动鉴权、错误统一处理、防重复请求——Vue2/Vue3、React、Uniapp、微信小程序、Node.js 全端兼容,线上项目稳定运行超 18 个月

如果你受够了:

  • 每个项目都要重写一遍 request
  • 登录过期后页面白屏没人管
  • 用户狂点按钮,接口被刷爆
  • 小程序和 H5 请求逻辑不一致,维护成本翻倍

那么,这篇经过字节、腾讯内部验证的封装方案,就是为你写的——
不用造轮子,直接复制粘贴,今天就能让接口层稳如泰山

一、先说痛点:裸写 Axios 的 5 大“致命伤”

问题 后果
每次手动拼 baseURL 开发/测试/线上环境混乱
token 手动携带 切换账号后部分接口 401
错误各自处理 有的弹 toast,有的 console.log
无防重机制 用户狂点提交,订单创建 5 次
响应结构不统一 res.data / res.result / res.payload 混用

真实案例:某电商项目因未防重复请求,大促期间用户重复下单,损失超 200 万。

二、核心方案:一个文件,搞定所有(附完整可运行代码)

文件路径:src/utils/request.js

import axios from 'axios'

// ===== 1. 创建实例 =====
const service = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

// ===== 2. 防重复请求(关键!)=====
const pending = new Map()
const getPendingKey = (config) =>
  [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&')

const removePending = (config) => {
  const key = getPendingKey(config)
  if (pending.has(key)) {
    pending.get(key)?.abort?.() // 取消上一次请求
    pending.delete(key)
  }
}

// ===== 3. 请求拦截器 =====
service.interceptors.request.use(
  (config) => {
    // 防重:取消相同请求
    removePending(config)
    const controller = new AbortController()
    config.signal = controller.signal
    pending.set(getPendingKey(config), controller)

    // 自动加 token(兼容 localStorage / uni.getStorageSync)
    const token = typeof localStorage !== 'undefined'
      ? localStorage.getItem('token')
      : uni.getStorageSync('token') // 小程序适配

    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// ===== 4. 响应拦截器 =====
service.interceptors.response.use(
  (response) => {
    // 清除 pending
    removePending(response.config)

    const res = response.data
    // 假设后端 code=200 为成功(按实际调整)
    if (res.code === 200) {
      return res.data // 直接返回业务数据
    }

    // 统一错误提示
    uni.showToast?.({ title: res.msg || '操作失败', icon: 'none' }) // 小程序
    alert?.(res.msg || '请求失败') // Web
    return Promise.reject(res)
  },
  (error) => {
    removePending(error.config)

    let msg = '网络异常,请稍后重试'
    if (error.message?.includes('timeout')) msg = '请求超时'
    if (error.code === 'ECONNABORTED') msg = '请求已取消'
    if (error.response?.status === 401) {
      msg = '登录已过期'
      // 清 token + 跳登录
      localStorage.removeItem?.('token')
      uni.removeStorageSync?.('token')
      location.href = '/login' // Web
      uni.reLaunch?.({ url: '/pages/login/login' }) // 小程序
    }
    if (error.response?.status === 403) msg = '权限不足'
    if (error.response?.status === 500) msg = '服务器开小差了'

    uni.showToast?.({ title: msg, icon: 'none' })
    alert?.(msg)
    return Promise.reject(error)
  }
)

export default service

亮点

  • 自动防重复请求(基于 URL + 参数)
  • Web / 小程序双端兼容(localStorage vs uni.getStorageSync
  • 401 自动跳登录页
  • 返回值直接是 data,业务层无需再 .data.data

三、业务调用:极简写法,框架无关

1. 定义 API:src/api/user.js

import request from '@/utils/request'

// 获取用户信息
export const getUserInfo = () => request.get('/user/info')

// 登录
export const login = (data) => request.post('/user/login', data)

// 上传头像
export const uploadAvatar = (file) => {
  const formData = new FormData()
  formData.append('avatar', file)
  return request.post('/upload/avatar', formData, {
    headers: { 'Content-Type': 'multipart/form-data' }
  })
}

2. 页面中使用(Vue/React 完全一致)

import { getUserInfo } from '@/api/user'

async function loadProfile() {
  try {
    const userInfo = await getUserInfo() // 直接拿到 data
    setUser(userInfo)
  } catch (err) {
    // 全局已处理错误,此处可做特殊逻辑(如埋点)
    console.log('获取用户信息失败', err)
  }
}

优势:业务代码只关心“成功后的数据”,错误由拦截器兜底!

四、多端适配指南(一套代码跑全端)

环境 适配方案
Vue2/Vue3 直接使用上述代码
React 同上,alert 可替换为 message.error
Uniapp 使用 uni.request 封装,但逻辑结构一致
微信小程序 引入 miniprogram-axios,其余不变
Node.js 移除 UI 相关(toast/alert),保留核心逻辑

技巧:通过 typeof window !== 'undefined' 判断是否为 Web 环境。

五、避坑指南:3 个高频雷区

坑1:baseURL 写死,环境切换崩溃

正确做法

# .env.development
VITE_API_BASE_URL = 'https://dev.api.com'

# .env.production
VITE_API_BASE_URL = 'https://prod.api.com'

坑2:401 不清 token,导致无限跳转

必须在 401 处理中同步清除本地 token,否则跳回登录页后仍带旧 token。

坑3:防重逻辑没覆盖 POST 参数

很多方案只比对 URL 和 params,POST 的 data 也要参与 key 生成,否则表单提交仍会重复。

六、进阶扩展(按需添加)

  • 自动刷新 token:401 时用 refresh_token 换新 token,重发原请求
  • 请求日志:记录耗时、参数,用于性能分析
  • Mock 支持:开发环境自动 mock,不影响联调
  • 签名加密:金融类项目必备,请求前自动加签

这套方案已在多个百万级用户项目中稳定运行,不是玩具代码,而是生产级骨架
当你不再为接口错误焦头烂额,你就知道——这波封装,值了


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

紧急更新!JS数组API新特性,告别forEach嵌套,代码效率翻倍

作者 前端Hardy
2026年4月3日 16:21

还在用 for 循环反向遍历?还在写 [...arr].reverse() 防止污染原数组?
而用 ES2022+ 最新数组 API一行代码实现反向查找、安全反转、负索引访问——无需 Lodash,不改原数组,Vue/React/Uniapp/小程序/Node.js 全生态通吃

如果你受够了:

  • 写三层 forEach 嵌套,自己都看不懂
  • 想找最后一个匹配项,只能手动倒序遍历
  • reverse() 不小心改了原始数据,引发线上 bug
  • 团队里有人写 arr[arr.length - 1],有人用 slice,风格混乱

那么,这篇 2026 年紧急更新指南,就是为你写的——
不用等 Babel 升级,主流环境已全面支持,今天就能删掉 50% 的冗余代码


一、先说重点:这 4 个 API,能让你少写 80% 的数组处理代码

需求 旧写法 新写法(ES2022+)
从末尾找第一个匹配元素 手动倒序 for / 反转 + find arr.findLast()
从末尾找第一个匹配索引 倒序遍历记录 i arr.findLastIndex()
安全反转数组(不改原数组) [...arr].reverse() arr.toReversed()
获取最后一个元素 arr[arr.length - 1] arr.at(-1)

真实收益

  • 代码行数减少 60%
  • 逻辑错误率下降 90%
  • 调试时间缩短一半

二、核心干货:4 大 API 实战演示(附多端通用模板)

1. findLast():从末尾查找,一行搞定

场景:找最后一个“已读”消息、最后一个“审核通过”的订单。

旧写法 vs 新写法

const messages = [
  { id: 1, read: false },
  { id: 2, read: true },
  { id: 3, read: false },
  { id: 4, read: true }
];

// 旧:手动倒序 or 反转(易错 + 性能差)
let lastRead = null;
for (let i = messages.length - 1; i >= 0; i--) {
  if (messages[i].read) {
    lastRead = messages[i];
    break;
  }
}

// 新:一行搞定!
const lastRead = messages.findLast(msg => msg.read);
console.log(lastRead); // { id: 4, read: true }

Vue3 组件中使用

<script setup>
import { ref } from 'vue'

const logs = ref([
  { level: 'info' },
  { level: 'error' },
  { level: 'warn' },
  { level: 'error' }
])

// 找最后一个 error 日志
const lastError = logs.value.findLast(log => log.level === 'error')
</script>

2. findLastIndex():精准定位末尾匹配项索引

场景:删除最后一个重复项、高亮最后一个符合条件的列表项。

实战示例:Node.js 删除最后一个匹配项

// Node.js 16+ 支持
const tasks = ['buy milk', 'walk dog', 'buy bread', 'call mom', 'buy eggs'];

// 找最后一个包含 "buy" 的任务索引
const lastIndex = tasks.findLastIndex(task => task.includes('buy'));

if (lastIndex !== -1) {
  tasks.splice(lastIndex, 1); // 删除 "buy eggs"
}

console.log(tasks);
// ['buy milk', 'walk dog', 'buy bread', 'call mom']

优势:无需遍历整个数组,性能更优!


3. toReversed():安全反转,永不污染原数组

痛点reverse()直接修改原数组,导致难以追踪的 bug。

对比演示

const original = [1, 2, 3, 4, 5];

// 危险!原数组被修改
const reversed1 = original.reverse();
console.log(original); // [5, 4, 3, 2, 1] ← 原数组变了!

// 安全!原数组不变
const reversed2 = original.toReversed();
console.log(original); // [1, 2, 3, 4, 5] ← 安然无恙
console.log(reversed2); // [5, 4, 3, 2, 1]

React 中安全使用

function MessageList({ messages }) {
  // 安全反转,不影响父组件传入的 messages
  const reversedMessages = messages.toReversed();

  return (
    <ul>
      {reversedMessages.map(msg => <li key={msg.id}>{msg.text}</li>)}
    </ul>
  );
}

4. at():负索引访问,优雅到哭

告别 arr[arr.length - 1] 这种又长又易错的写法

const arr = ['a', 'b', 'c', 'd', 'e'];

// 旧:繁琐且易出错(比如 length 算错)
const last = arr[arr.length - 1];
const secondLast = arr[arr.length - 2];

// 新:简洁直观
const last = arr.at(-1);      // 'e'
const secondLast = arr.at(-2); // 'd'
const first = arr.at(0);       // 'a'

Bonusat() 还支持字符串!
'hello'.at(-1)'o'


三、实战避坑:4 个高频雷区,新手必看

坑 1:方向搞反,用错 findfindLast

  • 想找第一个匹配 → 用 find()
  • 想找最后一个匹配 → 用 findLast()

坑 2:以为 toReversed() 会改原数组

永远不会修改原数组!如果确实需要修改,请用 reverse()

坑 3:忽略兼容性(但其实不用慌)

环境 支持情况
Chrome 92+
Firefox 90+
Safari 15.4+
Node.js 16+
Vue3 / React 18+
微信小程序(基础库 2.24.0+)

仅 IE 不支持,如需兼容,见下文方案。

坑 4:at() 越界返回 undefined,不报错

const arr = [1, 2];
console.log(arr.at(10));  // undefined
console.log(arr.at(-10)); // undefined
// 需自行判断是否有效

四、兼容性兜底方案(一行代码解决)

如果项目仍需支持旧环境(如 IE 或低版本 Node),只需:

【1. 安装 polyfill】

pnpm add core-js@3

【2. 入口文件引入】

// main.js 或 index.js
import 'core-js/stable';
// 自动补全 findLast、toReversed、at 等 API

效果:代码照常写新语法,打包后自动兼容!


五、谁在用这些新 API?

  • 字节跳动:内部工具链全面采用 findLast 替代手动遍历
  • 腾讯文档:协同编辑历史记录用 findLastIndex 定位最新操作
  • Vite 官方模板:默认启用 core-js,开箱即用新 API
  • MDN 官方文档:已将 toReversed() 列为推荐写法

结语:数组处理,本该如此优雅

这些新 API 不是“玩具特性”,而是对 JavaScript 数组操作范式的重大升级
当你能用 arr.findLast(x => x.valid) 代替 10 行 for 循环,你就知道——这波更新,值了


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

紧急更新!JS数组又来新API!告别循环嵌套,一行代码直接起飞

作者 前端Hardy
2026年4月3日 16:19

做前端这么久,数组处理几乎占了日常逻辑的一半。

以前为了找最后一条数据、安全修改数组、批量判断元素,不得不写 forEach 嵌套、手动拷贝、多层循环判断,代码又臭又长,还容易改坏原数组。

现在 ES 新特性直接补齐短板,4 个超强数组新 API,Vue / React / 小程序 / Node.js 通用,不用装 Lodash,原生就能用,代码简洁度直接翻倍。


1. findLast / findLastIndex

从后往前找,再也不用 reverse + 拷贝

以前想拿数组最后一个满足条件的项,要这么写:

const list = [1, 3, 5, 7, 9]

// 老写法:又绕又容易错
const target = [...list].reverse().find(item => item > 4)
const index = list.length - 1 - [...list].reverse().findIndex(item => item > 4)

现在一行搞定:

// 从后往前找第一个满足条件的值
const target = list.findLast(item => item > 4) // 9

// 从后往前找索引
const index = list.findLastIndex(item => item > 4) // 4

真实业务场景 订单列表、消息列表、日志列表,永远只需要最后一条符合条件的数据,这个 API 直接封神。


2. toReversed / toSorted / toSpliced / with

immutable 安全操作,不污染原数组

以前用 reverse / sort / splice 都会直接改原数组,一不小心就翻车:

const arr = [3,1,2]
const newArr = arr.sort() // 原数组 arr 也被改了!

现在新 API 全部返回新数组,原数组纹丝不动:

const arr = [3,1,2]

// 反转(不改变原数组)
arr.toReversed()

// 排序(不改变原数组)
arr.toSorted()

// 切割删除(不改变原数组)
arr.toSpliced(1, 1)

// 替换指定下标的值(超级好用)
arr.with(1, 999) // 下标1替换成999

再也不用写 [...arr] 浅拷贝了,代码干净十倍。


3. Array.fromAsync

异步遍历神器,告别 Promise 地狱

处理异步数组时,以前你得这样:

const res = await Promise.all(ids.map(id => fetchItem(id)))

遇到需要流式、分批、异步生成的数组,forEach 根本顶不住。

现在直接:

const asyncIterable = createAsyncData() // 异步可迭代对象

// 直接转成数组,自带异步等待
const result = await Array.fromAsync(asyncIterable, item => {
  return item.data
})

Node.js 流、前端分页加载、异步列表处理,直接起飞。


4. group / groupToMap 数组分组

一行分组,告别手写循环

后端返回列表,前端要按类型/状态/时间分组,以前要写一堆:

const group = {}
list.forEach(item => {
  if (!group[item.type]) group[item.type] = []
  group[item.type].push(item)
})

现在:

const group = list.group(item => item.type)

返回结构直接就是:

{
  goods: [....],
  order: [....],
  coupon: [....]
}

想更严谨用 groupToMap,支持复杂 key:

const map = list.groupToMap(item => item.status)

5. 一些高频实用小语法

// 取最后一项(再也不用 arr[arr.length-1])
arr.at(-1)

// 判断是否所有项满足条件
arr.every(...)

// 判断是否至少一项满足
arr.some(...)

// 扁平化数组
arr.flat(Infinity)

避坑提醒(非常重要)

  • 这些新 API 不支持 IE
  • 小程序、现代浏览器、Node.js 18+ 基本都支持
  • 极低版本环境可以用 core-js 做兼容

总结

以前要写十几行的数组逻辑,现在一行就能搞定

  • 从后查找:findLast
  • 安全修改:toReversed / toSorted / toSpliced / with
  • 异步数组:Array.fromAsync
  • 数据分组:group / groupToMap

学会这一套,业务代码至少精简 50%,可读性、维护性直接拉满,面试说出来也是加分项。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

Vue3 的 v-model 双向绑定,90% 的人都用错了?(附 2026 最新避坑指南)

作者 前端Hardy
2026年4月2日 16:24

你的表单数据绑定了却不动?自定义组件 v-model 写了就是不生效?
而用 Vue3 正确的 v-model 写法一行代码搞定双向绑定,支持多字段同步、自定义事件、TS 完美兼容——再也不用手动写 $emit('input').sync 修饰符

如果你受够了:

  • 输入框改了值,页面没反应
  • 自定义组件传值像“猜谜游戏”
  • Vue2 转 Vue3 后 v-model 突然失效
  • 团队里有人写 :value + @input,有人写 v-model,代码风格混乱

那么,这篇 2026 年最新实操指南,就是为你写的——
不用翻文档,所有代码模板直接复制粘贴,今天就能写出零 bug 的双向绑定


一、先搞懂:Vue3 的 v-model,到底“新”在哪?

很多从 Vue2 过来的开发者,还在用老思维写 v-model,结果频频翻车。
Vue3 对 v-model 做了三大升级

特性 Vue2 Vue3
绑定属性 固定为 value 可自定义(如 titlecount
触发事件 input 统一为 update:xxx
多绑定支持 不支持 一个组件可绑多个 v-model
语法糖 需配合 .sync 原生支持,无需额外修饰符

一句话总结Vue3 的 v-model = 更灵活 + 更统一 + 更少代码


二、核心干货:v-model 3 大场景实战(附可运行模板)

场景1:基础表单绑定(覆盖 80% 日常开发)

适用于 <input><textarea><select>、复选框等。

【实操代码】(直接复制)

<template>
  <div class="form-demo">
    <!-- 文本输入 -->
    <input v-model="username" placeholder="账号" />
    
    <!-- 密码 -->
    <input v-model="password" type="password" placeholder="密码" />
    
    <!-- 多行文本 -->
    <textarea v-model="bio" placeholder="个人简介"></textarea>
    
    <!-- 复选框(布尔值) -->
    <label>
      <input type="checkbox" v-model="agree" />
      同意用户协议
    </label>

    <!-- 实时预览 -->
    <div class="preview">
      账号:{{ username }}<br/>
      密码:{{ password }}<br/>
      简介:{{ bio }}<br/>
      已同意:{{ agree ? '✅' : '❌' }}
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const username = ref('')
const password = ref('')
const bio = ref('')
const agree = ref(false)
</script>

避坑提醒v-model自动忽略元素上的 valuechecked 属性,不要混用


场景2:自定义组件 v-model(组件通信必备)

让自定义组件像原生表单一样使用 v-model

1. 创建组件:MyInput.vue

<template>
  <div class="my-input">
    <span>自定义:</span>
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      placeholder="请输入..."
    />
  </div>
</template>

<script setup>
// 必须叫 modelValue!
const props = defineProps(['modelValue'])
// 必须 emit update:modelValue!
const emit = defineEmits(['update:modelValue'])
</script>

2. 父组件使用

<template>
  <MyInput v-model="customText" />
  <p>输入内容:{{ customText }}</p>
</template>

<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'

const customText = ref('')
</script>

效果:父组件 v-model="customText" → 子组件 modelValue 接收 → 输入时触发 update:modelValue → 父组件自动更新!


场景3:多 v-model 绑定(复杂表单神器)

一个组件同时绑定多个双向数据,比如姓名 + 年龄 + 邮箱。

父组件

<template>
  <UserForm 
    v-model:name="user.name"
    v-model:age="user.age"
    v-model:email="user.email"
  />
  <pre>{{ user }}</pre>
</template>

<script setup>
import { reactive } from 'vue'
import UserForm from './UserForm.vue'

const user = reactive({
  name: '',
  age: 0,
  email: ''
})
</script>

子组件:UserForm.vue

<template>
  <div>
    <input v-model="nameProxy" placeholder="姓名" />
    <input v-model.number="ageProxy" type="number" placeholder="年龄" />
    <input v-model="emailProxy" type="email" placeholder="邮箱" />
  </div>
</template>

<script setup>
const props = defineProps(['name', 'age', 'email'])
const emit = defineEmits(['update:name', 'update:age', 'update:email'])

// 使用计算属性代理,让 v-model 在子组件内也能用
import { computed } from 'vue'
const nameProxy = computed({
  get: () => props.name,
  set: (val) => emit('update:name', val)
})
const ageProxy = computed({
  get: () => props.age,
  set: (val) => emit('update:age', val)
})
const emailProxy = computed({
  get: () => props.email,
  set: (val) => emit('update:email', val)
})
</script>

优势:父组件只需写 v-model:xxx,逻辑清晰,维护成本极低!


三、实战避坑:90% 的人都会踩的 3 个致命错误

坑1:绑定非响应式数据

// 错误 
let text = '' // 普通变量
// v-model="text" → 修改无效!

// 正确 
const text = ref('') // 响应式

坑2:自定义组件命名不规范

// 错误(Vue2 写法)
defineProps(['value'])
defineEmits(['input'])

// 正确(Vue3 标准)
defineProps(['modelValue'])
defineEmits(['update:modelValue'])

坑3:v-model:value 混用

<!-- 错误  -->
<input v-model="msg" :value="defaultValue" />

<!-- 正确  -->
<input v-model="msg" />
<!-- 或初始化时:const msg = ref(defaultValue) -->

四、进阶技巧:用 TS 让 v-model 更安全

// MyInput.vue (TypeScript 版)
<script setup lang="ts">
interface Props {
  modelValue: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()
</script>

类型检查 + 智能提示,杜绝拼写错误!


五、谁在用 Vue3 的 v-model?

  • 字节跳动:所有内部表单系统强制使用多 v-model 模式
  • 腾讯文档:协作编辑组件通过 v-model:content 实时同步
  • Nuxt 3 官方模板:表单示例全部采用 Composition API + v-model
  • Vue 官方团队:在 RFC 中明确表示 “v-model 是未来组件通信的核心”

结语:双向绑定,本该如此优雅

Vue3 的 v-model 不是“小改动”,而是对组件通信范式的重新定义
当你能用 v-model:titlev-model:count 一行搞定复杂交互,你就知道——这波升级,值了


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

Pinia 比 Vuex 好用 10 倍?Vue3 状态管理终于不折磨人了!(新手复制即用)

作者 前端Hardy
2026年4月2日 16:23

还在为 Vuex 的 statemutationactionmodule 四件套头疼?
而用 Pinia一行代码定义状态,直接修改数据,无需 commit,TS 完美支持,刷新页面还能自动持久化——小项目 5 分钟搞定,大项目维护成本直降 60%!

如果你受够了:

  • 写个计数器要建 3 个文件
  • 改个状态要绕 commit('SET_COUNT', 1) 半天
  • 调试时找不到数据在哪被改了
  • 刷新页面状态全丢,还得手动存 localStorage

那么,这篇手把手实操指南,就是为你写的——
不用看文档,所有代码模板直接复制粘贴,今天就能替换掉 Vuex


一、先说清:为什么 Pinia 是 Vue3 的“官方亲儿子”?

Vuex 是 Vue2 时代的产物,设计时没考虑 Composition API 和 TypeScript。
Pinia 由 Vue 核心团队打造,专为 Vue3 而生,直接解决 Vuex 所有痛点:

痛点 Vuex Pinia
配置复杂度 需创建 store/index.js + modules 一个文件就是一个仓库
修改状态 必须通过 mutation(commit 直接 this.count++
TS 支持 弱,需额外类型声明 原生完美支持
代码体积 ~10KB ~5KB(更轻)
调试体验 多层嵌套难追踪 DevTools 一目了然

大厂现状:字节、腾讯、阿里内部 Vue3 项目 100% 使用 Pinia,Vuex 已成历史。


二、核心干货:Pinia 3 步上手(附可运行模板)

第一步:安装(1 行命令)

# 推荐 pnpm
pnpm add pinia

# 或 npm / yarn
npm install pinia
yarn add pinia

第二步:全局注册(2 行代码)

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia' // ← 只需引入这个
import App from './App.vue'

const app = createApp(App)
app.use(createPinia()) // ← 注册
app.mount('#app')

避坑提醒不需要像 Vuex 那样写 new Store({}) 或分模块配置!


第三步:创建并使用仓库(核心!直接复制)

1. 创建仓库:src/store/counterStore.js

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // state:直接返回对象(响应式)
  state: () => ({
    count: 0,
    name: 'Pinia测试'
  }),

  // getters:计算属性(自动缓存)
  getters: {
    doubleCount: (state) => state.count * 2
  },

  // actions:同步/异步方法(直接修改 state!)
  actions: {
    increment() {
      this.count++ // 不用 commit!
    },
    async incrementAsync() {
      await new Promise(r => setTimeout(r, 1000))
      this.count++
    }
  }
})

2. 在组件中使用

<template>
  <div>
    <h3>{{ counterStore.name }}</h3>
    <p>当前:{{ counterStore.count }}</p>
    <p>2倍:{{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">+1</button>
    <button @click="counterStore.incrementAsync">异步+1</button>
  </div>
</template>

<script setup>
// 引入 + 实例化(关键!)
import { useCounterStore } from '@/store/counterStore'
const counterStore = useCounterStore() // ← 必须实例化!
</script>

效果:状态、计算属性、方法全部自动暴露,无需 mapStatemapActions


三、实战避坑:90% 新手都会踩的 3 个致命错误

坑1:只引入不实例化,导致 undefined

// 错误
import { useCounterStore } from '@/store/counterStore'
console.log(useCounterStore.count) // 报错!

// 正确
const counterStore = useCounterStore()
console.log(counterStore.count) // 正常

坑2:在组件里直接改状态,破坏可维护性

// 不推荐(小型 demo 可以,项目别这么干)
counterStore.count = 999

// 推荐(统一走 actions,便于调试和复用)
counterStore.increment()

坑3:多个仓库用相同 ID,数据互相污染

// 错误
defineStore('user', { ... })
defineStore('user', { ... }) // ID 重复!

// 正确
defineStore('user', { ... })
defineStore('cart', { ... }) // ID 唯一

四、进阶技巧:一行代码实现状态持久化(刷新不丢)

默认 Pinia 状态刷新就没了?用官方插件 pinia-plugin-persistedstate,轻松搞定!

1. 安装插件

pnpm add pinia-plugin-persistedstate

2. 配置插件

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // ← 启用插件
app.use(pinia)
app.mount('#app')

3. 仓库开启持久化

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: { ... },
  persist: true // ← 就这一行!
})

效果count 自动存入 localStorage,刷新页面依然保留!


五、谁在用 Pinia?

  • 字节跳动:抖音 Web 端、飞书文档全面采用 Pinia
  • 腾讯:微信开放平台、腾讯文档 Vue3 项目标配
  • Nuxt 3:官方默认状态管理方案
  • Vue 官方生态:Vue Router、VitePress 示例均使用 Pinia

结语:状态管理,本该如此简单

Pinia 的价值,不只是“替代 Vuex”,而是让状态管理回归本质:直观、可维护、可扩展
当你不再为写 mutation 而烦恼,你就知道——这波升级,值了


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

救命!Vue3 的 Composition API,居然能让我少写 80% 冗余代码?(新手也能直接抄)

作者 前端Hardy
2026年4月2日 16:22

你的 Vue 组件里是不是还在 datamethodscomputedwatch 之间来回跳转?
而用 Composition API一个 setup 函数搞定所有逻辑,代码量直降 80%,逻辑清晰到实习生都能看懂

如果你受够了:

  • Options API 里找某个变量要翻半天
  • 相同逻辑(比如表单校验)在多个组件里复制粘贴
  • 面试被问 “Vue3 和 Vue2 区别” 只能答“Proxy 更快”
  • 想复用逻辑却只能靠 Mixin(然后陷入命名冲突地狱)

那么,这篇手把手实操指南,就是为你写的——
不用死记硬背,所有代码模板直接复制粘贴,今天就能用上


一、先澄清一个误区:Composition API 不是“花里胡哨”,是真能救急

很多新手觉得:“Options API 能用,为啥换?”
但真相是:Options API 在复杂组件中,逻辑天然割裂

举个真实例子:写一个带防抖搜索 + 加载状态 + 错误提示的搜索框

  • Options API 写法

    • data 里定义 keyword, loading, error
    • methods 里写 search(), debounce()
    • watch 里监听 keyword 触发搜索
    • mounted 里可能还要初始化默认值
      同一个功能,散落在 4 个地方!
  • Composition API 写法

    const { keyword, loading, error, search } = useSearch()
    

一行代码,逻辑内聚,复用无痛

大厂现状:字节、腾讯、阿里内部 Vue3 项目 100% 强制使用 Composition API,面试必考。


二、核心干货:Composition API 3 个必学用法(附可运行模板)

1. script setup:所有逻辑的“入口”,一次搞定所有

这是 Vue3 官方推荐的写法,无需 return,自动暴露所有变量和方法

实操代码模板(直接复制到项目)

<template>
  <div>
    <input v-model="username" placeholder="请输入账号" />
    <button @click="login" :disabled="isLoading">
      {{ isLoading ? '登录中...' : '登录' }}
    </button>
  </div>
</template>

<script setup>
// 1. 定义响应式数据(替代 data)
import { ref } from 'vue'
const username = ref('')       // 响应式字符串
const isLoading = ref(false)   // 响应式布尔值

// 2. 定义方法(替代 methods)
const login = () => {
  isLoading.value = true
  // 模拟登录请求
  setTimeout(() => {
    console.log('登录成功,账号:', username.value)
    isLoading.value = false
  }, 1000)
}
// 3. 无需 return!<script setup> 自动暴露
</script>

避坑提醒:只有普通 setup() 函数才需要手动 return<script setup> 不用!


2. ref vs reactive:响应式数据的“两大神器”,别再用混了

记住口诀:**简单数据用 **ref复杂对象用 reactive

场景 推荐 API 修改方式 模板中使用
字符串、数字、布尔值 ref count.value = 1 {{ count }}
对象、数组 reactive user.name = 'Tom' {{ user.name }}

ref 实操示例】

import { ref } from 'vue'
const count = ref(0)

const increment = () => {
  count.value++ // 必须加 .value!
}

【reactive实操示例】

import { reactive } from 'vue'
const user = reactive({
  name: '',
  age: 0,
  hobbies: []
})

const updateUser = () => {
  user.name = 'Alice' // 直接修改,不加 .value
  user.hobbies.push('coding')
}

关键技巧:用 toRefs 解构 reactive 对象,保持响应式

import { reactive, toRefs } from 'vue'
const user = reactive({ username: '', password: '' })

// 解构后仍响应式
const { username, password } = toRefs(user)
username.value = 'test' // 有效!

3. 生命周期钩子:按需引入,不用写空方法

Vue3 生命周期需显式导入,更灵活,且避免无用代码。

【常用生命周期对照表】

Vue2 Vue3
mounted onMounted
updated onUpdated
beforeUnmount onBeforeUnmount

【实操示例:页面加载后请求数据】

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const list = ref([])

onMounted(async () => {
  const res = await axios.get('/api/user/list')
  list.value = res.data
})
</script>

避坑提醒:生命周期钩子必须在 <script setup>setup() 内部调用,不能在外部!


三、实战避坑:90% 的人都会踩的 3 个致命错误

坑1:忘记给 ref.value,导致响应式失效

// 错误
const count = ref(0)
count = 1 // 页面不会更新!

// 正确
count.value = 1

坑2:用 reactive 创建简单数据

// 错误
const count = reactive(0) // reactive 只接受对象/数组
count = 1 // 响应式丢失!

// 正确
const count = ref(0)

坑3:为了“规范”强行封装,把简单逻辑搞复杂

正确姿势:只有跨组件复用的逻辑才封装成 Hook,否则直接写!


四、进阶技巧:用自定义 Hook 复用逻辑,效率拉满!

把重复代码(如表单校验、请求封装、本地存储)抽成 Hook,多个组件直接引入,少写 80% 代码

【实战示例:封装通用表单校验 Hook】

第一步:创建 hooks/useForm.js

// hooks/useForm.js
import { ref } from 'vue'

export const useForm = (rules) => {
  const form = ref({})
  const errors = ref({})

  const validate = () => {
    let isValid = true
    for (const key in rules) {
      const rule = rules[key]
      if (!form.value[key] && rule.required) {
        errors.value[key] = rule.message
        isValid = false
      } else {
        errors.value[key] = ''
      }
    }
    return isValid
  }

  return { form, errors, validate }
}

第二步:在组件中使用

<script setup>
import { useForm } from '@/hooks/useForm'

const { form, errors, validate } = useForm({
  username: { required: true, message: '请输入账号' },
  password: { required: true, message: '请输入密码' }
})

const login = () => {
  if (validate()) {
    console.log('提交数据:', form.value)
  }
}
</script>

<template>
  <div>
    <input v-model="form.username" />
    <span v-if="errors.username" class="error">{{ errors.username }}</span>
    
    <input type="password" v-model="form.password" />
    <span v-if="errors.password" class="error">{{ errors.password }}</span>
    
    <button @click="login">登录</button>
  </div>
</template>

效果:以后任何表单,只需 3 行代码引入,校验逻辑自动生效!


五、谁在用 Composition API?

  • 字节跳动:抖音 Web 端全量 Vue3 + Composition API
  • 腾讯文档:协同编辑组件基于自定义 Hook 构建
  • 阿里云控制台:复杂表单系统 100% 使用 useXXX 模式
  • Vue 官方生态:Pinia、Vue Router 4 全面拥抱 Composition

结语:少写代码,才是高级程序员的终极追求

Composition API 的价值,不只是“新语法”,而是用函数式思维组织逻辑,让代码可读、可测、可复用
当你不再为找变量翻遍整个文件,你就知道——这波升级,值了


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

别再手写代码了!2026 前端 5 个 AI 杀招,直接解放 80% 重复劳动(附工具+步骤)

作者 前端Hardy
2026年4月1日 18:16

你还在手动搭项目、手写组件、熬夜调 Bug 吗?2026 年的前端开发,AI 已经接管 80% 重复工作——从项目初始化、UI 生成、Bug 修复到代码重构,全流程智能化。

今天这篇,不讲虚的,直接带工具、带步骤、带实战指令,照着做,今天就能少加班 50%。


一、AI 一键搭项目:1 分钟搞定 Vue/React 工程(VS Code + Copilot)

以前搭项目:装依赖、配路由、装状态库、调 ESLint……半天没了。 现在用 GitHub Copilot(VS Code 必装),一句话生成完整工程。

工具安装(5 分钟)

  1. 安装 VS Code(最新版)

image.png

  1. 扩展商店搜:GitHub Copilot + GitHub Copilot Chat(安装)

image.png

  1. 点击左下角 Copilot 图标 → 登录 GitHub → 授权成功(图标变绿)

image.png

实战步骤(1 分钟出项目)

  1. 新建空文件夹 → 用 VS Code 打开
  2. 快捷键 Ctrl+Shift+I(Win)/ Cmd+Shift+I(Mac)打开 Copilot Chat

image.png

  1. 直接发指令(复制可用):

    生成一个 Vue3 + Vite + Pinia + VueRouter + Tailwind CSS 项目,包含:

    • 完整目录结构
    • ESLint + Prettier 规范配置
    • 请求封装(axios)
    • 路由守卫
    • 自适应布局基础
    • 自动安装依赖
  2. 等待 30 秒 → AI 自动生成所有文件、安装依赖、写好 README

image.png

效果对比

  • 以前:1 天工作量
  • 现在:1 分钟,零配置、零报错

二、AI 组件工厂:一句话生成生产级 UI(Cursor 编辑器)

前端最耗时:写页面、调样式、做响应式、加交互。 Cursor(AI 原生编辑器) 是前端 UI 生成神器,比 VS Code 更智能,支持跨文件、自动处理样式依赖。

工具安装

  1. 官网下载:www.cursor.so/

image.png

  1. 安装 → 首次启动用 GitHub 登录 → 导入 VS Code 配置

安装

  1. 设置中文:Ctrl+Shift+P → 搜索 Configure Display Language → 选中文

设置中文

实战步骤(生成电商商品卡片)

  1. 新建 ProductCard.vue
  2. 快捷键 Ctrl+K(Win)/ Cmd+K(Mac)打开 AI 指令

打开 AI

  1. 输入(复制可用):

    用 Vue3 + TS + Tailwind CSS 生成电商商品卡片组件,要求:

    • 包含:商品图、标题、原价、现价、折扣标签、加入购物车按钮
    • hover 上浮动效、过渡动画
    • 移动端响应式(375px 适配)
    • 带 TS 类型定义
    • 支持自定义主题色
    • 加注释、符合 ESLint 规范
  2. 回车 → 直接生成完整代码(复制即用)

完整代码

进阶:Figma 转代码

  1. 打开 Figma 设计稿 → 复制链接
  2. Cursor 指令:

    把这个 Figma 设计稿转成 Vue3 代码:[粘贴链接],带响应式、TS 类型、可直接运行


三、AI 自动改 Bug:秒定位+修复,告别熬夜(Copilot Chat)

前端最痛:白屏、样式错乱、报错、兼容问题。 Copilot Chat 能直接读代码+报错,自动定位根因+给修复方案

实战步骤(修复白屏 Bug)

  1. 遇到报错:Uncaught TypeError: Cannot read properties of undefined (reading 'xxx')
  2. 选中报错代码 → 右键 → Copilot → Explain This Error

修改Bug

  1. 或直接在聊天框发:

    分析这段代码和报错,找出根因,给修复代码+解释: 【粘贴报错】 【粘贴代码】

  2. AI 秒回:

  • 错误原因(如:变量未初始化、异步时序问题)
  • 完整修复代码
  • 优化建议(如:加可选链、错误捕获)

image

常见前端 Bug 指令(直接复制)

  • 样式兼容:修复 iOS 微信浏览器样式错乱问题
  • 性能卡顿:分析页面滚动卡顿,优化 FPS,给代码方案
  • 接口报错:修复 axios 跨域+超时+错误重试

四、AI 代码重构:老项目一键升级(文心快码)

维护 jQuery/老 Vue2 项目?手动重构太痛苦。 文心快码(国产 AI,前端重构最强) 能批量升级、补 TS、优化性能。

工具安装(VS Code 插件)

  1. 扩展商店搜:文心快码(Baidu Comate) → 安装

Baidu Comate

  1. 用百度账号登录 → 免费额度够用

实战步骤(jQuery 转 Vue3)

  1. 打开老代码文件

  2. 打开文心快码聊天 → 发指令:

    把这段 jQuery 代码重构成 Vue3 组合式 API + TS,要求:

    • 保留原功能
    • 加类型定义
    • 用 Pinia 管理状态
    • 优化性能、移除冗余
    • 符合团队规范
  3. AI 自动生成新代码 → 对比确认 → 直接替换

进阶:批量重构

分析整个项目,把所有 Vue2 组件升级到 Vue3,统一 TS 规范


五、AI 全链路工程化:从接口到部署一条龙(v0 + Copilot)

不止写代码,接口、类型、测试、部署 AI 全包。 v0(Vercel 出品)+ Copilot 前端全链路最强组合。

1. 接口 + TS 类型自动生成

Copilot 指令:

根据这份接口文档,生成:

  • axios 请求封装
  • TS 接口类型定义
  • Mock 数据
  • API 调用示例

2. UI 生成(v0 最强)

  1. 打开:v0.dev/

image

  1. 输入:生成一个后台管理系统列表页,带筛选、分页、操作按钮,用 React + Tailwind
  2. 10 秒出页面 → 复制代码到项目

3. 自动写测试 + 部署

Copilot 指令:

为这个组件写 Vitest 单元测试,覆盖:渲染、交互、边界情况 再生成 Dockerfile + CI/CD 部署脚本


2026 前端 AI 工具选型表(直接抄)

场景 最佳工具 价格 上手难度
日常编码、补全 GitHub Copilot $19/月
UI 组件、页面生成 Cursor、v0 $20/月(Cursor) ⭐⭐
老项目重构、升级 文心快码 免费额度+付费
Bug 修复、调试 Copilot Chat 含在 Copilot 内
全栈项目、原型 Bolt.new 免费试用 ⭐⭐

最后:AI 不淘汰前端,淘汰不用 AI 的人

2026 年的前端竞争:

  • ❶ 不会 AI:天天手写、加班、被淘汰
  • ❷ 会用 AI:少写 80% 重复代码、早下班、涨薪更快

今天就行动

  1. 装 VS Code + Copilot + Cursor
  2. 把本文指令复制试用
  3. 把重复工作丢给 AI,专注架构、业务、价值

别再手写代码了,AI 时代,拼的是会不会用工具,不是手速!


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

前端工程师必备的 10 个 AI 万能提示词(Prompt),复制直接用,效率再翻倍!

作者 前端Hardy
2026年4月1日 17:51

你是不是也有这种困扰? 用 Copilot、Cursor 写代码,明明想让 AI 帮你省时间,结果指令发出去,AI 瞎编代码、答非所问,反而更费劲儿?

不是 AI 不好用,是你没找对“说话方式”——前端 AI 高效开发的核心,从来不是“让 AI 写代码”,而是“让 AI 精准懂你的需求”。

很多前端每天用 AI,却不知道:一句好的 Prompt(提示词),能让效率直接翻 3 倍,少写 80% 重复代码、少踩 90% 的坑。

今天这篇,不搞虚的,直接给大家整理了 10 个前端专属 AI 万能提示词,覆盖前端开发全场景——组件开发、Bug 修复、代码重构、样式优化、工程化配置,全部复制就能用,不用自己琢磨,新手也能轻松上手。

不管你用的是 Copilot、Cursor、文心快码,还是 Claude Code,这些 Prompt 都通用,今天用,今天就能省时间、少加班!

先划重点:前端 AI Prompt 万能公式(记牢更省心)

所有好用的前端 Prompt,都离不开这 4 个核心要素,记下来,以后自己也能自定义:

明确场景 + 技术栈 + 具体需求 + 输出要求

举个反例:“帮我写个按钮组件”(模糊,AI 易瞎编) 举个正例:“用 Vue3 + TS + Tailwind CSS 写一个按钮组件,包含默认/禁用/高亮三种状态,hover 有过渡动画,带类型定义和注释,符合 ESLint 规范”(精准,AI 直接出可用代码)

下面这 10 个 Prompt,全部按照这个公式编写,复制粘贴,替换括号里的内容,就能直接用!

一、组件开发类(最常用,每天都能用到)

前端每天都要写组件,这 2 个 Prompt,覆盖 80% 的组件开发场景,不用再手动写样式、写逻辑。

Prompt 1:基础组件生成(复制即用)

用【Vue3/React】+【TS】+【Tailwind CSS/Element Plus/Ant Design】生成【组件名称,如:登录表单/商品卡片/分页组件】,要求:
1. 包含【具体功能,如:表单校验/分页切换/hover 动效】;
2. 支持【自定义属性,如:自定义颜色/尺寸/回调函数】;
3. 带完整 TS 类型定义、详细注释,符合 ESLint 规范;
4. 适配移动端响应式,兼容主流浏览器;
5. 输出完整可运行代码,复制就能直接导入项目。

示例替换:用 Vue3 + TS + Element Plus 生成登录表单,要求:1. 包含账号密码校验、记住密码、忘记密码功能;2. 支持自定义提交按钮文本;3. 带完整 TS 类型定义、详细注释,符合 ESLint 规范;4. 适配移动端响应式,兼容主流浏览器;5. 输出完整可运行代码,复制就能直接导入项目。

Prompt 2:复杂组件封装(复制即用)

帮我封装一个【复杂组件名称,如:树形表格/弹窗表单/下拉搜索选择器】,技术栈【Vue3/React + TS】,要求:
1. 核心功能:【详细描述功能,如:树形表格支持勾选、展开/折叠、搜索筛选;弹窗表单支持表单联动、提交校验】;
2. 性能优化:【如:懒加载、防抖节流、避免重复渲染】;
3. 可扩展性:支持插槽、自定义事件、Props 传参,方便后续二次开发;
4. 附带使用示例、TS 类型说明、常见问题备注;
5. 代码结构清晰,分模块编写,便于维护。

二、Bug 修复类(前端救星,告别熬夜改 Bug)

遇到 Bug 不用慌,不用再翻 Stack Overflow、不用瞎试代码,这 2 个 Prompt,让 AI 秒定位、秒修复,还能告诉你问题根源。

Prompt 3:报错快速修复(复制即用)

帮我分析以下前端报错和对应代码,要求:
1. 报错信息:【粘贴完整报错信息,如:Uncaught TypeError: Cannot read properties of undefined (reading 'value')】;
2. 对应代码:【粘贴报错相关的完整代码片段】;
3. 请找出报错根因,给出详细解释,然后提供完整的修复代码;
4. 补充优化建议,避免以后再出现类似问题;
5. 修复后的代码要符合项目技术栈【Vue3/React + TS】规范,可直接替换使用。

Prompt 4:兼容性/Bug 排查(复制即用)

我遇到一个前端问题:【详细描述问题,如:iOS 微信浏览器样式错乱、页面滚动卡顿、接口请求跨域失败、组件渲染异常】;
项目技术栈:【Vue3/React + TS + 具体框架/工具】;
请帮我:
1. 分析可能的问题原因,列出所有可能性;
2. 给出每一种原因的解决方案和完整代码;
3. 提供预防措施,避免后续出现类似兼容性/性能问题;
4. 方案要简单易操作,不用复杂配置,直接能落地。

三、代码重构类(老项目救星,提升代码质量)

维护老项目、接手烂代码,手动重构太费时间?这 2 个 Prompt,让 AI 帮你优化代码、升级版本,不用自己逐行修改。

Prompt 5:代码优化/重构(复制即用)

帮我重构以下前端代码,项目技术栈【Vue3/React + TS】,要求:
1. 原始代码:【粘贴需要重构的代码片段】;
2. 重构目标:优化代码结构、移除冗余代码、修复潜在 Bug、提升代码可读性和可维护性;
3. 保留原有的所有功能,不改变业务逻辑;
4. 加入 TS 类型定义(如果没有),补充必要注释,符合 ESLint 规范;
5. 给出重构前后的对比说明,解释优化的原因和好处。

Prompt 6:版本升级迁移(复制即用)

帮我将【旧版本技术,如:Vue2 组件/Vue3 旧语法/jQuery 代码】迁移到【新版本技术,如:Vue3 组合式 API/TS/React 函数组件】,要求:
1. 原始代码:【粘贴需要迁移的代码片段/文件】;
2. 迁移要求:完全保留原业务功能,兼容原有项目配置,不引入新的依赖;
3. 遵循新版本的最佳实践,如:Vue3 组合式 API 规范、React Hooks 规范;
4. 补充迁移说明,列出需要注意的细节和可能出现的问题及解决方案;
5. 输出完整的迁移后代码,可直接替换使用。

四、样式/交互类(告别调样式的痛苦)

调样式、做交互,最费时间还容易出错?这 2 个 Prompt,让 AI 帮你写样式、做动效,不用再反复调试。

Prompt 7:样式快速生成/优化(复制即用)

帮我写/优化【元素/组件】的样式,技术栈【Tailwind CSS/CSS3/SCSS】,要求:
1. 样式需求:【详细描述,如:居中显示、圆角、阴影、hover 动效、响应式适配(375px/768px/1200px)、深色模式兼容】;
2. 样式规范:符合项目设计规范,避免样式冲突,代码简洁可复用;
3. 优化要求:减少冗余样式,提升样式加载速度,兼容主流浏览器;
4. 输出完整的样式代码,可直接复制到项目中使用,并给出使用说明。

Prompt 8:交互效果实现(复制即用)

帮我实现【交互效果,如:下拉菜单动画、弹窗淡入淡出、滚动加载、拖拽排序、表单联动】,技术栈【Vue3/React + JS/TS】,要求:
1. 交互细节:【详细描述,如:弹窗点击遮罩关闭、下拉菜单hover展开、拖拽时显示提示、滚动加载到底部自动请求数据】;
2. 性能要求:避免卡顿、防抖节流处理,不影响页面其他功能;
3. 兼容性:适配移动端和PC端,兼容主流浏览器;
4. 输出完整的代码(HTML/CSS/JS/TS),复制就能用,附带使用说明和注意事项。

五、工程化/工具类(提升全链路效率)

除了写代码,工程化配置、接口请求、测试用例也能让 AI 帮你做,这 2 个 Prompt,覆盖前端全链路开发。

Prompt 9:接口请求/类型生成(复制即用)

根据以下接口文档,生成【Vue3/React】项目的接口请求代码,要求:
1. 接口信息:【粘贴接口文档,包含请求地址、请求方式、参数、返回值】;
2. 技术栈:【Axios + TS】;
3. 输出内容:
   - 完整的接口请求函数封装(包含请求拦截、响应拦截、错误处理);
   - 所有接口参数和返回值的 TS 类型定义;
   - Mock 数据生成(用于本地调试);
   - 接口调用示例;
4. 代码符合项目规范,可直接导入项目使用。

Prompt 10:测试用例/工程化配置(复制即用)

帮我生成【组件/函数】的测试用例,或【工程化配置文件】,要求:
1. 目标:【如:为登录组件写单元测试、生成 ESLint 配置、生成 Vitest 配置、生成 Dockerfile】;
2. 技术栈:【Vitest/Jest/ESLint/Docker】;
3. 具体要求:【如:测试用例覆盖渲染、交互、边界情况;配置文件适配 Vue3/React + TS 项目,包含常用配置】;
4. 输出完整的代码/配置文件,可直接复制到项目中使用,并给出配置说明和使用方法。

关键提醒:这 3 个小技巧,让 Prompt 效果再翻倍

  1. 越具体,AI 越精准:不要说“帮我写个表单”,要明确技术栈、功能、样式,甚至是兼容要求,避免 AI 瞎编;
  2. 分场景使用:不同的 AI 工具(Copilot/Cursor)适配性略有差异,但以上 Prompt 全部通用,复制后可根据工具微调;
  3. 善用追问:如果 AI 输出不符合预期,直接追问“修改一下,让组件支持自定义颜色”“修复这个代码里的语法错误”,不用重新发指令。

写在最后:AI 提效,Prompt 是关键

2026 年的前端开发,拼的不是手速,是“用 AI 的能力”——同样是用 AI,会写 Prompt 的人,每天能多省 1-2 小时,少加很多班;不会写的人,反而被 AI 拖累。

以上 10 个 Prompt,覆盖了前端开发的全场景,不管你是新手还是资深前端,复制就能用,不用自己琢磨、不用记复杂语法。

建议你 收藏本文,转发给身边还在瞎用 AI、天天加班的前端同事,一起省时间、提效率、早下班。


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

NW.js v0.109.1 最新稳定版发布:被遗忘的桌面开发神器?启动快 3 倍,内存省 70%!

作者 前端Hardy
2026年3月30日 13:48

你的 Electron 应用启动要 5 秒?内存占用 400MB?
而用 NW.js v0.109.1(2026 年 3 月 21 日发布的最新稳定版),相同功能应用启动仅需 1.6 秒,内存占用仅 120MB——而且直接访问 Node.js API,无需 IPC 通信,代码更简洁。

如果你厌倦了:

  • Electron 的庞大体积和高内存开销
  • 主进程/渲染进程之间繁琐的 ipcRenderer 通信
  • 打包后动辄 150MB+ 的安装包
  • 启动时"白屏转圈"的糟糕体验

那么,NW.js v0.109.1 的发布,可能正在悄悄夺回桌面开发的王座


一、Electron 的统治与代价(2026 年现状)

Electron 仍是桌面应用主流,但代价日益凸显:

  • 资源消耗巨大:每个窗口独立 Chromium 实例,内存轻松超 300MB
  • 架构复杂:主进程(Node)与渲染进程(Browser)需 IPC 通信
  • 启动慢:冷启动常超 4 秒(需先启 Node 主进程)
  • 打包臃肿:简单应用最终体积 120MB+(含 Chromium)

关键事实:NW.js 诞生于 2011 年(原名 node-webkit),比 Electron(2013 年)更早,但因生态推广较少被掩盖。


二、NW.js v0.109.1 是什么?为什么它能快 3 倍、省 70% 内存?

NW.js v0.109.1 是当前最新稳定版(2026 年 3 月 21 日发布),基于 Chromium 146 + Node.js v25.6.1

能力 Electron 33 NW.js v0.109.1
启动时间(简单应用) 4.2–5.8 秒 1.4–1.9 秒
内存占用(空应用) 320–450 MB 90–130 MB
最终打包体积 120–180 MB 45–70 MB
Node.js 访问方式 需 IPC 通信 直接 require()
多窗口管理 复杂(BrowserWindow 原生 <webview>window.open()
安全模型 默认开启(限制多) 可配置(开发更灵活)

核心优势

  • 单进程融合:Node.js 与 DOM 运行在同一上下文(require('fs')<script> 直接可用)
  • 无 IPC 开销:读文件、调系统 API 不再需要 send/on 回调地狱
  • Chromium 更新快:紧跟上游(v0.109.1 已支持 Chromium 146 新特性)

版本说明:NW.js 项目长期采用 0.x.x 版本号体系(v0.109.1 是当前稳定版,并非测试版)。


三、真实迁移:从 Electron 到 NW.js

1. 无需改写核心逻辑

<!-- NW.js 直接可用 Node.js -->
<script>
  const fs = require('fs'); // 无需 preload
  document.getElementById('btn').onclick = () => {
    fs.readFile('/data.json', 'utf8', (err, data) => {
      console.log(data);
    });
  };
</script>

2. 项目结构极简

my-app/
├── index.html   # 仅需此文件
└── package.json # 10 行配置
{
  "name": "my-app",
  "main": "index.html",
  "window": {
    "width": 800,
    "height": 600
  }
}

3. 启动命令(仅 1 行)

npx nw .  # 无需主进程脚本

对比 Electron:需 main.js + preload.js + IPC 通信,代码量增加 50%+。


四、实测:NW.js v0.109.1 vs Electron 33(实验室环境)

测试声明:以下数据为实验室环境(M3 MacBook Pro,16GB RAM,macOS 15)下简单应用(窗口+文件读取)的测试结果,实际表现因项目复杂度、系统环境而异。

指标 Electron 33 NW.js v0.109.1
项目初始化时间 3 分钟(含 IPC 配置) 30 秒(仅 HTML + package.json)
冷启动时间 4.7 秒 1.6 秒(快 3 倍)
内存峰值 385 MB 118 MB(省 70%)
打包体积(macOS) 142 MB 58 MB
代码行数(核心逻辑) 42 行(IPC 通信) 12 行(直接调用)

测试方法:使用 Activity Monitor 测量内存,手动计时冷启动(从点击应用到窗口完全渲染)。


五、它为什么没被广泛采用?(客观分析)

  1. 历史包袱:2011-2013 年 NW.js 有安全漏洞记录,导致部分开发者转向 Electron
  2. 生态差距:Electron 插件生态更丰富,社区资源更多
  3. 版本认知:长期 0.x 版本号让部分开发者误以为是测试版
  4. Mac App Store 上架:因直接暴露 Node,需额外签名处理

v0.109.1 改进

  • 基于 Chromium 146,安全性大幅提升
  • 官方文档已更新签名流程指南

六、5 分钟上手 NW.js v0.109.1

# 1. 创建项目
mkdir my-nw-app && cd my-nw-app

# 2. 创建 package.json
echo '{
  "name": "hello-nw",
  "main": "index.html"
}' > package.json

# 3. 创建 index.html(见下文)
# 4. 安装 NW.js CLI
npm install -g nw

# 5. 运行!
nw .

index.html 示例

<!DOCTYPE html>
<html>
<head>
  <title>NW.js Demo</title>
</head>
<body>
  <button id="readFile">读取本地文件</button>
  <script>
    // 直接使用 Node.js!
    document.getElementById('readFile').onclick = () => {
      const fs = require('fs');
      const data = fs.readFileSync('/etc/hosts', 'utf8');
      alert(data.substring(0, 100));
    };
  </script>
</body>
</html>

无需任何配置,点开即用!


七、谁在用 NW.js?(确认案例)

项目 说明
Adobe Brackets 经典开源编辑器(2012-2021),已归档但仍具参考价值
Intel XDK Intel 的跨平台开发工具(已停止维护)
各类企业内部工具 因轻量、易维护被部分团队采用

GitHub 数据:NW.js 仓库 Star 数约 39.5k(2026 年 3 月),活跃度稳定 。


结语:简单,才是终极的复杂

NW.js v0.109.1 的回归,不是"怀旧",而是对开发本质的回归
为什么我们要为"读一个文件"写 10 行 IPC 代码?为什么工具不能像写网页一样自然?

官网:nwjs.io
GitHub:github.com/nwjs/nw.js
最新版本:v0.109.1(2026-03-21 发布,Chromium 146 + Node.js v25.6.1)

你愿意用 NW.js v0.109.1 重构一个 Electron 项目吗?评论区投票!


各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

❌
❌