ES6(ES2015)新特性

发布时间:2015年6月 ES6(即 ES2015)是 JavaScript 历史上变化最大的一次升级,正式引入了块级作用域、类、模块、Promise、Generator、Proxy 等一整套现代语法与能力。

说明:本文件只保留 ES2015 正式特性,不混入 ES2016+ 才加入的能力(例如 Array.prototype.includes()padStart()Object.entries()Promise.finally()Array.prototype.flat() 等)。

1. let 和 const

let

  • 块级作用域,只在当前 {} 内有效
  • 不存在传统意义上的变量提升,存在暂时性死区(TDZ)
  • 同一作用域内不能重复声明
{
  let a = 10;
  var b = 1;
}

console.log(b);  // 1
console.log(a);  // ReferenceError

const

  • 用于声明常量,必须立即初始化
  • 也是块级作用域
  • 对于引用类型,地址不可变,但内容可以变
const PI = 3.14;
const arr = [1, 2, 3];
arr.push(4);      // 允许
// arr = [];      // TypeError

暂时性死区(TDZ)

{
  // console.log(x);  // ReferenceError
  let x = 1;
}

2. 解构赋值(Destructuring)

数组解构

let [a, b, c] = [1, 2, 3];
let [x, , y] = [1, 2, 3];
let [m, ...n] = [1, 2, 3, 4];

console.log(a, b, c);  // 1 2 3
console.log(x, y);     // 1 3
console.log(n);        // [2, 3, 4]

对象解构

let { name, age } = { name: '张三', age: 18 };
let { name: userName } = { name: '李四' };

console.log(name);      // 张三
console.log(userName);  // 李四

默认值

let [a = 1, b = 2] = [3];
let { x = 10, y = 20 } = { x: 5 };

console.log(a, b);  // 3 2
console.log(x, y);  // 5 20

函数参数解构

function greet({ name, age }) {
  console.log(`你好,${name},今年 ${age} 岁`);
}

greet({ name: '王五', age: 20 });

3. 字符串扩展

模板字符串

支持换行、插值、表达式:

let name = '张三';
let age = 18;

let str = `你好,${name},明年 ${age + 1} 岁`;
console.log(str);

标签模板

function tag(strings, ...values) {
  console.log(strings);
  console.log(values);
}

let name = '张三';
let age = 18;
tag`姓名:${name},年龄:${age}`;

新方法

'hello'.includes('ell');      // true
'hello'.startsWith('he');     // true
'hello'.endsWith('lo');       // true
'hello'.repeat(3);            // 'hellohellohello'

Unicode 增强

'𠮷'.length;                  // 2
'𠮷'.codePointAt(0);          // 134071
String.fromCodePoint(134071); // '𠮷'
'\u{20BB7}';                  // '𠮷'

String.raw()

String.raw`Hi\n${2 + 3}`;
// 'Hi\\n5'

4. 数值与 Math 扩展

二进制和八进制表示法

0b111110111 === 503;  // true
0o767 === 503;        // true

Number 新方法与常量

Number.isFinite(15);        // true
Number.isNaN(NaN);          // true
Number.isInteger(5);        // true
Number.EPSILON;             // 2.220446049250313e-16
Number.MAX_SAFE_INTEGER;    // 9007199254740991
Number.MIN_SAFE_INTEGER;    // -9007199254740991

Math 新方法

Math.trunc(4.9);      // 4
Math.sign(-5);        // -1
Math.cbrt(8);         // 2
Math.hypot(3, 4);     // 5

5. 函数扩展

默认参数

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello');          // Hello World
log('Hello', 'China'); // Hello China

rest 参数

function sum(...nums) {
  return nums.reduce((total, item) => total + item, 0);
}

sum(1, 2, 3);  // 6

箭头函数

const add = (a, b) => a + b;
const square = n => n * n;
const getObj = id => ({ id, name: 'test' });

注意:

  • 箭头函数没有自己的 this
  • 没有 arguments
  • 不能当构造函数使用
const obj = {
  id: 1,
  normal() {
    console.log(this.id);
  },
  arrow: () => {
    console.log(this.id);
  }
};

函数名 name 属性

function foo() {}
console.log(foo.name);  // 'foo'

尾调用优化(规范层面)

尾调用是函数最后一步调用另一个函数。ES2015 在规范层面定义了相关优化空间,但实际引擎支持并不统一,因此开发中不要强依赖它。

6. 数组扩展

扩展运算符 ...

let arr1 = [1, 2];
let arr2 = [3, 4];
let arr3 = [...arr1, ...arr2];

function add(x, y) {
  return x + y;
}

add(...[1, 2]);  // 3

Array.from()

将类数组或可迭代对象转为真正数组:

Array.from({ length: 3 }, (_, i) => i);  // [0, 1, 2]
Array.from('hello');                      // ['h', 'e', 'l', 'l', 'o']

Array.of()

Array.of(1, 2, 3);  // [1, 2, 3]
Array.of(3);        // [3]

find() 和 findIndex()

[1, 2, 3, 4].find(x => x > 2);       // 3
[1, 2, 3, 4].findIndex(x => x > 2);  // 2

fill()

[1, 2, 3].fill(0);        // [0, 0, 0]
[1, 2, 3].fill(0, 1, 2);  // [1, 0, 3]

copyWithin()

复制数组内部指定位置的成员到其他位置:

[1, 2, 3, 4, 5].copyWithin(0, 3);  // [4, 5, 3, 4, 5]

keys()、values()、entries()

let arr = ['a', 'b'];

for (let key of arr.keys()) {
  console.log(key);  // 0, 1
}

for (let value of arr.values()) {
  console.log(value);  // 'a', 'b'
}

for (let [index, value] of arr.entries()) {
  console.log(index, value);
}

7. 对象扩展

属性简写

let name = '张三';
let age = 18;
let obj = { name, age };

属性名表达式

let key = 'name';
let obj = {
  [key]: '张三'
};

方法简写

let obj = {
  hello() {
    return '你好';
  }
};

Object.is()

Object.is(NaN, NaN);  // true
Object.is(+0, -0);    // false
NaN === NaN;          // false

Object.assign()

let target = { a: 1 };
let source = { b: 2 };
let result = Object.assign(target, source);

console.log(result);  // { a: 1, b: 2 }

Object.setPrototypeOf() / Object.getPrototypeOf()

let proto = { greet() { return 'hello'; } };
let obj = {};

Object.setPrototypeOf(obj, proto);
console.log(Object.getPrototypeOf(obj) === proto);  // true

super 关键字(对象方法中)

let proto = {
  foo() {
    return 'hello';
  }
};

let obj = {
  foo() {
    return super.foo() + ' world';
  }
};

Object.setPrototypeOf(obj, proto);
obj.foo();  // 'hello world'

8. Symbol

表示独一无二的值,是第 7 种原始数据类型:

let s1 = Symbol('foo');
let s2 = Symbol('foo');

console.log(s1 === s2);  // false

作为对象属性名

const id = Symbol('id');
let user = {
  [id]: 1001,
  name: '张三'
};

console.log(user[id]);
console.log(Object.getOwnPropertySymbols(user));

内置 Symbol

let arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
console.log(iter.next());

9. Set、Map、WeakSet、WeakMap

Set

值唯一的集合:

let set = new Set([1, 2, 2, 3]);
set.add(4);
set.delete(1);
set.has(2);   // true
set.size;     // 3
[...set];     // [2, 3, 4]

Map

键可以是任意类型:

let map = new Map();
map.set('name', '张三');
map.set(1, '数字键');

map.get('name');   // '张三'
map.has(1);        // true
map.size;          // 2

WeakSet / WeakMap

  • 只能存对象(ES2015 语义下)
  • 是弱引用,不阻止垃圾回收
  • 不可遍历
let obj = {};
let weakMap = new WeakMap();
weakMap.set(obj, 'data');

let weakSet = new WeakSet();
weakSet.add(obj);

10. Proxy 和 Reflect

Proxy

可以拦截对象的读取、赋值、删除、函数调用等操作:

let obj = { name: '张三' };
let proxy = new Proxy(obj, {
  get(target, key) {
    return key in target ? target[key] : '不存在';
  },
  set(target, key, value) {
    target[key] = value;
    return true;
  }
});

console.log(proxy.name);  // 张三
console.log(proxy.age);   // 不存在
proxy.age = 18;

Reflect

与 Proxy 拦截器一一对应,提供默认行为:

Reflect.get(obj, 'name');
Reflect.set(obj, 'age', 18);
Reflect.has(obj, 'name');
Reflect.deleteProperty(obj, 'name');

11. Promise

Promise 是 ES2015 正式引入的异步编程解决方案,用于解决回调地狱。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('成功'), 1000);
});

promise
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });

常用静态方法

Promise.resolve(42);
Promise.reject(new Error('err'));
Promise.all([p1, p2, p3]);
Promise.race([p1, p2, p3]);

状态

  • pending
  • fulfilled
  • rejected

12. Iterator 和 for...of

Iterator

任何实现了 next() 方法并返回 { value, done } 的对象,都可以被视为迭代器。

let arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();

iter.next();  // { value: 1, done: false }
iter.next();  // { value: 2, done: false }
iter.next();  // { value: 3, done: false }
iter.next();  // { value: undefined, done: true }

for...of

用于遍历可迭代对象:

for (let val of [1, 2, 3]) {
  console.log(val);
}

for (let [key, val] of new Map([['a', 1], ['b', 2]])) {
  console.log(key, val);
}

13. Generator 生成器

Generator 是一种可以暂停和恢复执行的函数。

function* helloGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

let gen = helloGenerator();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

与 Iterator 的关系

Generator 函数执行后返回一个迭代器对象,因此它是生成迭代器的语法糖。

14. Class 类

ES6 提供了更清晰的类语法,本质仍然基于原型。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `我是 ${this.name},今年 ${this.age} 岁`;
  }

  static create(name) {
    return new Person(name, 0);
  }
}

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }

  greet() {
    return `${super.greet()},读 ${this.grade} 年级`;
  }
}

关键点

  • constructor 是构造方法
  • extends 用于继承
  • 子类中必须先 super() 再使用 this
  • static 定义静态方法

15. 模块化(Module)

ES2015 正式定义了原生模块语法。

export 导出

export const name = '张三';
export function add(a, b) {
  return a + b;
}

export default class Person {}

import 导入

import { name, add } from './module.js';
import Person from './module.js';
import * as mod from './module.js';

特点

  • 静态分析友好
  • 支持按需导出与导入
  • import 会提升到模块顶部执行

16. 正则、二进制数组与其他补充

RegExp 扩展

ES2015 为正则引入了 uy 标志:

/^.$/u.test('𠮷');     // true,正确识别 Unicode 字符
let r = /a/y;
r.lastIndex = 1;
r.exec('ba');         // ['a']
  • u:Unicode 模式
  • y:粘连匹配,要求从 lastIndex 位置开始匹配

二进制数组(TypedArray)

let buffer = new ArrayBuffer(16);
let int32View = new Int32Array(buffer);
int32View[0] = 42;

console.log(int32View[0]);  // 42

相关对象:

ArrayBuffer  -> 原始二进制数据缓冲区
DataView     -> 灵活读写二进制数据
TypedArray   -> 各种类型化数组视图

new.target

可用于判断函数是否通过 new 调用:

function Person(name) {
  if (!new.target) {
    throw new Error('必须使用 new 调用');
  }
  this.name = name;
}

总结

模块 代表特性
变量声明 letconst
结构处理 解构赋值、模板字符串、展开运算符
函数能力 默认参数、rest、箭头函数
数据结构 SetMapWeakSetWeakMapSymbol
面向对象 classextendssuper
异步编程 Promise
元编程 ProxyReflect
遍历机制 Iteratorfor...ofGenerator
模块系统 importexport
底层能力 TypedArray、RegExp u/ynew.target

ES6(ES2015)奠定了现代 JavaScript 的基础。后续每年的 ECMAScript 更新,大多是在这套能力之上继续迭代和补充。

ES6+新特性 文章被收录于专栏

包含ES6+新特性

全部评论
你是我见过最帅的牛客男孩
1 回复 分享
发布于 03-26 10:35 北京
ES6真香
1 回复 分享
发布于 03-26 10:06 广东

相关推荐

`multiset` 是 C++ 标准库中的一个关联容器,位于 `<set>` 头文件中。它与 `set` 类似,但**允许存储重复的元素**,且内部始终保持有序(默认升序)。---1. 基本定义```cpp#include <set>using namespace std;multiset<int> ms;                 // 升序(默认)multiset<int, greater<int>> ms2;  // 降序```---2. 常用操作| 操作 | 代码示例 | 说明 ||------|----------|------|| 插入 | `ms.insert(x);` | 插入一个元素,允许重复 || 查找 | `auto it = ms.find(x);` | 返回第一个等于 `x` 的迭代器,找不到返回 `end()` || 计数 | `int cnt = ms.count(x);` | 返回 `x` 出现的次数 || 删除单个 | `ms.erase(it);` | 删除迭代器指向的元素 || 删除所有 | `ms.erase(x);` | 删除所有等于 `x` 的元素 || 大小 | `int sz = ms.size();` | 当前元素个数 || 清空 | `ms.clear();` | 删除所有元素 |---3. 边界迭代器(重要)- **`lower_bound(x)`**:返回第一个 **≥ x** 的迭代器。- **`upper_bound(x)`**:返回第一个 **> x** 的迭代器。- **`begin()`**:指向第一个元素(最小)。- **`end()`**:指向最后一个元素**之后**的位置(不指向实际元素)。**注意**:- 当所有元素都小于 `x` 时,`lower_bound(x)` 返回 `end()`。- 当容器为空时,`begin() == end()`。---4. 迭代器与距离迭代器可以像指针一样移动和取值:```cppauto it = ms.begin();      // 指向第一个元素int first = *it;           // 获取元素值++it;                      // 移动到下一个元素```**`distance(begin, it)`**:计算两个迭代器之间的元素个数(需要 `<iterator>` 头文件)。例如:统计左边小于当前值的元素个数:```cppauto it = ms.lower_bound(x);int less_cnt = distance(ms.begin(), it);   // 比 x 小的元素个数```- 当 `it == ms.end()` 时,`distance` 等于当前总个数(所有元素都小于 `x`)。---5. 与 `set` 的区别| 特性 | `set` | `multiset` ||------|-------|------------|| 重复元素 | 不允许 | 允许 || 插入 | 唯一 | 可重复 || 删除 `erase(value)` | 最多删一个 | 删所有相等的 || 常用场景 | 需要唯一集合 | 需要计数或保留重复值 |---6. 时间复杂度- 插入、删除、查找、`lower_bound` / `upper_bound`:**O(log n)**- 遍历(如 `for` 循环):O(n)- `distance` 在非随机访问迭代器上是 O(k),k 为距离,**不是 O(1)**。---7. 实际应用:统计左边比当前小的元素个数(经典题)```cpp#include <iostream>#include <set>#include <iterator>using namespace std;int main() {int n, x;cin >> n;multiset<int> left;   // 存放左边已出现的数for (int i = 0; i < n; ++i) {cin >> x;auto it = left.lower_bound(x);          // 第一个 >= x 的位置int cnt = distance(left.begin(), it);   // 左边 < x 的个数cout << cnt << " ";left.insert(x);}return 0;}```**解释**:- 因为 `left` 有序,所有小于 `x` 的元素都在 `begin()` 到 `it` 之间。- `lower_bound` 返回第一个 ≥ x 的位置,正好划出了“小于 x”的区间。- 使用 `distance` 得到区间长度,即小于 `x` 的个数。- 每次遍历后把当前值插入,供后续比较。---8. 为什么不用 `set`?`set` 会去重,如果左边有重复的可爱值,统计结果会偏小,而 `multiset` 保留了所有重复值,保证计数正确。---9. 注意事项- 包含头文件 `<set>`,使用迭代器时可能需要 `<iterator>`(`distance` 在其中)。- 不要解引用 `end()`,也不要在空容器上解引用 `begin()`。- 当使用 `auto` 时,编译器自动推导迭代器类型,简化代码。---10. 总结`multiset` 是一个**有序可重复容器**,适合需要维护动态有序序列且允许重复的场景。通过 `lower_bound` 和 `distance` 可以轻松统计比当前元素小的个数,是解决“左边比当前小”类问题的利器。掌握它的基本操作,可以让你在处理有序数据时更加得心应手。
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务