跳到主要内容

ES6 基础

简单的特性​

基础特性

符号(Symbol)​

Symbol 解决了对象属性命名冲突的问题,通过 Symbol 创建的数据是互不相等的(即使参数一样,也不会相等)

注意的点:

  • Symbol 没有字面量
  • 使用 Symbol 做属性,则该属性不可被枚举(for...in、Object.keys、Object.getOwnPropertyNames),可以通过 Object.getOwnPropertySymbols 读取符号属性
  • Symbol 不能被隐式转换(数字、字符串拼接等),但可以显示转换为字符串(String(Symbol("111")))

共享符号​

通过 Symbol.for(符号描述)创建,只要 符号描述 相等,则生成的符号也互等

// 读取符号属性
const obj = {
a: 1,
b: 2,
[Symbol.for("c")]: 3
}

console.log(obj[Symbol.for("c")]); // 3

知名符号​

概念: 是一种特殊的共享符号,知名符号可以暴露 js 对象的一些内部实现方法,通过自定义这些暴露的方法就可以改写 js 的默认实现逻辑

常见的知名符号 有以下几种

  • Symbol.hasInstance: 该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定
  • Symbol.isConcatSpreadable: 该知名符号会影响数组的 concat 方法
  • Symbol.toPrimitive: 该知名符号会影响类型转换的结果
  • Symbol.toStringTag: 该知名符号会影响 Object.prototype.toString 的返回值([object Xxxx])

一些例子

// ----------------- Symbol.species 为构造函数创建派生对象,从当前对象新派生对象的构造函数可以通过 Symbol.species 改变
class MyArray extends Array {
// static [Symbol.species] = Array;
// 等同于上面效果
static get [Symbol.species]() {
return Array;
}
}

const array = new MyArray();
array.push(1);
array.push(2);
console.log(array instanceof Array); // true
console.log(array instanceof MyArray); // true
// 从 array 派生一个 mapArray 数组,mapArray 的构造函数是 Symbol.species 的返回值
const mapArray = array.map((item) => item);
console.log(mapArray instanceof Array); // true
console.log(mapArray instanceof MyArray); // false

// --------------------- Symbol.split 自定义字符串的 split 方法 -------------
const exp = {
pat: "in",
[Symbol.split](str) {
return str.split(this.pat);
},
};

console.log("1dqwinqwdwq".split(exp));

// --------------------- Symbol.asyncIterator 自定义对象的异步迭代器 -------------
const myAsyncIterable = new Object();
// 异步迭代器 Symbol.asyncIterator
myAsyncIterable[Symbol.asyncIterator] = async function*() {
yield "hello";
yield "async";
yield "iteration!";
};

(async () => {
for await (const x of myAsyncIterable) {
// console.log(x);
// expected output:
// "hello"
// "async"
// "iteration!"
}
})();

// ---------------------- Symbol.toPrimitive 自定义对象转换规则 ----------
const obj = {
a: 1,
b: 2,
[Symbol.toPrimitive](hint) {
// hint obj 通过运算符要转换的目标类型,来定制返回的原始值
// default 默认的转换方式
if (hint === "number") {
return 4;
} else if (hint === "string") {
return "string";
}
return 1;
},
};

console.log(+obj, "" + obj);

// -------------------------- Symbol.hasInstance,自定义 instanceof 判断规则 ------
const arr = [1, 2, 3];
arr[Symbol.hasInstance] = function(instance) {
console.log("wef2f", instance);
// return "svs";
return false; // 若返回值不是布尔类型会被转为 boolean 类型
};

console.log([] instanceof arr); // wef2f [] false

// ------------------------- Symbol.isConcatSpreadable 自定义数组被 concat 拼接时是否可以展开 ------
const arrConcat = [1, 2, 3];

arrConcat[Symbol.isConcatSpreadable] = true; // true-可展开,不可展开
console.log([4, 5, 6, ...arrConcat]); // [ 4, 5, 6, 1, 2, 3 ]
console.log([4, 5, 6].concat(arrConcat)); // [ 4, 5, 6, [1, 2, 3]]

// ---------------------- Symbol.replace 自定义 String.prototype.replace 匹配规则 -------
class Replace {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return `${string}--${this.value}`;
}
}
const str = String("123");
console.log(str.replace(new Replace("456")));

// ---------------------- Symbol.toStringTag 自定义 Object.prototype.string 返回的类型标签 -------

class Persion {
constructor(value) {
this.value = value;
}

[Symbol.toStringTag] = "Persion"; // 默认值是 Object
}

const persion = new Persion("123");

console.log(Object.prototype.toString.call(persion)); // [object Persion]

// ------------------------ Symbol.match 标识对象是否有正则表达式的行为 --------------------------------

const regexp1 = /foo/;
regexp1[Symbol.match] = false;
console.log("/foo/".startsWith(regexp1)); // true
console.log("/baz/".endsWith(regexp1)); // false

Promise​

Promise

集合​

集合可存储和操作多个数据,扩展了 JS 存储多个数据的能力

Set​

Set 用于存储不重复的数据

创建 Set 对象需要使用 new 操作符,参数是一个可传的「可迭代对象」,Set 最终保存的数据是「通过执行可迭代对象返回的数据」。Set 对象是一个可迭代对象,所以可以使用数组的扩展运算符展开

class MySet {
constructor(iterator = []) {
//参数是一个可迭代对象
if (typeof iterator[Symbol.iterator] !== "function") {
return new Error("请提供一个可迭代对象");
}
this.state = [];
for (let data of iterator) {
// 存储迭代出的数据
this.add(data);
}
}

// 获取数据个数
get size() {
return this.state.length;
}

// 添加数据
add(data) {
// 数据去重
if (!this.has(data)) {
this.state.push(data);
}
}

// 删除数据 返回值 为 boolean
delete(data) {
const index = this.state.findIndex((item) => this.isEqual(data, item));
if (index !== -1) {
return !!this.state.splice(index, 1);
}
return false;
}

// 自己定义的内部方法:用于判断两个数句是否相等
isEqual(data1, data2) {
// 兼容 +-0 间互等
if (data1 === data2) {
return true;
}
return Object.is(data1, data2); // 兼容 NaN 互等
}

// 遍历 set
forEach(fn) {
for (let data of this.state) {
fn(data, data, this);
}
}

// 清空 set
clear() {
this.state.length = 0;
}

// Symbol.iterator 使得 set 可迭代
*[Symbol.iterator]() {
for (let data of this.state) {
yield [data, data];
}
}

// entries 每次迭代返回一个元组,值为两个当前迭代的数据
*entries() {
for (let data of this.state) {
yield [data, data];
}
}

// entries 每次迭代返回一个 key 值,值为迭代的数据
*keys() {
for (let data of this.state) {
yield data;
}
}

// entries 每次迭代返回当前的数据
*values() {
for (let data of this.state) {
yield data;
}
}

// 判断数据是否存在
has(data) {
for (let _data of this.state) {
if (this.isEqual(data, _data)) {
return true;
}
}
return false;
}
}

Map​

Map 用于存储多个键值对,键不可重复,解决了对象键为字符串的问题,Map 的键可以是任意类型,并且查询和删除比 Object 更方便

创建 Map 对象需要使用 new 操作符,参数必须是一个可迭代对象,每个迭代出的数据必须是长度大于 1 的数组

class MyMap {
constructor(iterator = []) {
if (typeof iterator[Symbol.iterator] !== "function") {
return new Error("参数必须是一个可迭代对象");
}

this.state = [];
for (let item of iterator) {
if (typeof item[Symbol.iterator] !== "function") {
return new Error("参数每一项必须是一个可迭代对象");
}
const [key, val] = item;
this.state.set(key, val);
}
}

// 获取数据个数
get size() {
return this.state.length;
}

// 设置(添加)数据
set(key, value) {
const obj = this._getItemObj(key);
if (obj) {
obj.value = value;
} else {
this.state.push({ key, value });
}
}

// 获取数据
get(key) {
const obj = this._getItemObj(key);
if (obj) {
return obj.value;
}
return undefined;
}

// 删除数据
delete(key) {
for (let i = 0; i < this.size; i++) {
const { key: _key, value } = this.state[i];
if (this.isEqual(key === _key)) {
this.state.splice(i, 1);
return true;
}
}
return false;
}

// 迭代 keys
*keys() {
for (let item of this.state) {
yield item.key;
}
}

// 迭代 values
*values() {
for (let item of this.state) {
yield item.values;
}
}

// 迭代 keys values
*entries() {
for (let [key, value] of this.state) {
yield [key, value];
}
}

// 判断是否存在数据 NaN 和 NaN 是互等的
has(key) {
return !!this._getItemObj(key);
}

// 清空数据
clear() {
this.state.length = 0;
}

// 一次遍历 map 对象
forEach(fn) {
for (let [key, value] of this.state) {
fn(value, key, this);
}
}

_getItemObj(key) {
for (let item of this.state) {
const { key: _key } = item;
if (this.isEqual(key === _key)) {
return item;
}
}
return false;
}

isEqual(data1, data2) {
if (data1 === data2) {
return true;
}
return Object.is(data1, data2);
}
}

WeakSet​

和 Set 类似,有以下不同

  • 数据只能是对象
  • 「存储的对象不影响垃圾回收,当存储的对像被回收后,该对象会被删除掉」
  • 不是可迭代对象
  • 只有 add、delete 和 has 三个方法

WeakMap​

和 Map 类似,有以下不同

  • 数据的 key 只能是对象
  • 「key 存储的对象不影响垃圾回收,当存储的对像被回收后,对应的键值对会被删除」
  • 不是可迭代对象
  • 只有 set、delete、has 和 get 三个方法

代理和反射​

反射(Reflect)​

受到函数式编程的影响,js 将类似于函数调用、对象设置数据、获取对象数据等等此类的方法(具体到 MDN 查看)聚合到一个对象上,这个对象就是 Reflect,通过 Reflect 我们只需要关心函数的输入和输出就可以,不需要去关注底层的内部实现,Reflect 已经帮我们处理的所有逻辑

declare namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: Function): any;
function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
function deleteProperty(target: object, propertyKey: PropertyKey): boolean;
function get(target: object, propertyKey: PropertyKey, receiver?: any): any;
function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor | undefined;
function getPrototypeOf(target: object): object | null;
function has(target: object, propertyKey: PropertyKey): boolean;
function isExtensible(target: object): boolean;
function ownKeys(target: object): (string | symbol)[];
function preventExtensions(target: object): boolean;
function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: object, proto: object | null): boolean;
}

代理(Proxy)​

Proxy 是可以拦截底层默认的逻辑,使得我们可以修改和替换目标对象的某些操作,比如拦截函数对象的调用可以使用 apply handler,使用方式就是将目标对象使用 Proxy 包一层

对应文档到 MDN 查看

一个拦截函数调用的例子:

interface ProxyHandler<T extends object> {
// 拦截函数调用
apply?(target: T, thisArg: any, argArray: any[]): any;
// 拦截 new 命令
construct?(target: T, argArray: any[], newTarget: Function): object;
// 拦截 Object.defineProperty
defineProperty?(target: T, property: string | symbol, attributes: PropertyDescriptor): boolean;
// 拦截 delete 操作符
deleteProperty?(target: T, property: string | symbol): boolean;
// 拦截 get 属性读取器
get?(target: T, p: string | symbol, receiver: any): any;
// 拦截 Object.getOwnPropertyDescriptor
getOwnPropertyDescriptor?(target: T, p: string | symbol): PropertyDescriptor | undefined;
// 拦截 Object.getPrototypeOf
getPrototypeOf?(target: T): object | null;
// 拦截 in 操作符
has?(target: T, p: string | symbol): boolean;
// 拦截 Object.isExtensible
isExtensible?(target: T): boolean; // 判断一个对象是否可扩展(添加属性)
// Object.getOwnPropertyNames、Object.getOwnPropertySymbols
ownKeys?(target: T): ArrayLike<string | symbol>;
// Object.preventExtensions
preventExtensions?(target: T): boolean; // 让一个对象变得不可扩展
// 拦截 set 属性存储器
set?(target: T, p: string | symbol, value: any, receiver: any): boolean;
// 拦截 Object.setPrototypeOf
setPrototypeOf?(target: T, prototype: object | null): boolean;
}

interface ProxyConstructor {
// 创建一个可撤销的 Proxy 对象
revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
// 创建 Proxy 对象 target-目标对象,handler-目标对象一些默认操作的拦截器
new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}