普通视图

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

前端 JavaScript 核心知识点 + 高频踩坑 + 大厂面试题全汇总(开发 / 面试必备)

作者 WayneYang
2026年4月16日 10:43

本文汇总了前端开发中99% 会遇到的 JS 核心知识点、高频踩坑、大厂面试题,每一个知识点都搭配代码示例,踩坑点附落地解决方案,面试题附详细解析,适合前端新手查漏补缺、老手复习巩固,可直接用于开发实战和面试准备~


一、JavaScript 核心基础知识点(必掌握)

1.1 数据类型(原始类型 + 引用类型)

JS 数据类型分为原始值类型引用数据类型,是前端开发的基石。

  • 原始类型(7 种):UndefinedNullBooleanNumberStringSymbolBigInt(ES11新增)
  • 引用类型:Object(包含ArrayFunctionDateRegExp等)

核心区别

  1. 原始类型存栈内存,值不可变;引用类型存堆内存,栈中存储堆地址
  2. 原始类型赋值是值拷贝,引用类型赋值是地址拷贝
  3. 原始类型比较是值比较,引用类型比较是地址比较

代码示例

// 原始类型:值拷贝,互不影响
let a = 10;
let b = a;
b = 20;
console.log(a); // 10

// 引用类型:地址拷贝,修改会相互影响
let obj1 = { name: "掘金" };
let obj2 = obj1;
obj2.name = "前端开发";
console.log(obj1.name); // 前端开发

// 精准类型判断
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call([]); // [object Array]

为什么要加 BigInt?

Number 局限:只能精确表示 ±2⁵³−1 范围内的整数(约 9e15)。

精度丢失问题

9007199254740992 === 9007199254740993; // true(错误)

// 1. 字面量(加 n)
const a = 123n;
const b = -456n;

// 2. 构造函数
const c = BigInt(789);
const d = BigInt("9007199254740992");

// 3. 类型判断
typeof a; // "bigint"

//与 Number 不兼容 不支持小数、Math 方法、JSON.stringify
123n + 123; // TypeError(不能混合运算)
123n === 123; // false

BigInt 解决:支持任意精度整数,适合金融、区块链、大 ID、密码学。

1.2 变量声明:var /let/const

前端最基础的声明规则,也是面试必考、开发必用知识点。

特性 var let const
变量提升 ✅ 存在 ❌ 暂时性死区 ❌ 暂时性死区
块级作用域 ❌ 无 ✅ 有 ✅ 有
重复声明 ✅ 允许 ❌ 不允许 ❌ 不允许
重新赋值 ✅ 允许 ✅ 允许 ❌ 不允许

代码示例

// var:变量提升 + 全局污染
console.log(num); // undefined
if (true) var num = 10;
console.log(num); // 10

// let:块级作用域隔离
let age = 20;
if (true) {
  let age = 30;
}
console.log(age); // 20

// const:必须初始化,引用类型可改属性
const PI = 3.14;
const user = { name: "张三" };
user.name = "李四"; // 合法

1.3 类型转换(显式 + 隐式)

JS 是弱类型语言,类型转换是开发高频操作。

  • 显式转换:Number()String()Boolean()parseInt()
  • 隐式转换:+-==if判断等自动触发

代码示例

// 显式转换
Number("123"); // 123
String(true); // "true"
Boolean(0); // false

// 隐式转换
1 + "2"; // "12"(数字转字符串)
"12" - 0; // 12(字符串转数字)
if (1) {} // 1转true

1.4 运算符核心(== / === / 短路运算 / 空值合并)

// ==:隐式转换后比较;===:严格比较(类型+值)
0 == ""; // true
0 === ""; // false

// 短路运算:&&(一假则假)、||(一真则真)
const name = null || "默认名称";
const age = 18 && "成年";

// 空值合并??:仅null/undefined时取默认值(开发推荐)
const obj = { age: 0 };
obj.age ?? 18; // 0
obj.height ?? 180; // 180

1.5 函数核心(普通函数 / 箭头函数 /this)

箭头函数 vs 普通函数

  1. 箭头函数没有this,继承父级作用域的this
  2. 没有arguments、不能用作构造函数、没有原型
  3. 简写语法,适合回调函数

代码示例

// 普通函数:this指向调用者
function fn() { console.log(this); }
fn(); // window/global

// 箭头函数:this继承外层
const obj = {
  fn: () => console.log(this)
};
obj.fn(); // window

1.6 数组高频方法(开发必备)

const arr = [1,2,3];
// 遍历:forEach、map、filter、find、some、every
arr.map(item => item * 2); // [2,4,6]
arr.filter(item => item > 1); // [2,3]

// 增删改查:push/pop/unshift/shift/splice
arr.push(4); // [1,2,3,4]
arr.splice(1,1); // 删除索引1的元素 → [1,3,4]

// 高阶:reduce(求和、去重、扁平化)
arr.reduce((sum, cur) => sum + cur, 0); // 8

1.7 闭包(核心概念)

定义:函数嵌套函数,内部函数访问外部函数变量,形成闭包。作用:私有化变量、延长变量生命周期、实现柯里化风险:滥用会导致内存泄漏

代码示例

function outer() {
  let num = 10;
  return function inner() {
    console.log(num); // 访问外部变量 → 闭包
  };
}
const fn = outer();
fn(); // 10

1.8 原型与原型链

JS 继承的核心机制,面试必考。

  1. 所有对象都有__proto__,指向构造函数的prototype
  2. 原型链:对象查找属性 / 方法的路径,终点是null

代码示例

function Person(name) {
  this.name = name;
}
// 原型方法
Person.prototype.sayHi = function() {
  console.log(this.name);
};
const p = new Person("张三");
p.sayHi(); // 张三

// 原型链关系
p.__proto__ === Person.prototype;
Person.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;

1.9 异步编程(回调 / Promise /async-await)

JS 是单线程语言,异步解决阻塞问题。

// Promise 基础
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve("成功"), 1000);
});
p.then(res => console.log(res));

// async-await(语法糖,开发首选)
async function getData() {
  const res = await p;
  console.log(res);
}
getData();

1.10 事件循环(宏任务 / 微任务)

JS 执行机制,大厂面试必考题:

  1. 执行栈 → 微任务队列 → 宏任务队列
  2. 微任务:Promise.then/catch/finallyMutationObserver
  3. 宏任务:setTimeoutsetIntervalajaxDOM事件

代码示例

console.log(1);
setTimeout(() => console.log(2), 0); // 宏任务
Promise.resolve().then(() => console.log(3)); // 微任务
console.log(4);
// 执行顺序:1 → 4 → 3 → 2

二、JavaScript 开发高频踩坑汇总(99% 开发者都遇到过)

2.1 隐式类型转换踩坑(== 滥用)

错误场景==自动隐式转换,导致逻辑错误

console.log(0 == ''); // true
console.log('' == false); // true

原因==会先转换类型再比较解决方案开发永远优先用 ===,仅判断null/undefined==

let a;
if (a == null) { // 等价于 a === null || a === undefined
  console.log("变量为空");
}

2.2 forEach 中使用 await 失效

错误场景:forEach 不支持异步,无法按顺序执行

const arr = [1,2,3];
arr.forEach(async item => {
  await new Promise(r => setTimeout(r,1000));
  console.log(item); // 1秒后同时输出1、2、3
});

解决方案:用for...of/ 普通 for 循环

(async () => {
  for(let item of arr) {
    await new Promise(r => setTimeout(r,1000));
    console.log(item); // 每隔1秒输出
  }
})();

2.3 引用类型浅拷贝导致数据篡改

错误场景:对象 / 数组直接赋值,修改新变量污染原数据

let obj1 = { name: "张三" };
let obj2 = obj1;
obj2.name = "李四";
console.log(obj1.name); // 李四

解决方案:浅拷贝.../Object.assign,深拷贝JSON.parse/ 手写深拷贝

// 浅拷贝
let obj2 = {...obj1};
// 深拷贝(无函数/undefined时)
let deepObj = JSON.parse(JSON.stringify(obj1));

2.4 this 指向丢失

错误场景:定时器 / 回调函数中 this 指向改变

const obj = {
  name: "张三",
  sayName() {
    setTimeout(function() {
      console.log(this.name); // undefined
    }, 100);
  }
};

解决方案:箭头函数 / 存 this/bind

// 箭头函数
setTimeout(() => console.log(this.name), 100);

2.5 数组空位导致方法异常

错误场景:数组空位(empty)被 forEach/map 跳过

const arr = [1,,3];
arr.forEach(item => console.log(item)); // 只输出1、3

解决方案:初始化数组时避免空位,用fill填充

const arr = [1, undefined, 3];

2.6 闭包导致内存泄漏

错误场景:闭包变量长期占用内存不释放

function leak() {
  let bigData = new Array(1000000).fill("数据");
  return () => bigData;
}
const fn = leak(); // bigData永远不被回收

解决方案:使用完手动置空

fn = null; // 释放内存

2.7 异步同步混淆执行顺序错误

错误场景:直接获取异步函数返回值

function getData() {
  setTimeout(() => return "数据", 1000);
}
const res = getData();
console.log(res); // undefined

解决方案:用 Promise/async-await 接收

2.8 函数默认参数踩坑

错误场景:默认参数仅在undefined时生效

function fn(a = 10) { console.log(a); }
fn(null); // null
fn(undefined); // 10

三、大厂高频 JavaScript 面试题(附答案 + 解析)

3.1 数据类型相关(必考)

题目 1:JS 有哪些数据类型?Symbol 和 BigInt 的特点?

答案:JS 共8 种原始类型 + 引用类型(Object),其中原始类型包含:

  • 7 种原始类型UndefinedNullBooleanNumberStringSymbol(ES2015)、BigInt(ES2020)
  • 1 种引用类型Object(包含ArrayFunctionDateRegExp等子类型)

Symbol 特点

  • 独一无二,不可重复:Symbol('a') !== Symbol('a')
  • 可作为对象属性名,避免属性冲突
  • 不能参与隐式类型转换,Symbol转字符串需手动调用toString()

BigInt 特点

  • 解决Number精度丢失问题(Number仅能精确表示±2^53-1范围内整数)
  • 定义方式:123n / BigInt('456')
  • 不可与Number混合运算,1n + 2会抛错

题目 2:typeof 和 instanceof 的区别?手写 instanceof 原理

答案

对比项 typeof instanceof
作用 判断原始类型(除 null)和引用类型 判断引用类型的继承关系
返回值 字符串(如'number''object' 布尔值(true/false
特殊点 typeof null === 'object'(历史 bug) 无法判断原始类型(如1 instanceof Number === false

手写 instanceof 原理

/**
 * 手写instanceof
 * @param {*} left 待检测对象 
 * @param {*} right 构造函数 
 * @returns {boolean}
 */
function myInstanceof(left, right) {
  // 原始类型直接返回false
  if (typeof left !== 'object' || left === null) return false;
  // 获取右构造函数的原型对象
  let prototype = right.prototype;
  // 获取左对象的隐式原型
  left = left.__proto__;
  // 遍历原型链
  while (true) {
    // 原型链终点为null
    if (left === null) return false;
    // 原型匹配
    if (left === prototype) return true;
    // 向上遍历原型链
    left = left.__proto__;
  }
}

// 测试
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof(123, Number)); // false

3.2 变量声明(var/let/const)

题目:var、let、const 的区别?暂时性死区是什么?

答案:核心差异体现在变量提升、块级作用域、重复声明、重新赋值四个维度:

  1. var:存在变量提升,无块级作用域,可重复声明,可重新赋值
  2. let:无变量提升(存在暂时性死区),有块级作用域,不可重复声明,可重新赋值
  3. const:无变量提升,有块级作用域,不可重复声明,不可重新赋值(引用类型属性可改)

暂时性死区(TDZ) :在代码块内,使用let/const声明变量前,变量处于 “不可访问” 状态,称为暂时性死区。

console.log(a); // 报错:Cannot access 'a' before initialization
let a = 10;

3.3 作用域与作用域链

题目 1:JS 的作用域有哪些?作用域链的作用?

答案:JS 采用词法作用域(静态作用域) ,作用域分为 3 类:

  1. 全局作用域:代码最外层,全局可访问
  2. 函数作用域:函数内部定义,仅函数内可访问
  3. 块级作用域{}包裹(let/const生效),如if/for/switch

作用域链:当访问变量时,会从当前作用域向上查找,直到全局作用域,这条查找链条就是作用域链。作用域链决定了变量的访问权限优先级

题目 2:手写实现块级作用域(用 var 模拟 let)

答案:利用 ** 立即执行函数(IIFE)** 的函数作用域模拟块级作用域:

// 原代码
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
} // 输出 0 1 2

// 用var模拟
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
} // 输出 0 1 2

3.4 闭包(核心难点)

题目 1:什么是闭包?闭包的应用场景?优缺点?

答案闭包定义:内部函数访问外部函数的变量 / 参数,且内部函数被外部引用,形成闭包。

应用场景

  1. 私有化变量:隐藏内部属性,仅暴露接口(如 JS 模块、单例模式)
  2. 防抖 / 节流:缓存定时器标识
  3. 柯里化函数:参数复用、延迟执行
  4. 模块模式:实现单例、封装私有属性

优缺点

  • 优点:私有化变量、延长变量生命周期、实现函数柯里化
  • 缺点:闭包会占用内存,若未及时释放易导致内存泄漏(大量闭包 + 大对象)

题目 2:手写闭包实现私有属性

答案

/**
 * 闭包实现私有属性
 */
function Person(name) {
  // 私有属性
  let _age = 0;
  // 公有方法(闭包访问私有属性)
  this.getName = function() {
    return name;
  };
  this.getAge = function() {
    return _age;
  };
  this.setAge = function(val) {
    if (val >= 0) _age = val;
  };
}

// 测试
const p = new Person('张三');
console.log(p.getName()); // 张三
console.log(p.getAge()); // 0
p.setAge(20);
console.log(p.getAge()); // 20
console.log(p._age); // undefined(私有属性无法直接访问)

题目 3:闭包导致的内存泄漏如何解决?

答案

  1. 及时解除引用:闭包函数不再使用时,将其赋值为null,释放对内部变量的引用
  2. 避免滥用闭包:减少闭包嵌套层级,避免缓存大对象
  3. 使用弱引用:ES6 的WeakMap/WeakSet存储闭包数据,垃圾回收时自动释放(无引用限制)

3.5 原型基础

题目 1:原型、原型对象、构造函数的关系?

答案

  1. 构造函数:通过new创建实例的函数(如function Person() {}
  2. 原型对象:每个函数都有prototype属性,指向原型对象;每个实例都有__proto__属性,指向构造函数的原型对象
  3. 关系实例.__proto__ === 构造函数.prototype,原型对象的constructor属性指向构造函数

题目 2:JS 的继承方式有哪些?手写 ES6 类继承

答案:JS 常见继承方式:原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承(最优)、ES6 class 继承

手写 ES6 class 继承

/**
 * ES6 class继承
 */
class Parent {
  constructor(name) {
    this.name = name;
  }
  // 原型方法
  sayHi() {
    console.log(`Hello, ${this.name}`);
  }
  // 静态方法
  static create() {
    return new Parent('Static');
  }
}

class Child extends Parent {
  constructor(name, age) {
    // 必须调用super,初始化父类构造函数
    super(name);
    this.age = age;
  }
  // 重写原型方法
  sayHi() {
    // 调用父类方法
    super.sayHi();
    console.log(`I'm ${this.age} years old`);
  }
}

// 测试
const c = new Child('李四', 18);
c.sayHi(); // Hello, 李四 → I'm 18 years old
console.log(Child.create()); // Parent { name: 'Static' }

3.6 原型链深入

题目:手写实现寄生组合式继承(最优继承方式)

答案:寄生组合式继承解决了组合继承(调用两次父类构造函数)的效率问题,是 ES6 之前的最优方案:

/**
 * 寄生组合式继承
 * @param {Function} Child 子类 
 * @param {Function} Parent 父类 
 */
function inheritPrototype(Child, Parent) {
  // 创建父类原型的浅拷贝,避免修改父类原型
  const prototype = Object.create(Parent.prototype);
  // 修正constructor指向
  prototype.constructor = Child;
  // 子类原型指向拷贝的父类原型
  Child.prototype = prototype;
}

// 父类
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHi = function() {
  console.log(`Hi, ${this.name}`);
};

// 子类
function Child(name, age) {
  // 调用父类构造函数,初始化属性
  Parent.call(this, name);
  this.age = age;
}

// 实现继承
inheritPrototype(Child, Parent);

// 子类添加方法
Child.prototype.sayAge = function() {
  console.log(`Age: ${this.age}`);
};

// 测试
const c = new Child('王五', 20);
c.sayHi(); // Hi, 王五
c.sayAge(); // Age: 20
console.log(c instanceof Child); // true
console.log(c instanceof Parent); // true

3.7 Promise(核心)

题目 1:Promise 的三种状态?状态能否逆转?then 方法的执行机制?

答案

  1. 三种状态

    • pending:初始状态,未完成
    • fulfilled(resolved):成功状态
    • rejected:失败状态
  2. 状态逆转:状态一旦改变,不可逆转pendingfulfilledpendingrejected,不可逆)

  3. then 执行机制

    • then是微任务(异步执行),返回新的 Promise,支持链式调用
    • then回调返回非 Promise 值,会包装为resolved状态的 Promise;若返回 Promise,会等待其状态改变

题目 2:手写实现 Promise(简易版,含 resolve/reject/then)

答案

/**
 * 简易版Promise实现
 */
class MyPromise {
  // 状态
  #state = 'pending';
  #value = undefined;
  #reason = undefined;
  // 回调队列(处理异步resolve/reject)
  #onFulfilledCallbacks = [];
  #onRejectedCallbacks = [];

  constructor(executor) {
    // 绑定this,避免执行时this丢失
    const resolve = (value) => {
      if (this.#state === 'pending') {
        this.#state = 'fulfilled';
        this.#value = value;
        // 执行成功回调
        this.#onFulfilledCallbacks.forEach(cb => cb());
      }
    };

    const reject = (reason) => {
      if (this.#state === 'pending') {
        this.#state = 'rejected';
        this.#reason = reason;
        // 执行失败回调
        this.#onRejectedCallbacks.forEach(cb => cb());
      }
    };

    try {
      // 执行执行器
      executor(resolve, reject);
    } catch (err) {
      // 执行器抛错,触发reject
      reject(err);
    }
  }

  // then方法
  then(onFulfilled, onRejected) {
    // 处理参数默认值(值穿透)
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
    onRejected = typeof onRejected === 'function' ? onRejected : (r) => { throw r };

    // 返回新的Promise,实现链式调用
    return new MyPromise((resolve, reject) => {
      // 执行成功回调
      const handleFulfilled = () => {
        try {
          const result = onFulfilled(this.#value);
          // 处理返回Promise的情况
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      };

      // 执行失败回调
      const handleRejected = () => {
        try {
          const result = onRejected(this.#reason);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      };

      // 同步状态时直接执行
      if (this.#state === 'fulfilled') {
        handleFulfilled();
      } else if (this.#state === 'rejected') {
        handleRejected();
      } else {
        // 异步状态时,存入回调队列
        this.#onFulfilledCallbacks.push(handleFulfilled);
        this.#onRejectedCallbacks.push(handleRejected);
      }
    });
  }

  // catch方法(等价于then(null, onRejected))
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  // 静态方法:resolve
  static resolve(value) {
    return new MyPromise(resolve => resolve(value));
  }

  // 静态方法:reject
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason));
  }
}

// 测试
new MyPromise((resolve) => {
  setTimeout(() => resolve('Promise测试'), 1000);
}).then(res => {
  console.log(res); // 1秒后输出 Promise测试
  return 123;
}).then(res => {
  console.log(res); // 输出 123
});

3.8 事件循环(Event Loop)

题目 1:JS 的事件循环机制?宏任务与微任务的区别?执行顺序?

答案:JS 是单线程语言,事件循环是解决异步操作的核心机制,流程如下:

  1. 执行栈:先执行同步代码
  2. 微任务队列:同步代码执行完,清空所有微任务
  3. 宏任务队列:微任务清空后,取一个宏任务执行
  4. 循环往复:微任务→宏任务→微任务→宏任务

宏任务script整体代码、setTimeoutsetIntervalAJAX请求DOM事件UI渲染微任务Promise.then/catch/finallyMutationObserverqueueMicrotaskprocess.nextTick(Node.js)

题目 2:分析以下代码的执行顺序(大厂经典题)

console.log('1');
setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => {
    console.log('3');
  });
}, 0);
Promise.resolve().then(() => {
  console.log('4');
  setTimeout(() => {
    console.log('5');
  }, 0);
});
console.log('6');

答案:执行顺序:1 → 6 → 4 → 2 → 3 → 5

四、总结

本文覆盖了JS 核心基础、开发 99% 高频踩坑、大厂必考面试题,所有知识点都搭配了可直接运行的代码示例,踩坑点提供了落地解决方案,手写题是面试高频考点。

如果本文对你有所帮助,欢迎点赞、收藏、转发,一起成长!

昨天 — 2026年4月15日首页

Angular 基础知识点全汇总(附实战示例 | 新手友好)

作者 WayneYang
2026年4月14日 14:43

前言

Angular 是由 Google 维护的企业级前端框架,基于 TypeScript 开发,内置路由、表单、HTTP、依赖注入等全套解决方案,适合中大型后台管理系统、企业级应用开发。本文整理了 Angular 从入门到实战的全套基础知识点,每个知识点搭配可直接运行的代码示例,新手也能快速上手!

适用版本:Angular 14+ / 18+(长期支持版)阅读对象:前端新手、Vue/React 转 Angular 开发者

1. Angular 核心概述

核心特点

  • 完整的企业级框架(全家桶,无需额外集成第三方库)
  • 强类型:基于 TypeScript 开发
  • 内置依赖注入 (DI)、路由、表单、HTTP 客户端
  • 单向数据流 + 可选双向绑定
  • 适合大型团队、长期维护的项目

核心组成

  • 模块 (Module) :组织应用的最小单元
  • 组件 (Component) :页面的最小单元
  • 服务 (Service) :公共逻辑封装
  • 指令 / 管道 / 路由:扩展功能

2. 环境搭建与项目创建

2.1 安装 Angular CLI

bash 运行

# 全局安装 Angular 脚手架
npm install -g @angular/cli

# 验证安装
ng version

2.2 创建新项目

bash 运行

# 创建项目(支持路由、SCSS、严格模式)
ng new angular-demo --routing --style=scss --strict

# 进入项目
cd angular-demo

# 启动项目(默认端口4200)
ng serve --open

2.3 常用 CLI 命令

bash 运行

# 创建组件
ng generate component components/home
# 简写
ng g c components/home

# 创建服务
ng g s services/http

# 创建路由模块
ng g m app-routing --module=app --flat

3. 核心概念:模块 (Module)

定义

模块是 Angular 应用的组织结构,一个应用至少有一个根模块 AppModule

核心作用

  • 声明组件、指令、管道
  • 导入依赖模块
  • 提供服务
  • 启动应用

示例:根模块 app.module.ts

typescript 运行

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// 路由模块
import { AppRoutingModule } from './app-routing.module';
// 根组件
import { AppComponent } from './app.component';
// 自定义组件
import { HomeComponent } from './components/home/home.component';

@NgModule({
  // 声明:当前模块的组件/指令/管道
  declarations: [
    AppComponent,
    HomeComponent
  ],
  // 导入:依赖的其他模块
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  // 提供:全局服务
  providers: [],
  // 启动:根组件
  bootstrap: [AppComponent]
})
export class AppModule { }

4. 组件 (Component) 基础

定义

组件是 Angular 应用的页面最小单元,由 HTML 模板 + TS 逻辑 + CSS 样式 组成。

组件结构

  1. xxx.component.ts:逻辑 / 数据
  2. xxx.component.html:模板
  3. xxx.component.scss:样式
  4. xxx.component.spec.ts:测试文件

示例:自定义组件

typescript 运行

// home.component.ts
import { Component } from '@angular/core';

@Component({
  // 组件选择器(HTML标签)
  selector: 'app-home',
  // 模板路径
  templateUrl: './home.component.html',
  // 样式路径
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {
  // 组件数据
  title = 'Angular 入门组件';
  // 方法
  sayHello() {
    alert('Hello Angular!');
  }
}

html 预览

<!-- home.component.html -->
<div class="home">
  <h2>{{ title }}</h2>
  <button (click)="sayHello()">点击我</button>
</div>

5. 模板基础语法

5.1 插值表达式

作用:渲染组件中的变量

html 预览

<h1>{{ 变量名 }}</h1>
<p>{{ 1 + 1 }}</p>
<p>{{ name.toUpperCase() }}</p>

5.2 属性绑定

作用:给 HTML 标签动态绑定属性

html 预览

<!-- 原生属性 -->
<img [src]="imgUrl" alt="图片">
<!-- 类名绑定 -->
<div [class.active]="isActive">激活状态</div>
<!-- 样式绑定 -->
<div [style.color]="textColor">文字颜色</div>

6. 数据绑定(单向 / 双向)

6.1 单向绑定

  1. 组件 → 模板[]
  2. 模板 → 组件()

6.2 双向绑定(核心)

依赖 FormsModule,用于表单数据同步

html 预览

<input [(ngModel)]="username" placeholder="请输入用户名">
<p>你输入的用户名:{{ username }}</p>

使用前提:在 app.module.ts 导入模块

typescript 运行

import { FormsModule } from '@angular/forms';

imports: [BrowserModule, FormsModule]

7. Angular 内置指令

分为 结构指令(修改 DOM 结构)和 属性指令(修改 DOM 样式 / 属性)

7.1 结构指令

1. *ngIf 条件渲染

html 预览

<div *ngIf="isShow">显示内容</div>
<div *ngIf="!isShow">隐藏内容</div>

2. *ngFor 列表渲染

html 预览

<ul>
  <li *ngFor="let item of list; let i = index">
    索引:{{ i }},内容:{{ item.name }}
  </li>
</ul>

3. *ngSwitch 多条件判断

html 预览

<div [ngSwitch]="status">
  <p *ngSwitchCase="1">待支付</p>
  <p *ngSwitchCase="2">已支付</p>
  <p *ngSwitchDefault>未知状态</p>
</div>

7.2 属性指令

1. ngClass 动态类名

html 预览

<div [ngClass]="{ active: isActive, disabled: isDisabled }">
  动态样式
</div>

2. ngStyle 动态样式

html 预览

<div [ngStyle]="{ color: 'red', fontSize: '20px' }">
  内联样式
</div>

8. 事件绑定与用户交互

8.1 基础事件绑定

html 预览

<!-- 点击事件 -->
<button (click)="handleClick()">点击</button>
<!-- 输入事件 -->
<input (input)="handleInput($event)" />
<!-- 表单提交 -->
<form (ngSubmit)="handleSubmit()"></form>

8.2 事件对象

typescript 运行

handleInput(e: Event) {
  const value = (e.target as HTMLInputElement).value;
  console.log(value);
}

9. 组件通讯(核心)

9.1 父组件 → 子组件(@Input)

子组件

typescript 运行

import { Component, Input } from '@angular/core';

@Component({ selector: 'app-child' })
export class ChildComponent {
  // 接收父组件数据
  @Input() msg = '';
}

父组件模板

html 预览

<app-child [msg]="父组件传递的数据"></app-child>

9.2 子组件 → 父组件(@Output + EventEmitter)

子组件

typescript 运行

import { Component, Output, EventEmitter } from '@angular/core';

export class ChildComponent {
  @Output() sendMsg = new EventEmitter<string>();
  
  sendToParent() {
    this.sendMsg.emit('子组件传递的消息');
  }
}

父组件

html 预览

<app-child (sendMsg)="getMsg($event)"></app-child>

typescript 运行

getMsg(msg: string) {
  console.log(msg);
}

9.3 兄弟组件通讯

步骤 1:创建消息服务

bash 运行

ng g s services/message

typescript 运行

// message.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class MessageService {
  // 订阅主体
  private msgSubject = new Subject<any>();

  // 发送消息
  sendMessage(data: any) {
    this.msgSubject.next(data);
  }

  // 接收消息
  getMessage() {
    return this.msgSubject.asObservable();
  }
}

步骤 2:兄弟组件 A(发送方)

typescript 运行

// brother-a.component.ts
import { MessageService } from '../../services/message.service';
constructor(private msgService: MessageService) {}

sendBrotherMsg() {
  this.msgService.sendMessage('来自兄弟A的消息');
}

html 预览

<button (click)="sendBrotherMsg()">发送给兄弟B</button>

步骤 3:兄弟组件 B(接收方)

typescript 运行

// brother-b.component.ts
import { MessageService } from '../../services/message.service';
import { Subscription } from 'rxjs';

msg = '';
sub!: Subscription;

constructor(private msgService: MessageService) {}

ngOnInit() {
  // 订阅消息
  this.sub = this.msgService.getMessage().subscribe(data => {
    this.msg = data;
  });
}

// 销毁时取消订阅(防内存泄漏)
ngOnDestroy() {
  this.sub.unsubscribe();
}

html 预览

<p>接收兄弟消息:{{ msg }}</p>

步骤 4:父组件承载两个兄弟组件

html 预览

<!-- parent.component.html -->
<app-brother-a></app-brother-a>
<app-brother-b></app-brother-b>

10. 服务与依赖注入 (Service)

typescript

运行

// data.service.ts
@Injectable({ providedIn: 'root' })
export class DataService {
  user = { name: 'Angular' };
  getUser() { return this.user; }
}

组件使用

typescript

运行

constructor(private dataService: DataService) {}
ngOnInit() {
  console.log(this.dataService.getUser());
}

11. 路由 (Routing) 基础

typescript

运行

// app-routing.module.ts
const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: '**', component: HomeComponent }
];

html

预览

<router-outlet></router-outlet>
<a routerLink="/home">首页</a>

10. 服务与依赖注入 (Service)

定义

服务用于封装公共逻辑(HTTP 请求、工具函数、全局状态),实现业务解耦。

10.1 创建服务

bash 运行

ng g s services/data

10.2 服务示例

typescript 运行

// data.service.ts
import { Injectable } from '@angular/core';

// 注入根组件(全局单例)
@Injectable({ providedIn: 'root' })
export class DataService {
  // 公共数据
  userInfo = { name: 'Angular用户' };
  
  // 公共方法
  getUserInfo() {
    return this.userInfo;
  }
}

10.3 组件使用服务(依赖注入)

typescript 运行

import { DataService } from '../../services/data.service';

export class HomeComponent {
  // 依赖注入服务
  constructor(private dataService: DataService) {}
  
  ngOnInit() {
    // 使用服务
    const user = this.dataService.getUserInfo();
    console.log(user);
  }
}

11. 路由 (Routing) 基础

11.1 路由配置

typescript 运行

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { AboutComponent } from './components/about/about.component';

const routes: Routes = [
  // 默认路由
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  // 404路由
  { path: '**', redirectTo: '/home' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

11.2 路由出口 + 路由跳转

html 预览

<!-- 路由出口(渲染组件) -->
<router-outlet></router-outlet>

<!-- 声明式跳转 -->
<a routerLink="/home">首页</a>
<a routerLink="/about">关于</a>

<!-- 编程式跳转 -->
<button (click)="toAbout()">跳转到关于页</button>

typescript 运行

// 编程式导航
import { Router } from '@angular/router';
constructor(private router: Router) {}
toAbout() {
  this.router.navigate(['/about']);
}

12. 表单开发(模板驱动 / 响应式)

12.1 模板驱动表单(简单表单)

html 预览

<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
  <input name="username" ngModel required placeholder="用户名">
  <button type="submit">提交</button>
</form>

12.2 响应式表单(复杂表单,推荐)

导入模块

typescript 运行

import { ReactiveFormsModule } from '@angular/forms';

使用

typescript 运行

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class LoginComponent {
  loginForm: FormGroup;
  
  constructor(private fb: FormBuilder) {
    // 初始化表单
    this.loginForm = this.fb.group({
      username: ['', [Validators.required, Validators.minLength(2)]],
      password: ['', [Validators.required]]
    });
  }
  
  onSubmit() {
    console.log(this.loginForm.value);
  }
}

html 预览

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <input formControlName="username">
  <input formControlName="password">
  <button type="submit" [disabled]="!loginForm.valid">提交</button>
</form>

13. HTTP 网络请求

13.1 导入模块

typescript 运行

import { HttpClientModule } from '@angular/common/http';

13.2 封装 HTTP 服务

typescript 运行

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class HttpService {
  constructor(private http: HttpClient) {}
  
  // GET请求
  getList(): Observable<any> {
    return this.http.get('https://api.example.com/list');
  }
  
  // POST请求
  addData(data: any): Observable<any> {
    return this.http.post('https://api.example.com/add', data);
  }
}

13.3 组件使用

typescript 运行

this.httpService.getList().subscribe({
  next: (res) => console.log(res),
  error: (err) => console.error(err)
});

14. 管道 (Pipe)

14.1 内置管道

html 预览

<!-- 日期管道 -->
<p>{{ now | date:'yyyy-MM-dd' }}</p>
<!-- 大小写管道 -->
<p>{{ name | uppercase }}</p>
<!-- 小数管道 -->
<p>{{ num | number:'1.2-2' }}</p>
<!-- JSON管道 -->
<p>{{ obj | json }}</p>

14.2 自定义管道

示例 1:性别转换管道

bash 运行

ng g p pipes/sex-transform

typescript 运行

// sex-transform.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'sexTransform' })
export class SexTransformPipe implements PipeTransform {
  // value:传入值,args:额外参数
  transform(value: number): string {
    switch (value) {
      case 1: return '男';
      case 2: return '女';
      default: return '未知';
    }
  }
}

使用方式

html 预览

<p>{{ 1 | sexTransform }}</p> <!-- 输出:男 -->
<p>{{ 2 | sexTransform }}</p> <!-- 输出:女 -->

示例 2:数组过滤管道

typescript 运行

// filter.pipe.ts
@Pipe({ name: 'filterList' })
export class FilterListPipe implements PipeTransform {
  transform(list: any[], key: string, keyword: string): any[] {
    if (!keyword) return list;
    return list.filter(item => item[key].includes(keyword));
  }
}

html 预览

<li *ngFor="let item of list | filterList: 'name': keyword">{{item.name}}</li>

15. 组件生命周期钩子

15.1 Angular 共有 8 个生命周期钩子,按执行顺序排列,包含创建 / 更新 / 销毁全流程

typescript 运行

import {
  Component,
  OnChanges,
  OnInit,
  DoCheck,
  AfterContentInit,
  AfterContentChecked,
  AfterViewInit,
  AfterViewChecked,
  OnDestroy,
  Input
} from '@angular/core';

@Component({
  selector: 'app-life-cycle',
  template: `<p>{{ msg }}</p>`
})
export class LifeCycleComponent implements
  OnChanges,
  OnInit,
  DoCheck,
  AfterContentInit,
  AfterContentChecked,
  AfterViewInit,
  AfterViewChecked,
  OnDestroy {

  @Input() msg = '测试';

  // 1. 输入属性(@Input)改变时触发
  ngOnChanges(): void {
    console.log('1. ngOnChanges - 属性改变');
  }

  // 2. 组件初始化(最常用,请求数据)
  ngOnInit(): void {
    console.log('2. ngOnInit - 组件初始化完成');
  }

  // 3. 脏值检测(每次变更检测触发)
  ngDoCheck(): void {
    console.log('3. ngDoCheck - 变更检测');
  }

  // 4. 内容投影初始化完成
  ngAfterContentInit(): void {
    console.log('4. ngAfterContentInit - 内容投影初始化');
  }

  // 5. 内容投影变更检测
  ngAfterContentChecked(): void {
    console.log('5. ngAfterContentChecked - 内容检测');
  }

  // 6. 视图初始化完成(操作DOM)
  ngAfterViewInit(): void {
    console.log('6. ngAfterViewInit - 视图渲染完成');
  }

  // 7. 视图变更检测
  ngAfterViewChecked(): void {
    console.log('7. ngAfterViewChecked - 视图检测');
  }

  // 8. 组件销毁(清理定时器/订阅)
  ngOnDestroy(): void {
    console.log('8. ngOnDestroy - 组件销毁');
  }
}

15.2 执行顺序

  1. ngOnChanges → 输入属性变化
  2. ngOnInit → 初始化
  3. ngDoCheck → 每次渲染检查
  4. ngAfterContentInit → 内容投影
  5. ngAfterContentChecked → 内容检查
  6. ngAfterViewInit → DOM 渲染完成
  7. ngAfterViewChecked → 视图检查
  8. ngOnDestroy → 组件销毁

15.3 核心使用场景

  • ngOnInit发起网络请求、初始化数据
  • ngOnChanges:监听父组件传值变化
  • ngAfterViewInit:操作 DOM 元素
  • ngOnDestroy清除定时器、取消订阅、防内存泄漏

16. 常用装饰器总结

表格

装饰器 作用 位置
@Component 定义组件 组件类
@NgModule 定义模块 模块类
@Injectable 定义服务 服务类
@Input 父传子 子组件属性
@Output 子传父 子组件事件
@ViewChild 获取 DOM / 子组件 组件属性

17. 项目打包与部署

17.1 生产打包

bash 运行

ng build --prod
# 或
ng build --configuration production

打包产物:dist/ 目录

17.2 部署

dist 目录静态文件部署到 Nginx、Apache、GitHub Pages 等平台。


18. 总结

本文覆盖了 Angular 入门所有核心基础知识点,包含:

  • 环境搭建、模块 / 组件基础
  • 模板语法、数据绑定、内置指令
  • 组件通讯、服务注入、路由、表单、HTTP
  • 生命周期、管道、打包部署

Angular 作为企业级框架,学习曲线略陡,但规范统一、生态完善、长期维护,非常适合中大型项目开发。掌握以上知识点,即可独立开发 Angular 中小型应用!

昨天以前首页

Vue2 与 Vue3 超全基础知识点汇总

作者 WayneYang
2026年4月14日 16:12

本文涵盖Vue2/Vue3 所有核心基础 API,每个知识点都配完整可运行代码 + 逐行注释,0 基础也能看懂。全文从入门到实战,对比两代 Vue 差异。

1. Vue 基础认知

1.1 核心定义

Vue 是一套用于构建用户界面的渐进式 JavaScript 框架,核心是数据驱动视图,数据变→视图自动变,无需手动操作 DOM。

1.2 Vue2 vs Vue3 核心区别

对比项 Vue2 Vue3
响应式原理 Object.defineProperty ES6 Proxy(无监听缺陷)
代码风格 选项式 API(Options API) 组合式 API(Composition API)+ 兼容选项式
TS 支持 差,需手动配置 原生 TS,类型推导完美
构建工具 Vue-CLI(Webpack) Vite(极速启动)
体积 全量打包,体积大 按需引入,Tree-Shaking 优化
性能 普通 渲染性能提升 55%,内存减少 33%

2. 项目创建与目录结构

2.1 Vue2 项目创建

bash 运行

# 1. 全局安装Vue脚手架
npm install -g @vue/cli

# 2. 创建项目
vue create vue2-project

# 3. 运行项目
cd vue2-project
npm run serve

2.2 Vue3 项目创建(推荐 Vite)

bash 运行

# 1. Vite创建Vue3项目
npm create vite@latest vue3-project -- --template vue

# 2. 安装依赖
cd vue3-project
npm install

# 3. 运行项目
npm run dev

3. 入口文件 main.js 完整写法

3.1 Vue2 入口文件

javascript 运行

// 引入Vue核心库
import Vue from 'vue'
// 引入根组件
import App from './App.vue'
// 关闭生产环境提示
Vue.config.productionTip = false

// 创建Vue实例,挂载根组件到#app
new Vue({
  // 渲染函数
  render: h => h(App)
}).$mount('#app') // 挂载到public/index.html的#app节点

3.2 Vue3 入口文件

javascript 运行

// 引入createApp创建应用实例
import { createApp } from 'vue'
// 引入根组件
import App from './App.vue'
// 引入样式
import './style.css'

// 1. 创建应用实例
const app = createApp(App)
// 2. 挂载到DOM节点
app.mount('#app')

// 拓展:全局配置(Vue3无全局污染)
// app.config.globalProperties.$msg = '全局变量'

4. 模板核心语法(全指令详解)

Vue 模板语法基于 HTML,所有指令以 v- 开头,数据变视图自动更新

4.1 文本插值 {{}}

<!-- Vue2 和 Vue3 模板插值用法完全一致 -->
<template>
  <div>
    <!-- 直接渲染变量 -->
    <h1>{{ msg }}</h1>
    <!-- 渲染表达式 -->
    <p>{{ num + 1 }}</p>
    <p>{{ isShow ? '显示' : '隐藏' }}</p>
  </div>
</template>

<!-- Vue2 -->
<script>
export default {
  data() {
    return {
      msg: 'Hello Vue2',
      num: 10,
      isShow: true
    }
  }
}
</script>

<!-- Vue3 -->
<script setup>
import { ref } from 'vue'
const msg = ref('Hello Vue3')
const num = ref(10)
const isShow = ref(true)
</script>

4.2 v-html 渲染 HTML 标签

{{}} 会转义标签,v-html 可解析原生 HTML

<template>
  <div v-html="htmlStr"></div>
</template>

<!-- Vue2 -->
<script>
export default {
  data() {
    return { htmlStr: '<span style="color:red">红色文字</span>' }
  }
}
</script>

<!-- Vue3 -->
<script setup>
import { ref } from 'vue'
const htmlStr = ref('<span style="color:red">红色文字</span>')
</script>

⚠️ 安全警告:仅在信任的内容上使用 v-html,防止 XSS 攻击!

4.3 v-bind 绑定属性(简写:)

动态绑定标签属性(src、class、style、disabled 等)

<template>
  <!-- 完整写法 -->
  <img v-bind:src="imgUrl" />
  <!-- 简写: (最常用) -->
  <img :src="imgUrl" />
  <!-- 绑定class -->
  <div :class="{ active: isActive }">class绑定</div>
</template>

<!-- Vue2 -->
<script>
export default {
  data() {
    return {
      imgUrl: 'https://xxx.jpg',
      isActive: true
    }
  }
}
</script>

4.4 v-on 绑定事件(简写 @)

绑定点击、输入、鼠标等事件

<template>
  <!-- 完整写法 -->
  <button v-on:click="handleClick">点击</button>
  <!-- 简写@ (最常用) -->
  <button @click="handleClick">点击</button>
  <!-- 传参 -->
  <button @click="handleClickParams(10)">传参点击</button>
</template>

<!-- Vue2 -->
<script>
export default {
  methods: {
    handleClick() { alert('Vue2点击事件') },
    handleClickParams(num) { alert('参数:' + num) }
  }
}
</script>

4.5 v-model 双向绑定(表单专用)

表单元素(input/textarea/select)数据双向同步

vue2

<template>
  <input v-model="inputVal" placeholder="请输入" />
  <p>输入内容:{{ inputVal }}</p>
</template>

<!-- Vue2 -->
<script>
export default {
  data() { return { inputVal: '' } }
}
</script>

vue3 统一语法,支持多 v-model,废弃 .sync

<!-- 父组件 -->
<Child v-model:msg="msg" v-model:age="age" />

<!-- 子组件 -->
<script setup>
const props = defineProps(['msg', 'age'])
const emit = defineEmits(['update:msg', 'update:age'])
</script>

4.6 v-for 列表渲染(必须加 key)

循环渲染数组 / 对象,key 必须唯一,不能用 index

<template>
  <ul>
    <li v-for="item in list" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

<!-- Vue2 -->
<script>
export default {
  data() {
    return { list: [{id:1,name:'苹果'},{id:2,name:'香蕉'}] }
  }
}
</script>

4.7 v-if /v-else/v-else-if 条件渲染

控制元素创建 / 销毁,切换开销大

<template>
  <div v-if="score >= 90">优秀</div>
  <div v-else-if="score >= 60">及格</div>
  <div v-else">不及格</div>
</template>

4.8 v-show 条件显示

控制元素显示 / 隐藏(display:none),切换开销小

<template>
  <div v-show="isShow">v-show显示内容</div>
</template>

4.9 其他指令

  • v-once:只渲染一次,后续数据更新不重新渲染
  • v-pre:跳过编译,直接显示原始内容
  • v-cloak:解决插值表达式闪烁问题

5. 响应式数据 API 全解(最核心)

响应式:数据改变 → 视图自动刷新,无需操作 DOM

5.1 Vue2 响应式 API

5.1.1 data 定义响应式数据

data 必须是函数,返回对象,防止组件复用数据污染

<script>
export default {
  // data是函数,返回对象
  data() {
    return {
      // 基本数据类型
      msg: 'Vue2',
      num: 0,
      // 引用数据类型
      user: { name: '张三', age: 18 },
      list: [1,2,3]
    }
  }
}
</script>

5.1.2 Vue2 响应式缺陷 & 修复 API

Vue2 用 Object.defineProperty无法监听数组下标修改、对象新增属性

<script>
export default {
  data() { return { user: {}, list: [1,2] } },
  mounted() {
    // 1. 对象新增属性 → 视图不更新
    this.user.name = '张三' // 无效
    // 修复:this.$set
    this.$set(this.user, 'name', '张三')

    // 2. 数组下标修改 → 视图不更新
    this.list[0] = 100 // 无效
    // 修复:this.$set
    this.$set(this.list, 0, 100)

    // 3. 删除对象属性 → 视图不更新
    // 修复:this.$delete
    this.$delete(this.user, 'name')
  }
}
</script>

5.2 Vue3 响应式 API(组合式)

Vue3 用 Proxy,无任何响应式缺陷,所有操作都能监听

5.2.1 ref 定义基本数据类型

用于:字符串、数字、布尔、null、undefined

<script setup>
// 1. 引入ref
import { ref } from 'vue'

// 2. 定义响应式数据
const msg = ref('Hello Vue3') // 字符串
const num = ref(0) // 数字
const isShow = ref(true) // 布尔

// 3. JS中修改值必须加 .value
const changeMsg = () => {
  msg.value = '修改后的Vue3'
  num.value++
}
</script>

<template>
  <!-- 模板中自动解包,无需.value -->
  <p>{{ msg }}</p>
  <p>{{ num }}</p>
  <button @click="changeMsg">修改数据</button>
</template>

5.2.2 reactive 定义引用数据类型

用于:对象、数组、Map、Set

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

// 定义对象
const user = reactive({ name: '李四', age: 20 })
// 定义数组
const list = reactive(['苹果', '香蕉'])

// 修改数据:直接修改,无需.value
const updateUser = () => {
  user.age = 21 // 直接改
  list[0] = '葡萄' // 直接改数组下标,无缺陷
}
</script>

5.2.3 toRefs 解构 reactive 数据

reactive 解构后会丢失响应式,用 toRefs 修复

<script setup>
import { reactive, toRefs } from 'vue'
const user = reactive({ name: '王五', age: 25 })

// 错误:解构后无响应式
// const { name, age } = user

// 正确:toRefs 保持响应式
const { name, age } = toRefs(user)

// 修改数据
const changeAge = () => {
  age.value = 26
}
</script>

5.2.4 toRef 单独抽取一个属性

<script setup>
import { reactive, toRef } from 'vue'
const user = reactive({ name: '赵六' })
// 抽取单个属性
const name = toRef(user, 'name')
</script>

5.2.5 其他响应式 API

  • unref:如果是 ref 返回.value,否则返回本身
  • shallowRef:浅响应式,只监听.value 修改
  • shallowReactive:浅响应式,只监听第一层属性

6. 方法(methods)与事件处理

6.1 Vue2 定义方法

所有方法放在 methods 选项中

<template>
  <button @click="add">点击+1</button>
  <p>数字:{{ num }}</p>
</template>

<script>
export default {
  data() { return { num: 0 } },
  // 方法存放位置
  methods: {
    add() {
      // 通过this访问data数据
      this.num++
    }
  }
}
</script>

6.2 Vue3 定义方法

直接在 <script setup> 中定义函数,模板直接用

<template>
  <button @click="add">点击+1</button>
  <p>数字:{{ num }}</p>
</template>

<script setup>
import { ref } from 'vue'
const num = ref(0)

// 直接定义函数
const add = () => {
  num.value++
}
</script>

6.3 事件修饰符

<!-- 阻止冒泡 -->
<button @click.stop="handle">.stop</button>
<!-- 阻止默认行为 -->
<a @click.prevent="handle">.prevent</a>
<!-- 只触发一次 -->
<button @click.once="handle">.once</button>

7. 生命周期钩子完整对比 + 使用

生命周期:Vue 实例从创建→挂载→更新→销毁的全过程

7.1 完整生命周期对应表

Vue2 钩子 Vue3 钩子 执行时机
beforeCreate setup 创建前(无 this)
created setup 创建后(可访问数据)
beforeMount onBeforeMount 挂载前
mounted onMounted 挂载完成(操作 DOM、发请求)
beforeUpdate onBeforeUpdate 更新前
updated onUpdated 更新完成
beforeDestroy onBeforeUnmount 销毁前
destroyed onUnmounted 销毁完成

7.2 Vue2 生命周期使用

<script>
export default {
  data() { return { msg: 'Vue2生命周期' } },
  // 挂载完成,最常用
  mounted() {
    console.log('DOM渲染完成,可发请求')
  },
  // 销毁前
  beforeDestroy() {
    console.log('组件销毁,清除定时器')
  }
}
</script>

7.3 Vue3 生命周期使用

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const msg = ref('Vue3生命周期')

// 挂载完成
onMounted(() => {
  console.log('DOM渲染完成')
})

// 组件销毁
onUnmounted(() => {
  console.log('组件销毁')
})
</script>

8. 计算属性 computed(缓存特性)

8.1 作用

处理复杂逻辑,有缓存,依赖数据不变时不会重新计算,比 methods 性能高

8.2 Vue2 computed

<template>
  <p>全名:{{ fullName }}</p>
</template>

<script>
export default {
  data() {
    return { firstName: '张', lastName: '三' }
  },
  // 计算属性
  computed: {
    // 只读计算属性
    fullName() {
      return this.firstName + this.lastName
    },
    // 读写计算属性(get+set)
    fullName2: {
      get() { return this.firstName + this.lastName },
      set(val) {
        const arr = val.split(' ')
        this.firstName = arr[0]
        this.lastName = arr[1]
      }
    }
  }
}
</script>

8.3 Vue3 computed

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('李')
const lastName = ref('四')

// 只读计算属性
const fullName = computed(() => {
  return firstName.value + lastName.value
})

// 读写计算属性
const fullName2 = computed({
  get() { return firstName.value + lastName.value },
  set(val) {
    const arr = val.split(' ')
    firstName.value = arr[0]
    lastName.value = arr[1]
  }
})
</script>

9. 侦听器 watch /watchEffect(监听数据变化)

9.1 watch 监听指定数据(Vue2)

<script>
export default {
  data() { return { num: 0, user: { age: 18 } } },
  watch: {
    // 监听基本类型
    num(newVal, oldVal) {
      console.log('新值:', newVal, '旧值:', oldVal)
    },
    // 深度监听对象(必须加deep:true)
    user: {
      handler(newVal) { console.log('user变化:', newVal) },
      deep: true,
      immediate: true // 立即执行一次
    }
  }
}
</script>

9.2 watch 监听指定数据(Vue3)

<script setup>
import { ref, reactive, watch } from 'vue'
const num = ref(0)
const user = reactive({ age: 18 })

// 监听基本类型
watch(num, (newVal, oldVal) => {
  console.log(newVal, oldVal)
})

// 深度监听对象
watch(user, (newVal) => {
  console.log(newVal)
}, { deep: true, immediate: true })
</script>

9.3 watchEffect 自动监听(Vue3 专属)

无需指定依赖,自动收集,代码更简洁

<script setup>
import { ref, watchEffect } from 'vue'
const num = ref(0)

// 只要num变化,自动执行
watchEffect(() => {
  console.log('num变化:', num.value)
})
</script>

10. 组件基础定义与使用

组件:可复用的 Vue 实例,一个组件就是一个.vue 文件

10.1 Vue2 组件使用

<!-- 父组件 App.vue -->
<template>
  <div>
    <!-- 使用子组件 -->
    <Child />
  </div>
</template>

<script>
// 1. 引入子组件
import Child from './components/Child.vue'
export default {
  // 2. 注册组件
  components: { Child }
}
</script>

<!-- 子组件 Child.vue -->
<template>
  <div>我是子组件</div>
</template>

10.2 Vue3 组件使用

<script setup>引入即注册,无需手动注册

<!-- 父组件 -->
<template>
  <Child />
</template>

<script setup>
// 引入直接用,无需注册
import Child from './components/Child.vue'
</script>

11. 组件通信 8 种方式(全场景)

11.1 父传子 props(最常用)

Vue2 父传子

<!-- 父组件 -->
<Child :msg="父组件数据" :user="user" />
<script>
import Child from './Child.vue'
export default {
  components: { Child },
  data() { return { msg: '父组件传给子组件', user: { name: '父' } } }
}
</script>

<!-- 子组件 -->
<script>
export default {
  // 接收父组件数据
  props: {
    msg: String,
    user: Object
  }
}
</script>

Vue3 父传子

<!-- 父组件 -->
<Child :msg="父组件数据" />
<script setup>
import Child from './Child.vue'
const msg = ref('父组件数据')
</script>

<!-- 子组件 -->
<script setup>
import { defineProps } from 'vue'
// 定义props,接收数据
const props = defineProps({
  msg: {
    type: String,
    default: ''
  }
})
// 模板中直接用{{ msg }}
</script>

11.2 子传父 $emit /defineEmits

Vue2 子传父

<!-- 子组件 -->
<button @click="sendToParent">发送给父组件</button>
<script>
export default {
  methods: {
    sendToParent() {
      // 触发自定义事件,传参
      this.$emit('sendMsg', '子组件数据')
    }
  }
}
</script>

<!-- 父组件 -->
<Child @sendMsg="handleReceive" />
<script>
export default {
  methods: {
    handleReceive(val) { console.log('接收子组件数据:', val) }
  }
}
</script>

Vue3 子传父

<!-- 子组件 -->
<script setup>
import { defineEmits } from 'vue'
// 定义自定义事件
const emit = defineEmits(['sendMsg'])
const send = () => {
  emit('sendMsg', 'Vue3子组件数据')
}
</script>

<!-- 父组件 -->
<Child @sendMsg="handleReceive" />

11.3 其他通信方式

1、兄弟组件通信:Vue2 → EventBus;Vue3 → mitt / Pinia

2、跨级组件通信:provide /inject

适用场景:多层嵌套组件(如祖父→父→孙→曾孙),无需逐层透传props,由祖先组件提供数据,后代组件直接注入使用。


Vue2 vs Vue3 核心差异

特性 Vue2 Vue3
响应式 默认非响应式,需手动用computed/ref包装 原生支持响应式,直接传递ref/reactive即可
API 位置 选项式 API(provide/inject选项) 组合式 API(setup中用provide/inject函数)
TS 支持 强(类型自动推导)

Vue2 示例

javascript 运行

// 祖先组件(App.vue)
export default {
  provide() {
    return {
      // 传递响应式数据,必须用computed包装
      appName: computed(() => this.appName),
      userInfo: { name: '张三', age: 30 }
    }
  },
  data() {
    return {
      appName: 'Vue2 应用'
    }
  }
}

// 后代组件(任意层级,如孙组件)
export default {
  inject: ['appName', 'userInfo'],
  mounted() {
    console.log('注入的appName:', this.appName.value) // computed需.value
    console.log('注入的userInfo:', this.userInfo)
  }
}

Vue3 示例(组合式 API + <script setup>

<!-- 祖先组件 App.vue -->
<script setup>
import { provide, ref, reactive } from 'vue'

// 响应式数据
const appName = ref('Vue3 应用')
const userInfo = reactive({ name: '张三', age: 30 })

// 提供数据,直接传递响应式变量
provide('appName', appName)
provide('userInfo', userInfo)
</script>

<!-- 后代组件(任意层级) -->
<script setup>
import { inject } from 'vue'

// 注入数据,第二个参数为默认值(可选)
const appName = inject('appName', '默认应用名')
const userInfo = inject('userInfo')

console.log('注入的appName:', appName.value) // ref需.value
console.log('注入的userInfo:', userInfo)
</script>

3、ref / $refs:父组件获取子组件实例

适用场景:父组件需要直接调用子组件的方法、访问子组件的 data,或操作子组件的 DOM 元素。


Vue2 vs Vue3 核心差异

表格

特性 Vue2 Vue3
获取实例 this.$refs.child 选项式同 Vue2;组合式需在onMounted后通过ref获取
<script setup>支持 无此语法 子组件必须用defineExpose显式暴露属性 / 方法
DOM 访问 this.$refs.dom 同 Vue2,组合式需ref绑定

Vue2 示例

<!-- 子组件 Child.vue -->
<template>
  <div>子组件</div>
</template>
<script>
export default {
  data() {
    return {
      childMsg: '我是子组件数据'
    }
  },
  methods: {
    childMethod() {
      console.log('子组件方法被调用')
      return '子组件返回值'
    }
  }
}
</script>

<!-- 父组件 Parent.vue -->
<template>
  <Child ref="childComp" />
  <div ref="domBox">父组件DOM</div>
</template>
<script>
import Child from './Child.vue'
export default {
  components: { Child },
  mounted() {
    // 获取子组件实例
    const childInstance = this.$refs.childComp
    console.log('子组件数据:', childInstance.childMsg)
    // 调用子组件方法
    const res = childInstance.childMethod()
    console.log('子组件方法返回值:', res)

    // 获取DOM元素
    const dom = this.$refs.domBox
    console.log('DOM元素:', dom)
  }
}
</script>

Vue3 示例(<script setup>

<!-- 子组件 Child.vue -->
<template>
  <div>子组件</div>
</template>
<script setup>
import { ref } from 'vue'
const childMsg = ref('我是子组件数据')
const childMethod = () => {
  console.log('子组件方法被调用')
  return '子组件返回值'
}

// 【关键】<script setup>默认闭包,必须显式暴露给父组件!
defineExpose({
  childMsg,
  childMethod
})
</script>

<!-- 父组件 Parent.vue -->
<template>
  <Child ref="childComp" />
  <div ref="domBox">父组件DOM</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

// 声明ref,绑定到子组件/DOM
const childComp = ref(null)
const domBox = ref(null)

onMounted(() => {
  // 必须在onMounted后才能获取到实例/DOM!
  console.log('子组件实例:', childComp.value)
  console.log('子组件数据:', childComp.value.childMsg)
  const res = childComp.value.childMethod()
  console.log('子组件方法返回值:', res)

  // 获取DOM
  console.log('DOM元素:', domBox.value)
})
</script>

4、全局状态管理:Vuex(Vue2) / Pinia(Vue3)

适用场景:中大型项目,任意关系组件共享全局状态,需要统一管理状态读写、异步操作。


Vue2 方案:Vuex(Vue2 官方状态管理)

Vuex 核心概念:State(状态)、Mutations(同步修改)、Actions(异步操作)、Getters(计算属性)、Modules(模块化)。

javascript 运行

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    userInfo: null
  },
  mutations: {
    // 同步修改state(唯一修改state的方式)
    increment(state, payload = 1) {
      state.count += payload
    },
    setUserInfo(state, user) {
      state.userInfo = user
    }
  },
  actions: {
    // 异步操作,提交mutation
    async fetchUserInfo({ commit }) {
      const res = await fetch('/api/user')
      const user = await res.json()
      commit('setUserInfo', user)
    }
  },
  getters: {
    doubleCount: state => state.count * 2
  }
})

// 组件中使用
export default {
  computed: {
    count() {
      return this.$store.state.count
    },
    doubleCount() {
      return this.$store.getters.doubleCount
    }
  },
  methods: {
    addCount() {
      this.$store.commit('increment', 2) // 提交mutation
    },
    getUserInfo() {
      this.$store.dispatch('fetchUserInfo') // 触发action
    }
  }
}

Vue3 方案:Pinia(Vue3 官方推荐,替代 Vuex)

Pinia 是 Vue3 的下一代状态管理,相比 Vuex:

  • 去掉Mutations,直接在actions中修改状态(同步 / 异步都支持)
  • 自动模块化,无需手动注册
  • 完美支持 TS,组合式 API 友好
  • 体积仅~1KB,DevTools 支持更好

javascript 运行

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    userInfo: null
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    // 同步/异步直接写,无需mutation
    increment(payload = 1) {
      this.count += payload
    },
    async fetchUserInfo() {
      const res = await fetch('/api/user')
      const user = await res.json()
      this.userInfo = user
    }
  }
})

// 组件中使用(<script setup>)
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const counterStore = useCounterStore()
// 解构保持响应式(必须用storeToRefs)
const { count, doubleCount, userInfo } = storeToRefs(counterStore)

// 直接调用action
const addCount = () => counterStore.increment(2)
const getUserInfo = () => counterStore.fetchUserInfo()
</script>

5、parent/parent / children:父子直接访问(不推荐)


12. 插槽 Slot(默认 / 具名 / 作用域)

插槽:父组件向子组件传递 HTML 结构

12.1 默认插槽

<!-- 子组件 -->
<slot>默认内容</slot>
<!-- 父组件 -->
<Child>我是插入的内容</Child>

12.2 具名插槽(多个插槽)

Vue2

<!-- 子组件 -->
<slot name="header"></slot>
<!-- 父组件 -->
<template slot="header">头部内容</template>

Vue3(v-slot 简写 #)

<!-- 子组件 -->
<slot name="header"></slot>
<!-- 父组件 -->
<template #header>头部内容</template>

12.3 作用域插槽(子传数据给插槽)

<!-- 子组件 -->
<slot :user="user"></slot>
<script setup>
const user = reactive({ name: '插槽数据' })
</script>

<!-- 父组件 -->
<template #default="scope">
  {{ scope.user.name }}
</template>

13. 自定义指令 Directive

13.1 Vue2 自定义指令

javascript 运行

// 全局指令
Vue.directive('focus', {
  inserted(el) { el.focus() }
})

// 局部指令
export default {
  directives: {
    focus: { inserted(el) { el.focus() } }
  }
}

13.2 Vue3 自定义指令

javascript 运行

// 全局指令
app.directive('focus', {
  mounted(el) { el.focus() }
})

// 局部指令
<script setup>
const vFocus = { mounted: (el) => el.focus() }
</script>
<template> <input v-focus /> </template>

14. 过滤器 Filter(Vue2 有 / Vue3 废弃)

Vue2 过滤器

<template>
  <p>{{ msg | reverse }}</p>
</template>
<script>
export default {
  data() { return { msg: 'abc' } },
  filters: {
    reverse(val) { return val.split('').reverse().join('') }
  }
}
</script>

Vue3 替代方案

计算属性函数替代过滤器

<script setup>
import { ref, computed } from 'vue'
const msg = ref('abc')
const reverseMsg = computed(() => msg.value.split('').reverse().join(''))
</script>

15. 混入 Mixin(Vue2 有 / Vue3 废弃)

Vue2 用于复用代码,Vue3 用组合式函数替代

// mixin.js
export default {
  data() { return { mixinMsg: 'mixin数据' } },
  methods: { mixinFun() { console.log('mixin方法') } }
}

// 组件使用
import myMixin from './mixin.js'
export default { mixins: [myMixin] }

16. 路由 Vue-Router(完整配置 + 使用)

16.1 Vue2 + Vue-Router@3

javascript 运行

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)

const routes = [
  { path: '/', component: Home }
]
const router = new VueRouter({ routes })
export default router

// main.js 引入
import router from './router'
new Vue({ router }).$mount('#app')

16.2 Vue3 + Vue-Router@4

javascript 运行

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
  { path: '/', component: Home }
]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router

// 组件使用
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter() // 跳转
const route = useRoute() // 获取参数
// 跳转
const goHome = () => router.push('/')
</script>

17. Vue3 新增高级 API

17.1 Teleport 传送门

将组件渲染到指定 DOM,常用于弹窗

<teleport to="body">
  <div class="modal">弹窗内容</div>
</teleport>

17.2 Suspense 异步组件

<Suspense>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <div>加载中...</div>
  </template>
</Suspense>

17.3 defineComponent 类型推导

<script setup>
import { defineComponent } from 'vue'
const MyComponent = defineComponent({/*...*/})
</script>

18. Vue2 废弃 API 汇总

  1. $on / $off / $once(EventBus 废弃)
  2. filter 过滤器
  3. mixin 混入
  4. $children / $destroy
  5. 旧版插槽语法 slot / slot-scope
  6. .sync 修饰符

19. 新手常见问题与注意事项

  1. Vue3 ref 修改变量必须加 .value,模板中不用
  2. v-for 必须加唯一 key,不要用 index
  3. v-if 和 v-for 不要同标签使用
  4. Vue2 对象 / 数组修改用 $set,Vue3 直接修改
  5. 计算属性有缓存,methods 无缓存
  6. 组件名要大驼峰,避免和 HTML 标签冲突

文末总结

  1. Vue2 以选项式 API为主,适合小型项目,响应式有缺陷;
  2. Vue3 以组合式 API为主,适合中大型项目,响应式完美,TS 友好;
  3. 新手优先学 Vue3 + Vite + Pinia,这是未来主流技术栈。
❌
❌