JavaScript(JS)作为一门轻量级的面向对象编程语言,一直在不断发展和完善。ES6(ECMAScript 2015)是JS的一个重要里程碑,引入了大量新特性和语法改进,极大地提升了开发效率和代码可读性。
ES6,也称为ECMAScript 2015,是JS语言的一次重大更新。它不仅修复了旧版本的许多问题,还引入了许多新特性,使得JS更加现代化和强大。
let
和const
是ES6引入的两个新的变量声明关键字,用于解决var
的变量提升问题。
let a = 10;
const b = 20;
箭头函数提供了一种更简洁的函数声明方式。
const sum = (x, y) => x + y;
console.log(sum(1, 2)); // 3
模板字符串可以简化字符串的拼接和处理。
const name = '张三';
const greeting = `你好,${name}!`;
console.log(greeting); // 你好,张三!
解构赋值可以简化从数组或对象中提取数据的操作。
const [x, y] = [1, 2];
const {a, b} = {a: 3, b: 4};
console.log(x, y, a, b); // 1 2 3 4
Promise用于处理异步操作,使代码更加清晰和易于管理。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
}).then(result => console.log(result)); // 成功
ES6引入了类的概念,使得面向对象编程更加直观。
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`你好,我是${this.name}`);
}
}
const p = new Person('李四');
p.sayHello(); // 你好,我是李四
理解ES6的特性不仅有助于提升代码质量,还能在实际项目中解决复杂问题。例如,使用Promise可以更好地处理异步请求,使用解构赋值可以简化数据处理逻辑。
新增的块级作用域声明方式。let 用于声明可变变量,const 用于声明常量。
允许从数组或对象中提取值并赋值给变量,简化代码书写。
新增了模板字符串,使用反引号(``)定义字符串,支持多行字符串和插值表达式。
常用的一些方法如下:
当基于 RegExp 构造函数生成正则对象,第一个参数是正则表达式时,第二个参数可以指定修饰符,且修饰符会被第二个参数覆盖。
u 修饰符用于处理 Unicode 字符,可以正确处理大于 \uFFFF 的 Unicode 字符。添加 u 修饰符后:
y 修饰符叫做"粘连"(sticky)修饰符,表示从剩余的第一个字符开始匹配。
y 修饰符的设计本意是,让头部匹配的标志^在全局匹配中都有效。
let s = 'aaa_aa_a';
let r1 = /a+/g; // 全局匹配
let r2 = /a+/y; // 粘连匹配
r1.exec(s); // ["aaa"]
r2.exec(s); // ["aaa"]
r1.exec(s); // ["aa"] // g修饰符会继续往后匹配
r2.exec(s); // null // y修饰符要求必须从剩余的第一个字符开始匹配,因为剩余的第一个字符是_,所以返回null
ES6 提供了二进制和八进制数值的新的写法:
0b
(或0B
)表示0o
(或0O
)表示
// 二进制
0b111110111 === 503 // true
// 八进制
0o767 === 503 // true
新增了两个方法来检测数值:
Number.isFinite()
用来检查一个数值是否为有限的Number.isNaN()
用来检查一个值是否为 NaN
Number.isFinite(15); // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite(NaN); // false
Number.isNaN(NaN); // true
Number.isNaN(15); // false
Number.isNaN('15'); // false
ES6 将全局方法parseInt()
和parseFloat()
移植到 Number 对象上:
// 等同于
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
用来判断一个数值是否为整数:
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false
表示一个极小的常量,它的值是 JavaScript 能够表示的最小精度:
Number.EPSILON // 2.220446049250313e-16
// 用于浮点数运算的误差检查
0.1 + 0.2 === 0.3 // false
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON // true
JavaScript 能够准确表示的整数范围在 -2^53 到 2^53 之间。ES6 引入了:
Number.MAX_SAFE_INTEGER
:最大安全整数Number.MIN_SAFE_INTEGER
:最小安全整数Number.isSafeInteger()
:判断一个整数是否在这个范围内
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_SAFE_INTEGER // -9007199254740991
Number.isSafeInteger(9007199254740991) // true
Number.isSafeInteger(9007199254740992) // false
ES6 在 Math 对象上新增了多个方法:
Math.trunc()
去除小数部分Math.sign()
判断一个数是正数、负数、还是零Math.cbrt()
计算立方根Math.hypot()
返回所有参数的平方和的平方根
Math.trunc(4.9) // 4
Math.sign(-5) // -1
Math.cbrt(27) // 3
Math.hypot(3, 4) // 5
引入了**
运算符,用来进行指数运算:
2 ** 3 // 8
2 ** 3 ** 2 // 512
let a = 2;
a **= 3; // 等同于 a = a ** 3
ES6 引入了箭头函数,使用=>
定义函数。箭头函数有以下特点:
// 普通函数
const fun1 = function(x) {
return x * 2;
}
// 箭头函数
const fun2 = x => x * 2;
const obj = {
data: [],
init() {
// 箭头函数中的this指向定义时的对象
document.addEventListener('click', () => {
this.data.push('clicked'); // this指向obj
});
}};
可以直接在参数定义时设置默认值:
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
// 解构赋值默认值
function fetch({url, method = 'GET'} = {}) {
console.log(method);
}
fetch({url: '/api'}) // GET
fetch() // GET
注意事项:
let count = 1;
function foo(value = count) {
console.log(value);
}
foo(); // 1
count = 2;
foo(); // 2 // 每次调用时都会重新获取count的值
// 更复杂的例子
function bar(func = () => Date.now()) {
console.log(func());
}
bar(); // 1709xxxxx1
bar(); // 1709xxxxx2 // 每次调用都会得到新的时间戳
rest 参数(形式为...变量名)用于获取函数的多余参数:
// 代替 arguments
function add(...numbers) {
return numbers.reduce((sum, num) => sum + num, 0);
}
add(2, 3, 4) // 9
// 结合解构使用
function push(array, ...items) {
items.forEach(item => {
array.push(item);
});
}
let arr = [];
push(arr, 1, 2, 3); // arr现在是[1, 2, 3]
rest 参数的特点:
扩展运算符(...)将一个数组转为用逗号分隔的参数序列:
console.log(...[1, 2, 3]) // 1 2 3
// 常见用途
// 1. 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
// 2. 合并数组
const arr3 = [...arr1, ...arr2];
// 3. 与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
// first: 1
// rest: [2, 3, 4, 5]
将类数组对象和可遍历对象转换为真正的数组:
// 转换类数组对象
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.from(arrayLike) // ['a', 'b', 'c']
// 转换可遍历对象
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
// 接受第二个参数,类似于数组的map方法
Array.from([1, 2, 3], x => x * 2) // [2, 4, 6]
将一组值转换为数组,弥补数组构造函Array()的不足:
Array.of(3) // [3]
Array.of(1, 2, 3) // [1, 2, 3]
// 对比Array()构造函数
Array(3) // [empty × 3]
Array(1, 2, 3) // [1, 2, 3]
在当前数组内部,指定位置的成员复制到其他位置:
// array.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
find()
用于找出第一个符合条件的数组成员,findIndex()
返回第一个符合条件的数组成员的位置:
const numbers = [1, 4, -5, 10];
numbers.find(n => n < 0) // -5
numbers.findIndex(n => n < 0) // 2
使用给定值,填充一个数组:
new Array(3).fill(7) // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
用于遍历数组,它们都返回一个遍历器对象:
const arr = ['a', 'b', 'c'];
for (let index of arr.keys()) {
console.log(index); // 0, 1, 2
}
for (let elem of arr.values()) {
console.log(elem); // 'a', 'b', 'c'
}
for (let [index, elem] of arr.entries()) {
console.log(index, elem); // 0 'a', 1 'b', 2 'c'
}
当属性名与变量名相同时,可以简写:
const name = 'Tom';
const age = 18;
const person = { name, age }; // 等同于 { name: name, age: age }
对象方法可以简写,省略function关键字:
const obj = {
sayHi() { // 等同于 sayHi: function()
console.log('Hi');
}
}
支持用表达式作为属性名:
const prop = 'foo';
const obj = {
[prop]: 'bar',
[`a${prop}`]: 'hello'
};
用于对象的合并,将源对象的所有可枚举属性复制到目标对象:
const target = { a: 1 };
const source = { b: 2 };
Object.assign(target, source); // { a: 1, b: 2 }
用于比较两个值是否严格相等:
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Symbol 是 ES6 引入的一种新的原始数据类型,表示独一无二的值。
// 创建 Symbol
const s1 = Symbol();
const s2 = Symbol('描述文字');
// Symbol 值是唯一的
Symbol() !== Symbol() // true
Symbol('foo') !== Symbol('foo') // true
// 作为对象属性
const obj = {
[Symbol('foo')]: 'foo value',
[Symbol('bar')]: 'bar value'
};
// Symbol.for() 会在全局注册表中搜索键为"foo"的 Symbol
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
s1 === s2 // true
// Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key
Symbol.keyFor(s1) // "foo"
ES6 提供了一些内置的 Symbol 值,指向语言内部使用的方法:
// Symbol.iterator
// 对象进行 for...of 循环时,会调用这个方法
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
iterator.next() // { value: 1, done: false }
// Symbol.hasInstance
// instanceof 运算符调用这个方法
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
[] instanceof MyArray // true
// Symbol.toPrimitive
// 对象转换为原始类型值时调用
const obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': return 123;
case 'string': return 'str';
case 'default': return 'default';
}
}
};
主要特点:
Set 是一个新的数据结构,类似于数组,但成员值都是唯一的:
// 基本用法
const s = new Set();
s.add(1).add(2).add(2); // Set(2) {1, 2}
// 接收数组初始化
const set = new Set([1, 2, 2, 3]); // Set(3) {1, 2, 3}
// 常用方法
set.has(1); // true
set.delete(2); // true
set.size; // 2
set.clear(); // 清空
// 数组去重
const arr = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
// 遍历方法
for (let item of set) {
console.log(item);
}
set.forEach((value, key) => console.log(value));
WeakSet 结构与 Set 类似,但有两个重要的区别:
const ws = new WeakSet();
const obj = {};
ws.add(obj);
ws.has(obj); // true
Map 是一种键值对的集合,类似于对象,但键的范围不限于字符串:
// 基本用法
const m = new Map();
const obj = {p: 'Hello'};
m.set(obj, 'content');
m.get(obj); // 'content'
// 接收数组初始化
const map = new Map([
['name', '张三'],
['age', 18]
]);
// 常用方法
map.has('name'); // true
map.delete('name'); // true
map.size; // 1
map.clear(); // 清空
// 遍历方法
for (let [key, value] of map) {
console.log(key, value);
}
map.forEach((value, key) => console.log(key, value));
WeakMap 结构与 Map 类似,但有两个重要的区别:
const wm = new WeakMap();
const key = {};
wm.set(key, 'value');
wm.get(key); // 'value'
主要特点:
Proxy 是一个强大的对象代理机制,它可以拦截并自定义对象的基本操作,比如属性查找、赋值、枚举、函数调用等。通过 Proxy,你可以在这些操作执行前后添加自定义行为。
// 基本用法
const target = {
name: '张三',
age: 18
};
const handler = {
// 拦截对象属性的读取
get(target, prop) {
console.log(`访问了${prop}属性`);
return target[prop];
},
// 拦截对象属性的设置
set(target, prop, value) {
console.log(`设置${prop}属性为${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
// 使用代理对象
proxy.name; // 输出:"访问了name属性" 返回:"张三"
proxy.age = 20; // 输出:"设置age属性为20"
const handler = {
// 拦截属性的读取
get(target, prop) {},
// 拦截属性的设置
set(target, prop, value) {},
// 拦截函数的调用
apply(target, thisArg, argumentsList) {},
// 拦截 new 操作
construct(target, args) {},
// 拦截 Object.defineProperty
defineProperty(target, prop, descriptor) {},
// 拦截属性的删除
deleteProperty(target, prop) {},
// 拦截获取原型
getPrototypeOf(target) {},
// 拦截设置原型
setPrototypeOf(target, proto) {}
};
Reflect 是一个内置的全局对象,它提供了一组统一的方法来操作对象。这些方法是对象操作的底层封装,与 Proxy 处理器的方法一一对应,使得代理对象的操作更加规范和可控。
// 基本用法
const obj = {
name: '张三'
};
// 读取属性
Reflect.get(obj, 'name'); // '张三'
// 设置属性
Reflect.set(obj, 'age', 18); // true
// 删除属性
Reflect.deleteProperty(obj, 'age'); // true
// 判断对象是否有某个属性
Reflect.has(obj, 'name');
const target = {
name: '张三',
age: 18
};
const handler = {
get(target, prop, receiver) {
console.log(`获取${prop}属性`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置${prop}属性`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy(target, handler);
主要用途:
Promise 是异步编程的一种解决方案,比传统的回调函数更加优雅。
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if(/* 成功 */) {
resolve('成功结果');
} else {
reject('失败原因');
}
}, 1000);
});
// 使用 Promise
promise
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log('无论成功失败都会执行');
});
Promise 有三种状态:
状态一旦改变,就不会再变。
// Promise.all():所有 Promise 都成功才成功
Promise.all([promise1, promise2])
.then(([result1, result2]) => {
console.log(result1, result2);
});
// Promise.race():返回最快的那个 Promise 的结果
Promise.race([promise1, promise2])
.then(result => {
console.log(result);
});
// 【ES2020】Promise.allSettled():等待所有 Promise 完成
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
// 【ES2021】Promise.any():任意一个 Promise 成功就返回
Promise.any([promise1, promise2])
.then(result => {
console.log('第一个成功的结果:', result);
});
// 封装 AJAX 请求
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject(new Error(`请求失败:${xhr.statusText}`));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.send();
});
}
// 使用 async/await 更优雅地处理
async function getData() {
try {
const result = await fetchData('api/data');
console.log(result);
} catch (error) {
console.error(error);
}
}
主要特点:
Iterator 是一种遍历机制,它就像一个指针,指向数据结构的某个位置,并且通过 next() 方法指向下一个位置,直到遍历完所有数据。
// Iterator 的基本实现
function makeIterator(array) {
let nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
// 使用迭代器
const it = makeIterator(['a', 'b']);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: undefined, done: true }
// Array
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
iter.next(); // { value: 'a', done: false }
// String
const str = "hello";
for (let char of str) {
console.log(char); // h,e,l,l,o
}
// Map
const map = new Map([['name', '张三'], ['age', 18]]);
for (let [key, value] of map) {
console.log(key, value);
}
// Set
const set = new Set(['a', 'b', 'c']);
for (let value of set) {
console.log(value);
}
for...of 循环可以自动遍历具有 Iterator 接口的数据结构:
// 遍历数组
const arr = ['red', 'green', 'blue'];
for (let value of arr) {
console.log(value);
}
// 与其他遍历方法的对比
const arr = ['a', 'b', 'c'];
// for...in 遍历索引
for (let index in arr) {
console.log(index); // "0", "1", "2"
}
// for...of 遍历值
for (let value of arr) {
console.log(value); // "a", "b", "c"
}
// forEach 无法中断
arr.forEach(value => {
console.log(value);
// 无法使用 break
});
class Collection {
constructor() {
this.items = ['a', 'b', 'c'];
}
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
return index < items.length ?
{value: items[index++], done: false} :
{value: undefined, done: true};
}
};
}
}
const collection = new Collection();
for (let value of collection) {
console.log(value); // 'a', 'b', 'c'
}
Generator 函数是一种特殊的函数,可以暂停执行和恢复执行。通过 yield 关键字,实现函数的分段执行。
// 定义 Generator 函数
function* numberGenerator() {
yield 1;
yield 2;
return 3;
}
// 使用 Generator
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
function* calculateNumbers() {
const result1 = yield 1 + 1; // result1 是下一次 next 传入的值
console.log('第一次计算结果:', result1);
const result2 = yield 2 + 2;
console.log('第二次计算结果:', result2);
}
const calc = calculateNumbers();
console.log(calc.next()); // { value: 2, done: false }
console.log(calc.next(100)); // 打印 "第一次计算结果: 100"
// { value: 4, done: false }
console.log(calc.next(200)); // 打印 "第二次计算结果: 200"
// { value: undefined, done: true }
// 1. 实现迭代器
function* createIterator(array) {
for(let item of array) {
yield item;
}
}
const iterator = createIterator(['a', 'b', 'c']);
console.log(iterator.next()); // { value: 'a', done: false }
// 2. 状态机
function* toggle() {
while(true) {
yield '开';
yield '关';
}}
const light = toggle();
console.log(light.next().value); // '开'
console.log(light.next().value); // '关'
console.log(light.next().value); // '开'
*
号yield
表达式next()
方法依次遍历async 函数是 Generator 函数的语法糖,使异步操作更简单直观。它返回一个 Promise 对象。
// 基础示例
async function getData() {
try {
const response = await fetch('api/data');
const data = await response.json();
return data;
} catch (error) {
console.log('错误:', error);
}}
// 调用方式
getData().then(data => {
console.log(data);
});
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function() {}
// 对象方法
const obj = {
async foo() {}
}
// 箭头函数
const foo = async () => {}
// Class 方法
class Storage {
async getData() {}
}
实际应用示例
// 1. 并行请求
async function getMultipleData() {
// Promise.all 并行执行多个请求
const [users, posts] = await Promise.all([
fetch('api/users').then(r => r.json()),
fetch('api/posts').then(r => r.json())
]);
return { users, posts };
}
// 2. 错误处理
async function fetchData() {
try {
const response = await fetch('api/data');
if (!response.ok) {
throw new Error('请求失败');
}
return await response.json();
} catch (error) {
console.error('出错了:', error);
// 可以返回默认值或重新抛出错误
return [];
}
}
// 3. 顺序请求
async function getOrderDetails() {
// 每个请求依赖前一个请求的结果
const user = await fetch('api/user').then(r => r.json());
const orders = await fetch(`api/orders/${user.id}`).then(r => r.json());
const details = await fetch(`api/details/${orders[0].id}`).then(r => r.json());
return details;
}
Class 是 ES6 引入的定义类的新方式,让对象原型的写法更加清晰、更像面向对象编程的语法。
class Person {
// 构造方法
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
sayHello() {
console.log(`你好,我是${this.name}`);
}
// getter
get info() {
return `${this.name}, ${this.age}岁`;
}
// setter
set age(value) {
this._age = value < 0 ? 0 : value;
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}}
// 使用类
const person = new Person('张三', 20);
person.sayHello(); // 你好,我是张三
console.log(person.info); // 张三, 20岁
class Student extends Person {
constructor(name, age, grade) {
// 调用父类构造函数
super(name, age);
this.grade = grade;
}
study() {
console.log(`${this.name}正在学习`);
}
// 重写父类方法
sayHello() {
super.sayHello();
console.log(`我是${this.grade}年级的学生`);
}
}
const student = new Student('小明', 15, '初三');
student.sayHello();
// 你好,我是小明
// 我是初三年级的学生
class Account {
// 私有属性(新提案)
#balance = 0;
// 私有方法(新提案)
#validate(amount) {
return amount > 0 && amount <= this.#balance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
withdraw(amount) {
if (this.#validate(amount)) {
this.#balance -= amount;
return true;
}
return false;
}
get balance() {
return this.#balance;
}
}
const account = new Account();
account.deposit(100);
console.log(account.balance); // 100
console.log(account.#balance); // 语法错误
ES6 模块化规范,通过 import 和 export 实现模块的导入导出。
// math.js
// 单个导出
export const add = (x, y) => x + y;
export const subtract = (x, y) => x - y;
// 默认导出
export default class Calculator {
// ...
}
// 统一导出
const multiply = (x, y) => x * y;
const divide = (x, y) => x / y;
export { multiply, divide };
// main.js
// 导入单个或多个
import { add, subtract } from './math.js';
// 导入默认导出
import Calculator from './math.js';
// 导入全部并重命名
import * as MathUtils from './math.js';
// 重命名导入
import { add as addition } from './math.js';
// 同时导入默认和非默认
import Calculator, { add, subtract } from './math.js';
// 按需加载模块
button.addEventListener('click', async () => {
const module = await import('./dialog.js');
module.showDialog();
});
<!-- 使用 type="module" -->
<script type="module">
import { add } from './math.js';
console.log(add(2, 3));
</script>
<!-- 导入外部模块脚本 -->
<script type="module" src="main.js"></script>