ES6 核心语法
let 和 const 声明变量
ES6 引入了 let 和 const 来替代 var,解决了 var 的诸多问题。
var 的历史问题
// 问题1:变量提升(hoisting)—— 声明被提升到作用域顶部
console.log(x); // undefined(不会报错!)
var x = 10;
// 问题2:没有块级作用域 —— 花括号无法限制 var
if (true) {
var y = 20;
}
console.log(y); // 20(在块外部仍可访问!)
// 问题3:可以重复声明
var z = 10;
var z = 20; // 不会报错!
console.log(z); // 20
// 问题4:循环中的变量泄漏
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(全部引用同一个 i,循环结束后 i = 3)
let — 块级作用域变量
// let 有块级作用域
if (true) {
let a = 10;
console.log(a); // 10
}
// console.log(a); // ReferenceError: a is not defined
// 不能重复声明
let b = 10;
// let b = 20; // SyntaxError: Identifier 'b' has already been declared
// 解决循环中的闭包问题
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2(每次迭代都是新的 i)
// let 可以先声明后赋值
let c;
c = 30;
const — 声明常量
const 用于声明一个只读的常量,一旦声明就不能改变指向。
// 定义常量
const PI = 3.14;
// PI = 3; // TypeError: Assignment to constant variable
// JS 中常量必须在定义时初始化,以下写法会报错
// const x; // SyntaxError: Missing initializer in const declaration
// 注意:const 保证的是变量指向的"内存地址"不变,而非值不变
const student = {
id: 10,
name: 'zs',
age: 10,
addr: 'qd'
};
// student = {}; // 报错:不能指向新的对象
student.name = 'ls'; // 允许:对象内部的属性可以修改
student.age = 20; // 允许
// 对于数组也是同理
const arr = [1, 2, 3];
// arr = [4, 5, 6]; // 报错:不能指向新数组
arr.push(4); // 允许:修改数组内容
arr[0] = 99; // 允许
let vs const vs var 对比
| 特性 | var |
let |
const |
|---|---|---|---|
| 块级作用域 | ❌ 没有 | ✅ 有 | ✅ 有 |
| 变量提升 | ✅ 提升(undefined) | ⚠️ 提升但有”暂时性死区” | ⚠️ 提升但有”暂时性死区” |
| 重复声明 | ✅ 允许 | ❌ 不允许 | ❌ 不允许 |
| 必须初始化 | ❌ 不需要 | ❌ 不需要 | ✅ 必须 |
| 重新赋值 | ✅ 可以 | ✅ 可以 | ❌ 不可以 |
| 修改对象属性 | ✅ 可以 | ✅ 可以 | ✅ 可以 |
最佳实践:默认使用
const,只有确定变量需要重新赋值时才用let。完全弃用var。
解构赋值(Destructuring)
ES6 允许按照一定模式从数组或对象中提取值,赋给变量。这是 Vue3 中使用最频繁的 ES6 特性之一。
数组解构
let arr = [1, 2, 3];
// 按位置一一对应提取
let [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
// 跳过某些元素
let [first, , third] = arr;
console.log(first, third); // 1 3
// 设置默认值(当对应位置为 undefined 时生效)
let [x, y, z, w = 99] = [10, 20, 30];
console.log(x, y, z, w); // 10 20 30 99
// 剩余运算符(rest)— 收集剩余元素
let [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 交换两个变量的值(无需临时变量)
let p = 1, q = 2;
[p, q] = [q, p];
console.log(p, q); // 2 1
对象解构
let student = {
id: 10,
name: 'zs',
age: 10,
addr: 'qd'
};
// 变量名必须与属性名一致(这是与数组解构的最大区别)
let { name, age } = student;
console.log(name, age); // 'zs' 10
// 重命名 —— 用冒号取别名
let { name: studentName, age: studentAge } = student;
console.log(studentName, studentAge); // 'zs' 10
// 设置默认值
let { name, score = 60 } = student;
console.log(score); // 60(student 没有 score 属性,使用默认值)
// 嵌套解构
let person = {
name: 'tom',
address: {
city: 'beijing',
street: '长安街'
}
};
let { address: { city } } = person;
console.log(city); // 'beijing'
函数参数解构
// 对象参数解构 — 最常用的模式
function printStudent({ name, age }) {
console.log(`${name}, ${age}`);
}
printStudent(student); // 'zs, 10'
// 带默认值的参数解构
function createUser({ name = '匿名', age = 18, role = 'user' } = {}) {
console.log(`${name}, ${age}岁, 角色: ${role}`);
}
createUser({ name: '张三' }); // '张三, 18岁, 角色: user'
createUser(); // '匿名, 18岁, 角色: user'
// 数组参数解构
function sum([a, b, c]) {
return a + b + c;
}
console.log(sum([1, 2, 3])); // 6
在 Vue3 中的实际应用
// Vue3 中最常见的解构模式:从 Vue 全局对象中提取需要的 API
const { createApp, ref, reactive, computed, watch, onMounted } = Vue;
// 从 props 解构(注意:直接解构会丢失响应性,需要用 toRefs)
const { name, age } = defineProps(['name', 'age']);
要点:
- 数组解构看位置,对象解构看属性名
- 解构可以设置默认值:
let { name = 'unknown' } = obj - 对象解构可以用
:重命名:let { name: n } = obj - 函数参数解构是 Vue 组件开发中最常用的模式之一
模板字符串(Template Literals)
用反引号 ` “ 包裹的字符串,支持变量插值和多行文本,极大提升了字符串拼接的可读性。
基本用法
let name = '张三';
let age = 20;
// ES5 传统拼接 — 用 + 号串联,可读性差,容易出错
let str = name + "的年龄是" + age + "岁";
console.log(str); // 张三的年龄是20岁
// ES6 模板字符串 — ${} 中填写变量
str = `${name}的年龄是${age}岁`;
console.log(str); // 张三的年龄是20岁
高级用法
// ${} 中可以是任意 JavaScript 表达式
str = `${name}的年龄是${age + 1}岁`;
console.log(str); // 张三的年龄是21岁
// 三元表达式
str = `${name}${age >= 18 ? '已成年' : '未成年'}`;
// ${} 中可以调用函数
function test() {
return "world";
}
str = `hello ${test()}`;
console.log(str); // hello world
// 支持多行字符串(保留换行和缩进)
str = `hello
world`;
console.log(str);
// 输出(保留格式):
// hello
// world
// 嵌套模板字符串
let user = { name: '张三', scores: [80, 90, 85] };
let report = `${user.name}的成绩:
${user.scores.map((s, i) => ` 第${i + 1}次:${s}分`).join('\n')}`;
标签模板(Tagged Templates)
// 标签模板 — 函数处理模板字符串
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return `${result}${str}<strong>${values[i] || ''}</strong>`;
}, '');
}
let result = highlight`${name}今年${age}岁`;
// 输出:<strong>张三</strong>今年<strong>20</strong>岁
要点:
- 用反引号 `
`包裹,${}` 插值 ${}内可以放任意 JavaScript 表达式(变量、运算、函数调用、三元运算等)- 天然支持多行,无需
\n拼接 - 不要再使用
+拼接字符串,模板字符串在可读性和维护性上都更好
声明对象简写
当对象属性名与变量名相同时,可以省略 : value。
let name = "tom";
let age = 10;
let address = "qd";
// ES5 写法 — 属性名和变量名重复书写
let student = {
name: name,
age: age,
address: address
};
console.log(student); // { name: 'tom', age: 10, address: 'qd' }
// ES6 简写 — 属性名和变量名相同,只写一次即可
let student1 = {
name,
age,
address
};
console.log(student1); // { name: 'tom', age: 10, address: 'qd' }
// 两者完全等价
动态属性名
// ES6 支持用表达式作为属性名(计算属性名)
let key = 'score';
let student2 = {
name: 'tom',
[key]: 90, // 属性名为变量 key 的值 'score'
['avg' + key]: 85 // 属性名为 'avgscore'
};
console.log(student2); // { name: 'tom', score: 90, avgscore: 85 }
要点:
- 属性名和变量名一致时,只写一次即可(
{ name }={ name: name }) - 配合解构使用非常方便,Vue3 中
return { count, text }就是对象简写 - 动态属性名用
[表达式]包裹
定义方法简写
对象中的方法可以省略 function 关键字和冒号。
let name = "tom";
let age = 10;
let address = "qd";
let student = {
// 属性简写
name,
age,
address,
// ES5 写法
// study: function() {
// console.log("学习..");
// }
// ES6 方法简写 — 省略 function 关键字
study() {
console.log("学习..");
},
// 带参数的方法
greet(greeting) {
console.log(`${greeting}, 我是${this.name}`);
},
// 这也是一种简写(箭头函数作为属性值)
sayHi: () => console.log('hi')
};
student.study(); // 学习..
student.greet('你好'); // 你好, 我是tom
要点:
method() {}是method: function() {}的简写- 简写方法可以使用
this(指向所属对象) - 箭头函数属性(
fn: () => {})中的this指向定义时的外层作用域,不是所属对象 - 在 Vue3 的
setup()中定义的方法通常不放在对象里,但理解这个语法有助于阅读 Vue 组件选项式 API 代码
对象扩展运算符(Spread Operator)
用 ... 展开对象或数组,实现浅拷贝和合并。这是 ES2018 引入的特性。
对象展开
let student = {
id: 10,
name: 'zs',
age: 8,
addr: 'qd'
};
// 浅拷贝对象 — 创建一个新对象,属性值和原对象相同
let student1 = { ...student };
console.log(student1); // { id: 10, name: 'zs', age: 8, addr: 'qd' }
// 验证是浅拷贝:修改原对象不影响副本(对于基本类型属性)
student.age = 15;
console.log(student); // { id: 10, name: 'zs', age: 15, addr: 'qd' }
console.log(student1); // { id: 10, name: 'zs', age: 8, addr: 'qd' }
// student1 不受影响,因为是独立的副本
// ⚠️ 浅拷贝的陷阱:嵌套对象共享引用
let obj = { a: 1, nested: { b: 2 } };
let copy = { ...obj };
copy.nested.b = 999;
console.log(obj.nested.b); // 999(原对象也被修改了!)
合并对象
let student2 = { id: 10, name: 'zs', age: 15, addr: 'qd' };
// 合并对象 — 后面的属性覆盖前面的同名属性
let student3 = { ...student1, ...student2 };
console.log(student3);
// 结果:{ id: 10, name: 'zs', age: 15, addr: 'qd' }
// age 最终为 15,因为 student2 在后面
// 覆盖特定属性
let updated = { ...student, age: 20, name: 'newName' };
// 等价于:先拷贝 student 的所有属性,再覆盖 age 和 name
数组展开
// 数组合并
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]
// 在任意位置插入
let withInsert = [0, ...arr1, 99, ...arr2];
console.log(withInsert); // [0, 1, 2, 3, 99, 4, 5, 6]
// 浅拷贝数组
let arrCopy = [...arr1];
// 数组去重后的展开
let unique = [...new Set([1, 2, 2, 3, 3, 4])];
console.log(unique); // [1, 2, 3, 4]
剩余运算符(Rest)— 收集剩余
// 对象解构中的 rest — 收集"剩下的"属性
let { id, name, ...rest } = student;
console.log(id); // 10
console.log(name); // 'zs'
console.log(rest); // { age: 15, addr: 'qd' }
// 函数参数中的 rest — 收集剩余参数为数组
function sum(multiplier, ...numbers) {
return numbers.reduce((total, n) => total + n, 0) * multiplier;
}
console.log(sum(2, 1, 2, 3, 4)); // 20
要点:
{ ...obj }是浅拷贝,嵌套对象仍然共享引用- 合并时后面的属性覆盖前面的(同名属性)
- 展开运算符和剩余运算符是同一个
...,但用途相反(展开 vs 收集) - 在 Vue3 中常用于不可变更新状态:
state = { ...state, newProp: value }
箭头函数(Arrow Function)
箭头函数是 ES6 最重要的特性之一,类似于 Java 的 Lambda 表达式,写法更简洁,并且不绑定自己的 this。
基本语法
// 定义函数
function test(a, fun) {
fun(a);
}
// ES5 回调写法
test(10, function(x) {
console.log(x);
});
// ES6 箭头函数写法 — 简洁优雅
test(10, x => console.log(x));
test(10, x => console.log(x * 2));
箭头函数的多种形式
// 1. 无参数 — 必须写空括号
let fn1 = () => console.log('hello');
// 2. 单参数 — 可以省略括号
let fn2 = x => x * 2;
// 等价于:let fn2 = (x) => { return x * 2; };
// 3. 多参数 — 必须用括号包裹
let fn3 = (a, b) => a + b;
// 4. 函数体多条语句 — 必须用花括号 + return
let fn4 = (a, b) => {
let sum = a + b;
return sum;
};
// 5. 返回对象字面量 — 必须用括号包裹对象
let fn5 = (name, age) => ({ name, age });
// 如果写 (name, age) => { name, age },花括号会被当作函数体!
// 6. 立即执行的箭头函数(IIFE)
((name) => {
console.log(`Hello ${name}`);
})('world');
箭头函数的 this 绑定
这是箭头函数和普通函数最重要的区别:
// 普通函数:this 取决于调用方式(运行时绑定)
let obj1 = {
name: 'obj1',
sayName: function() {
console.log(this.name);
}
};
obj1.sayName(); // 'obj1'(this 指向 obj1)
// 箭头函数:this 取决于定义时的外层作用域(词法绑定)
let obj2 = {
name: 'obj2',
sayName: () => {
console.log(this.name); // this 不是 obj2!
}
};
obj2.sayName(); // undefined(this 指向全局对象/window)
// 实际应用场景 — 解决回调中的 this 丢失问题
let person = {
name: '张三',
hobbies: ['读书', '游泳'],
showHobbies: function() {
// ES5 做法 — 需要保存 this
// let self = this;
// this.hobbies.forEach(function(h) {
// console.log(self.name + '喜欢' + h);
// });
// ES6 做法 — 箭头函数自动继承外层 this
this.hobbies.forEach(h => {
console.log(this.name + '喜欢' + h);
});
}
};
person.showHobbies();
// 张三喜欢读书
// 张三喜欢游泳
什么时候不能用箭头函数
// ❌ 不能用作构造函数
// let Person = (name) => { this.name = name; };
// let p = new Person('tom'); // TypeError
// ❌ 对象方法需要访问 this 时(用方法简写代替)
let obj = {
name: 'obj',
// wrong: () => console.log(this.name), // this 不是 obj
right() { console.log(this.name); } // 正确
};
// ❌ 需要动态 this 的场景(如事件处理中需要 event.target)
// button.addEventListener('click', () => { console.log(this); });
// 此时 this 不是 button 元素
this 绑定规则总结
| 函数类型 | this 指向 |
何时确定 |
|---|---|---|
普通函数 function f() {} |
调用者(动态) | 运行时 |
对象方法 { fn() {} } |
所属对象 | 运行时 |
箭头函数 () => {} |
定义时的外层作用域(词法) | 定义时 |
构造函数 new F() |
新创建的实例 | 运行时 |
要点:
- 箭头函数没有自己的
this,继承外层作用域的this(词法作用域) - 简洁,特别适合回调场景(
map、filter、forEach、事件处理等) - 不能用作构造函数(没有
prototype,不能new) - 没有
arguments对象(用 rest 参数替代) - Vue3 的
setup()中定义的方法,箭头函数和普通函数都可用(因为在setup闭包内)
函数默认参数
ES6 允许为函数参数设置默认值,简化了参数校验逻辑。
// ES5 做法 — 手动判断并赋默认值
function greet(name) {
name = name || '匿名用户'; // 有 bug:空字符串、0、false 也会被替换
console.log('你好,' + name);
}
// ES6 默认参数 — 只在参数为 undefined 时生效
function greet(name = '匿名用户', greeting = '你好') {
console.log(`${greeting},${name}`);
}
greet(); // 你好,匿名用户
greet('张三'); // 你好,张三
greet('张三', '早上好'); // 早上好,张三
greet(undefined, '嗨'); // 嗨,匿名用户(undefined 触发默认值)
// 默认值可以是表达式(惰性求值,每次调用时计算)
function getTimestamp(date = new Date()) {
return date.getTime();
}
// 默认值可以使用前面的参数
function createRect(width, height = width) {
return { width, height };
}
console.log(createRect(100)); // { width: 100, height: 100 }
// 配合解构使用
function createUser({ name = '匿名', age = 18, role = 'user' } = {}) {
console.log(`${name}, ${age}岁, 角色: ${role}`);
}