ES6 核心语法

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}`);
}
上一篇
下一篇