第一篇:变量声明、解构赋值与Symbol
一、let 与 const 命令
ES6 新增了 let 和 const 命令,用于声明变量,它们与传统的 var 有本质区别。
1. 块级作用域
let 声明的变量只在它所在的代码块内有效:
javascript
{
let a = 10;
var b = 1;
}
console.log(b); // 1
console.log(a); // ReferenceError: a is not defined
2. 不存在变量提升
var 存在变量提升,而 let 必须先声明后使用:
javascript
// var 的情况
console.log(foo); // undefined
var foo = 2;
// let 的情况
console.log(bar); // ReferenceError
let bar = 2;
3. 暂时性死区(TDZ)
只要块级作用域内存在 let 或 const 命令,它所声明的变量就"绑定"在这个区域,不受外部影响。在声明之前使用该变量会报错:
javascript
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
4. 不允许重复声明
let 不允许在同一个作用域内重复声明同一个变量:
javascript
function func() {
let a = 10;
var a = 1; // 报错
}
function func() {
let a = 10;
let a = 1; // 报错
}
5. const 命令
const 用于声明常量,一旦声明其值就不能改变:
javascript
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable
// const 声明时必须立即赋值
const foo; // SyntaxError
注意 :const 实际上保证的是变量指向的内存地址不变。对于复合类型数据(如对象、数组),变量指向的地址不变,但内容可以修改:
javascript
const obj = { name: 'ES6' };
obj.name = 'JavaScript'; // 允许
obj = {}; // 报错
6. 块级作用域的实际应用
用 let 替代闭包解决循环变量问题:
javascript
// ES5 方式:使用闭包
var callbacks = [];
for (var i = 0; i < 5; i++) {
(function(i) {
callbacks[i] = function() { return i; };
})(i);
}
// ES6 方式:使用 let
var callbacks = [];
for (let i = 0; i < 5; i++) {
callbacks[i] = function() { return i; };
}
console.log(callbacks[0]()); // 0
二、解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
1. 数组解构赋值
数组的元素是按次序排列的,变量的取值由它的位置决定:
javascript
// 基本用法
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 嵌套数组解构
let [a, [[b], c]] = [1, [[2], 3]];
console.log(a, b, c); // 1 2 3
// 不完全解构
let [a, , c] = [1, 2, 3];
console.log(a, c); // 1 3
// 剩余运算符(rest)
let [head, ...tail] = [1, 2, 3, 4];
console.log(head); // 1
console.log(tail); // [2, 3, 4]
默认值 :当数组成员严格等于 undefined 时,默认值生效:
javascript
let [a = 1, b = 2] = [undefined, 3];
console.log(a, b); // 1 3
2. 对象解构赋值
对象的属性没有次序,变量必须与属性同名才能取到正确的值:
javascript
// 基本用法
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
console.log(foo, bar); // 'aaa' 'bbb'
// 变量名与属性名不同
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // 'aaa'
// 嵌套对象解构
let obj = { p: ['hello', { y: 'world' }] };
let { p: [x, { y }] } = obj;
console.log(x, y); // 'hello' 'world'
// 默认值
let { x = 3 } = { x: undefined };
console.log(x); // 3
3. 字符串解构赋值
字符串也可以解构赋值:
javascript
let [a, b, c] = 'hello';
console.log(a, b, c); // 'h' 'e' 'l'
4. 解构赋值的实际用途
(1)交换变量的值:
javascript
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
(2)从函数返回多个值:
javascript
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
console.log(a, b, c); // 1 2 3
function example2() {
return { foo: 1, bar: 2 };
}
let { foo, bar } = example2();
console.log(foo, bar); // 1 2
(3)函数参数的定义:
javascript
function foo({ x, y }) {
console.log(x, y);
}
foo({ x: 1, y: 2 }); // 1 2
三、Symbol
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 的第七种数据类型。
1. 基本用法
javascript
let sym1 = Symbol();
let sym2 = Symbol('foo');
let sym3 = Symbol('foo');
console.log(sym2 === sym3); // false
Symbol() 函数每次都会创建一个新的 Symbol,即使描述相同也不相等。
2. Symbol 作为对象属性名
Symbol 的主要用途是作为对象的属性键,确保属性的唯一性,防止属性冲突:
javascript
let mySymbol = Symbol('myKey');
let obj = {
[mySymbol]: 'Hello World'
};
console.log(obj[mySymbol]); // 'Hello World'
3. 创建私有属性
利用 Symbol 的唯一性可以模拟私有属性:
javascript
(function() {
let key = Symbol('key');
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype.doStuff = function() {
return this[key];
};
window.MyClass = MyClass;
})();
let obj = new MyClass('secret');
console.log(obj.key); // undefined(无法访问)
console.log(obj.doStuff()); // 'secret'
4. Symbol.for 与 Symbol.keyFor
Symbol.for() 方法会根据给定的键从全局 Symbol 注册表中查找对应的 Symbol,如果找到则返回,否则新建一个:
javascript
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2); // true
let s3 = Symbol('bar');
let s4 = Symbol('bar');
console.log(s3 === s4); // false
5. 内置 Symbol 值
ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。例如 Symbol.iterator:
javascript
let obj = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (let x of obj) {
console.log(x); // 1 2 3
}
第二篇:数据结构、元编程与内置对象扩展
四、Map 与 Set
ES6 新增了 Map 和 Set 两种数据结构。
1. Set
Set 是一种集合,存储一组唯一的值,不允许重复。
javascript
// 创建 Set
const set = new Set();
set.add(1);
set.add(2);
set.add(3);
set.add(1); // 重复值不会被添加
console.log(set.size); // 3
// 使用数组初始化
const set2 = new Set([1, 2, 3, 3, 4]);
console.log(set2); // Set {1, 2, 3, 4}
// 常用方法
set.delete(2);
console.log(set.has(1)); // true
console.log(set.has(2)); // false
// 遍历
set.forEach(value => console.log(value)); // 1 3
// 数组去重
const arr = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4, 5]
2. Map
Map 是一种键值对的集合,键可以是任意类型的值。
javascript
// 创建 Map
const map = new Map();
// 添加键值对
map.set('name', 'Alice');
map.set('age', 30);
map.set({}, 'object key');
// 获取值
console.log(map.get('name')); // 'Alice'
console.log(map.get('age')); // 30
// 修改值
map.set('age', 31);
// 检查键是否存在
console.log(map.has('name')); // true
// 删除键值对
map.delete('age');
// 遍历
map.forEach((value, key) => console.log(`${key}: ${value}`));
// 使用数组初始化
const map2 = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
3. Set 与 Map 的区别
| 特性 | Set | Map |
|---|---|---|
| 存储方式 | 唯一值的集合 | 键值对的集合 |
| 键的类型 | 值本身 | 任意类型 |
| 查找方式 | has() | get() |
| 适用场景 | 存储唯一值、去重 | 存储键值映射关系 |
五、Reflect 与 Proxy
Proxy 和 Reflect 是 ES6 中用于元编程的核心特性。
1. Proxy
Proxy 用于修改某些操作的默认行为,可以理解成在目标对象前架设一层"拦截层",外界对该对象的访问都必须先通过这层拦截。
javascript
let obj = {};
let proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`读取属性: ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置属性: ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
proxy.count = 1; // 设置属性: count = 1
console.log(proxy.count); // 读取属性: count
2. Proxy 支持的拦截操作
get(target, propKey, receiver):拦截对象属性的读取set(target, propKey, value, receiver):拦截对象属性的设置has(target, propKey):拦截propKey in proxy操作deleteProperty(target, propKey):拦截delete proxy[propKey]操作ownKeys(target):拦截Object.keys(proxy)等操作
3. 实际应用:数据验证
javascript
let validator = {
set(target, key, value) {
if (key === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 150) {
throw new TypeError('年龄必须是 0 到 150 之间的整数');
}
}
target[key] = value;
return true;
}
};
let person = new Proxy({}, validator);
person.age = 25; // 成功
// person.age = -1; // TypeError
4. Reflect
Reflect 是一个内置对象,提供与 Proxy handler 方法一一对应的静态方法。
javascript
// 传统方式 vs Reflect 方式
// 读取属性
let obj = { name: 'ES6' };
console.log(obj.name); // 传统
console.log(Reflect.get(obj, 'name')); // Reflect
// 设置属性
obj.age = 10;
Reflect.set(obj, 'age', 10);
// 判断属性是否存在
'name' in obj;
Reflect.has(obj, 'name');
// 删除属性
delete obj.age;
Reflect.deleteProperty(obj, 'age');
六、字符串的扩展
1. 模板字符串
javascript
let name = 'World';
let greeting = `Hello, ${name}!`;
console.log(greeting); // 'Hello, World!'
// 多行字符串
let multiline = `
第一行
第二行
第三行
`;
2. 新增的字符串方法
javascript
let str = 'Hello World';
// includes():是否包含子串
console.log(str.includes('World')); // true
console.log(str.includes('world')); // false
// startsWith():是否以某子串开头
console.log(str.startsWith('Hello')); // true
// endsWith():是否以某子串结尾
console.log(str.endsWith('World')); // true
// repeat():重复字符串
console.log('abc'.repeat(3)); // 'abcabcabc'
// trimStart() / trimEnd():去除首尾空格
let str2 = ' Hello ';
console.log(str2.trimStart()); // 'Hello '
console.log(str2.trimEnd()); // ' Hello'
七、数值的扩展
1. 二进制和八进制表示法
javascript
// 二进制:0b 前缀
console.log(0b1111); // 15
// 八进制:0o 前缀
console.log(0o17); // 15
2. Number 对象新增方法
javascript
// Number.isFinite():检查数值是否有限
console.log(Number.isFinite(15)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite('15')); // false(非数值返回 false)
// Number.isNaN():检查是否为 NaN
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('NaN')); // false
// Number.isInteger():检查是否为整数
console.log(Number.isInteger(25)); // true
console.log(Number.isInteger(25.0)); // true
console.log(Number.isInteger(25.1)); // false
// Number.parseInt() 和 Number.parseFloat()
console.log(Number.parseInt('12.34')); // 12
console.log(Number.parseFloat('12.34')); // 12.34
3. 安全整数与精度常量
javascript
// 安全整数范围
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
// 浮点数精度误差常量
console.log(Number.EPSILON);
function withinErrorMargin(left, right) {
return Math.abs(left - right) < Number.EPSILON;
}
console.log(withinErrorMargin(0.1 + 0.2, 0.3)); // true
八、对象的扩展
1. 属性的简洁表示法
javascript
let name = 'ES6';
let version = 2015;
// ES5 写法
let obj1 = { name: name, version: version };
// ES6 简洁写法
let obj2 = { name, version };
console.log(obj2); // { name: 'ES6', version: 2015 }
// 方法简写
let obj3 = {
sayHello() {
console.log('Hello');
}
};
2. 属性名表达式
javascript
let key = 'dynamic';
let obj = {
[key]: 'value',
['a' + 'b']: 123
};
console.log(obj.dynamic); // 'value'
console.log(obj.ab); // 123
3. 对象新增方法
javascript
// Object.is():比较两个值是否严格相等
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false
console.log(Object.is(+0, -0)); // false
console.log(+0 === -0); // true
// Object.assign():合并对象
let target = { a: 1 };
let source1 = { b: 2 };
let source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 2, c: 3 }
// Object.keys()、Object.values()、Object.entries()
let obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // ['a', 'b', 'c']
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.entries(obj)); // [['a', 1], ['b', 2], ['c', 3]]
九、数组的扩展
1. 扩展运算符(Spread)
javascript
// 合并数组
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4]
// 求最大值
let numbers = [5, 10, 3, 8];
console.log(Math.max(...numbers)); // 10
// 复制数组
let copy = [...numbers];
console.log(copy); // [5, 10, 3, 8]
2. Array.from() 和 Array.of()
javascript
// Array.from():将类数组对象转为数组
let arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
let arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b', 'c']
// 第二个参数:映射函数
let doubled = Array.from([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]
// Array.of():将一组值转为数组
console.log(Array.of(3)); // [3]
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
3. 数组实例新增方法
javascript
let arr = [1, 2, 3, 4, 5];
// find():查找第一个符合条件的元素
console.log(arr.find(n => n > 3)); // 4
// findIndex():查找第一个符合条件的元素的索引
console.log(arr.findIndex(n => n > 3)); // 3
// fill():填充数组
console.log([1, 2, 3].fill(0)); // [0, 0, 0]
console.log([1, 2, 3, 4, 5].fill(0, 1, 3)); // [1, 0, 0, 4, 5]
// includes():是否包含某元素
console.log([1, 2, 3].includes(2)); // true
console.log([1, 2, 3].includes(4)); // false
// entries()、keys()、values()
for (let index of ['a', 'b'].keys()) {
console.log(index); // 0 1
}
for (let value of ['a', 'b'].values()) {
console.log(value); // 'a' 'b'
}
for (let [index, value] of ['a', 'b'].entries()) {
console.log(index, value); // 0 'a' 1 'b'
}
第三篇:函数、类、模块与异步编程
十、函数的扩展
1. 函数参数的默认值
javascript
// 基本用法
function greet(name = 'World') {
console.log(`Hello, ${name}!`);
}
greet(); // 'Hello, World!'
greet('ES6'); // 'Hello, ES6!'
// 解构赋值 + 默认值
function foo({ x = 0, y = 0 } = {}) {
console.log(x, y);
}
foo(); // 0 0
foo({ x: 1 }); // 1 0
2. rest 参数
rest 参数用于获取函数的多余参数,代替 arguments 对象:
javascript
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// rest 参数必须是最后一个参数
function log(prefix, ...messages) {
messages.forEach(msg => console.log(`${prefix}: ${msg}`));
}
log('INFO', '启动', '加载', '完成');
// INFO: 启动
// INFO: 加载
// INFO: 完成
3. 箭头函数
javascript
// 基本语法
let add = (a, b) => a + b;
// 单参数可省略括号
let square = x => x * x;
// 无参数需要括号
let hello = () => 'Hello';
// 返回对象时需要加括号
let getObj = () => ({ name: 'ES6' });
// 箭头函数中的 this 指向定义时所在的作用域
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // this 指向 Timer 实例
}, 1000);
}
十一、Class 类
ES6 的 class 可以看作语法糖,让 JavaScript 的原型继承更加清晰。
1. 类的定义
javascript
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
}
let p = new Person('Alice', 25);
p.sayHello(); // "Hello, I'm Alice"
let p2 = Person.create('Bob', 30);
2. 类的继承
javascript
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 必须调用 super()
this.grade = grade;
}
// 重写父类方法
sayHello() {
console.log(`Hello, I'm ${this.name}, grade ${this.grade}`);
}
// 子类独有方法
study() {
console.log(`${this.name} is studying`);
}
}
let s = new Student('Charlie', 18, 'A');
s.sayHello(); // "Hello, I'm Charlie, grade A"
s.study(); // "Charlie is studying"
十二、模块(Module)
ES6 模块使用 export 和 import 关键字实现模块化。
1. 导出(export)
javascript
// module.js
// 分别导出
export const name = 'ES6';
export function greet() {
return 'Hello';
}
export class Calculator {
add(a, b) { return a + b; }
}
// 统一导出
const version = 2015;
const author = 'TC39';
export { version, author };
// 默认导出
export default function() {
console.log('Default export');
}
2. 导入(import)
javascript
// 导入具名导出
import { name, greet, Calculator } from './module.js';
// 导入默认导出
import defaultFunc from './module.js';
// 重命名
import { name as projectName } from './module.js';
// 整体导入
import * as myModule from './module.js';
console.log(myModule.name);
// 混合导入
import defaultFunc, { name, greet } from './module.js';
十三、Promise 对象
Promise 是异步编程的解决方案,用来避免回调地狱。
1. 基本用法
javascript
let promise = new Promise(function(resolve, reject) {
// 异步操作
setTimeout(() => {
let success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
promise.then(
value => console.log(value), // 成功回调
error => console.log(error) // 失败回调
);
2. Promise 的链式调用
javascript
function asyncTask(value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value * 2), 1000);
});
}
asyncTask(5)
.then(result => {
console.log(result); // 10
return asyncTask(result);
})
.then(result => {
console.log(result); // 20
return asyncTask(result);
})
.then(result => {
console.log(result); // 40
})
.catch(error => {
console.error('Error:', error);
});
3. Promise 的静态方法
javascript
// Promise.all():所有 Promise 都成功才成功
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [1, 2, 3]
});
// Promise.race():返回最先完成的 Promise
Promise.race([
new Promise(resolve => setTimeout(() => resolve('first'), 1000)),
new Promise(resolve => setTimeout(() => resolve('second'), 2000))
]).then(value => {
console.log(value); // 'first'
});
// Promise.resolve() 和 Promise.reject()
Promise.resolve('success').then(console.log);
Promise.reject('error').catch(console.log);
十四、Generator 函数
Generator 函数可以暂停执行,通过 yield 关键字交出执行权。
1. 基本用法
javascript
function* generatorFunc() {
yield '第一步';
yield '第二步';
return '结束';
}
let g = generatorFunc();
console.log(g.next()); // { value: '第一步', done: false }
console.log(g.next()); // { value: '第二步', done: false }
console.log(g.next()); // { value: '结束', done: true }
2. 传递参数
javascript
function* gen() {
let x = yield '请输入 x';
let y = yield '请输入 y';
return x + y;
}
let g = gen();
console.log(g.next()); // { value: '请输入 x', done: false }
console.log(g.next(5)); // { value: '请输入 y', done: false }
console.log(g.next(3)); // { value: 8, done: true }
十五、async 函数
async 函数是 Generator 函数的语法糖,让异步代码写起来像同步代码。
1. 基本用法
javascript
async function asyncFunc() {
let result = await new Promise((resolve) => {
setTimeout(() => resolve('完成'), 1000);
});
console.log(result);
return '函数返回';
}
asyncFunc().then(value => console.log(value));
// 1秒后输出 '完成'
// 然后输出 '函数返回'
2. 错误处理
javascript
async function fetchData() {
try {
let data = await new Promise((resolve, reject) => {
setTimeout(() => reject('请求失败'), 1000);
});
console.log(data);
} catch (error) {
console.log('捕获到错误:', error);
}
}
fetchData(); // '捕获到错误: 请求失败'
3. 多个异步操作
javascript
async function process() {
// 串行执行
let result1 = await asyncTask(1);
let result2 = await asyncTask(2);
// 并行执行
let [r1, r2] = await Promise.all([asyncTask(3), asyncTask(4)]);
return [result1, result2, r1, r2];
}
总结
本三篇教程涵盖了 ES6 的以下核心内容:
| 篇次 | 涵盖内容 |
|---|---|
| 第一篇 | let/const、解构赋值、Symbol |
| 第二篇 | Map/Set、Reflect/Proxy、字符串扩展、数值扩展、对象扩展、数组扩展 |
| 第三篇 | 函数扩展(箭头函数、默认参数、rest参数)、Class类、模块、Promise、Generator、async |
ES6(ECMAScript 2015)是 JavaScript 语言的重大更新,这些新特性极大地提升了 JavaScript 的开发效率和代码可读性,是现代前端开发的必备知识。建议您结合示例代码动手实践,加深理解。