深入浅出 ES6
let & const
- 声明变量 6 种: var,function,let, const,import, class
- 全局对象: window, global(node), globalThis(es6)
解构
- 通过模式匹配进行解构,等号两边模式相同,则进行完全匹配,否则为不完全匹配, 取不到的值为
undefined
// 完全匹配
const [a, b] = [1, 2]; // a = 1, b = 2
// 不完全匹配
const [a, [b], c] = [1, [2, 3]]; // a = 1, b = 2, c = undefined- 只可解构具有可遍历解构的数据结构(Array, Set, Map, Generator 函数)
- 解构赋值允许指定默认值,仅在取得得数组成员值严格等于(===)
undefined时生效
let [x = 2] = []; // x = 2
let [y = 2, z = 3] = [null, undefined]; // y = null, z= 3
// 默认值可以引用解构赋值的其他变量,但【该变量必须先声明】
let [x = 2, y = x] = [1, undefined]; // x = 1, y = 1
let [z = c] = []; // ReferenceError: c is not defined- 解构用途:交换变量;从函数返回多个值;函数参数定义;函数参数默认值;提取 JSON 数据;遍历 Map 结构;指定引入模块的部分方法
// 遍历Map结构
for (let [, value] of [{key: 1, value: 2}])
// 指定引入模块的部分方法
const { x, y } = require('test')es6 扩展
- 模板字符串
// 模版字符串
const name = "jack";
const x = `Hello, ${name}`; // Hello, jack
// 标签模版字符串(函数➕模板字符串)
// 过滤 HTML 字符串,防止用户输入恶意内容。
alert`jack`;实现一个模板字符串
let template = "我的工作是${job}, 我的钱是$${salary}";
let person = { job: "前端", salary: 30000 };
// 核心:将${}部分正则匹配后替换为相应变量
const render = (template, obj) => {
return template.replace(/\$\{(.*?)\}/g, (match, key) => {
console.log(match, key);
return obj[key];
});
};
console.log(render(template, person));- 正则
字符串对象有 4 个方法可以使用正则表达式: match、replace、split、search
- 数值
// 手写parseInt,未完善,没有处理负号
const _parseInt = (str, radix = 10) => {
if (!["string", "number"].includes(typeof str) && !str.length) {
return NaN;
}
if (!["number"].includes(typeof radix) || radix < 2 || radix > 36) {
return NaN;
}
const intStr = String(str).trim().split(".")[0]; // 截取小数点前的整数
let res = 0;
for (let i = 0; i < intStr.length; i++) {
let arr = intStr.split("").reverse().join("");
res += Math.floor(arr[i]) * Math.pow(radix, i);
}
return res;
};BigInt
js 数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示; 大于或等于 2 的 1024 次方的数值,JavaScript 会返回
Infinity
JS 引入新的数据类型 BigInt(大整数),用来精确表示大整数
const a = 12; // 普通整数
const A = 12n; // BigInt
a === A; // fasle
typeof a; // number
typeof A; // bigint
// BigInt 不能与普通数值进行混合运算,但允许比较
1n + 1; // 报错
1n > 0; // true- 函数
// 不报错
function f(x, x, y) {}
// 报错,函数使用参数默认值时,函数不能有同名参数
function f(x, x, y = 1) {}- 请问下面两种写法有什么差别? 两种写法都对函数的参数设定了默认值, 区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值; 写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
// 写法一
function m1({ x = 0, y = 0 } = {}) {
return [x, y];
}
// 写法二
function m2({ x, y } = { x: 0, y: 0 }) {
return [x, y];
}
// 函数没有参数的情况
m1(); // [0, 0]
m2(); // [0, 0]
// x 和 y 都有值的情况
m1({ x: 3, y: 8 }); // [3, 8]
m2({ x: 3, y: 8 }); // [3, 8]
// x 有值,y 无值的情况
m1({ x: 3 }); // [3, 0]
m2({ x: 3 }); // [3, undefined]
// x 和 y 都无值的情况
m1({}); // [0, 0];
m2({}); // [undefined, undefined]
m1({ z: 3 }); // [0, 0]
m2({ z: 3 }); // [undefined, undefined]- 函数的 length 属性是指预期传入的参数个数。
(function f(a) {}).length(
// 1
function f(x, a = 5, y) {}
).length; // 1,只有 x 计入,y 在 a 后面也不计入了- 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)
const x = 3;
function f(x, y = x) {
console.log(y);
}
f(2); // 2- 箭头函数
- 没有
arguments参数 - 不能作为构造函数,所以不能使用
new命令 - 不能作为 Generator 函数,不能使用
yield命令 this绑定定义时的对象,而非使用时对象
- 没有
函数尾调用
函数返回另一函数时,会清除重用当前调用栈而非新建一个。
函数内部有两个参数可以跟踪函数的调用栈:
- func.arguments : 返回调用函数的参数
- func.caller: 返回调用当前函数的函数
- 数组
Array.prototype.flat(n), n 为层数, 可以将嵌套的数组“拉平”
// 默认拉平1层,会跳过空位
[1, 2, , [4], 5]
.flat()
[
// [1, 2, 4, 5]
(1, 2, [3, [4, 5]])
].flat(2)
[
// [1, 2, 3, 4, 5]
// 转成一维数组
(1, [2, [3]])
].flat(Infinity);
// [1, 2, 3]flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组,只能展开一层数组。
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap((x) => [[x * 2]]);
// [[2], [4], [6], [8]]- 对象
es6 有 5 种方式遍历对象属性:
for-in:返回对象自身以及继承的可枚举属性(不含 symbol 属性)
Object.keys: 返回一个数组,返回对象自身的可枚举属性(不含 symbol 属性)
Object.getOwnPropertyNames(obj):返回一个数组,返回对象自身的所有属性(不含 symbol 属性)
Object.getOwnPropertySymbols(obj):返回一个数组,返回对象自身所有 symbol 属性
Reflect.ownKeys:返回一个数组,返回对象自身所有属性(包括 symbol)
super关键字指向当前对象的原型对象,且super关键字只能用于对象方法中。
// 报错,用在属性上
const obj = {
foo: super.foo,
};
// 报错, 函数返回给属性
const obj = {
foo: () => super.foo,
};
// 报错
const obj = {
foo: function () {
return super.foo;
},
};// 对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性
const proto = {
foo: "hello",
};
const obj = {
foo: "world",
find() {
return super.foo; // 方法调用
},
};
Object.setPrototypeOf(obj, proto);
obj.find(); // "hello"Object.is 用来比较两个值是否严格相等,与===不同的是:一是+0不等于-0,二是NaN等于自身。
Object.is({}, {}); // false
// 实现Object.is
function isEqual(x, y) {
if (x === y) {
// +0 不等于 -0
return x !== 0 || 1 / x === 1 / y;
}
return x !== x && y !== y;
}设置原型对象:Object.setPrototypeOf(object, prototype)
Symbol
Es6 引入的第八种原始数据类型Symbol,表示独一无二的值。其余七种原始数据类型是:undefined、null、Number、String、Boolean、Object、BigInt。
const uid = Symbol("only");
let uid1 = Symbol("only");
console.log(uid1 === uid); // false
const boy = Symbol.for("Jack");
const girl = Symbol.for("Jack");
console.log(boy === girl); // true
console.log(Symbol.keyFor(boy)); // Jack
console.log(String(uid)); // 'Symbol(only)'- Symbol.iterator 返回一个迭代器
- Symbol.hasInstance 执行 instanceof 时的内部方法 a instanceof b 相当于 bSymbol.hasInstance
- Symbol.toPrimitive 返回对象原始值
Set
Set 结构不会添加重复的值
const set = new Set([1, 2, 2]); // [1, 2]
set.add(2).add(3); // Set(3) {1, 2, 3}
set.has(2); // true
set.delete(1); // true -- 删除成功
set.size; // 2, Set(2) {2, 3}Set 可以很容易地实现并集、交集和差集
const a = new Set([1, 2, 4]);
const b = new Set([1, 2, 3]);
// 并集
const union = new Set([...a, ...b]); // Set(4) {1, 2, 4, 3}
// 交集
const inter = new Set([...a].filter((i) => b.has(i))); // Set(2) {1, 2}
// 差集
const diff = new Set([...a].filter((i) => !b.has(i))); // Set(1) {4}WeakSet
WeakSet 的成员只能是对象(不包括 null),而不能是其他类型的值,WeakSet 中的对象都是弱引用,不可遍历。
WeakSet 里面的引用,都不计入垃圾回收机制,防止内存泄露
const ws = new WeakSet();
ws.add(1); // Invalid value used in weak set
ws.add(null); // Invalid value used in weak set
const arr = [[2], [3]];
const arrWs = new WeakSet(arr); // WeakSet {[2], [3]}
// WeakSet 没有size属性,没有办法遍历它的成员
ws.size; // undefined
ws.forEach; //undefinedMap
Object 结构提供了 “字符串—值” 的对应,Map 结构提供了 “值—值” 的对应,是一种更完善的 Hash 结构实现。
// set | get | has | delete | clear| size
const x = new Map([
["key1", 1],
["key2", 2],
]);
console.log(x); // Map(2) { 'key1' => 1, 'key2' => 2 }
const m = new Map();
const o = { name: "Y" };
// 如果对同一个键多次赋值,后面的值将覆盖前面的值
m.set(o, 1);
m.set(o, 2);
m.set(null, 1);
m.get(o); // 2
m.has(o); // true
m.set("1", "str1");
m.set(1, "num1");
m.get("1"); // 'str1'
m.size; // 4
m.clear();
m.size; // 0WeakMap
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
// 注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = { foo: 1 };
wm.set(key, obj);
obj = null;
wm.get(key);
// Object {foo: 1}WeakMap的键名所指向的对象,不计入垃圾回收机制,防止内存泄露
WeakMap 与 Map 在 API 上的区别一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性。
二是无法清空,即不支持clear方法。
观察 WeakMap 里面的引用是否消失:
如果引用所指向的值占用特别多的内存,就可以通过 Node 的process.memoryUsage方法看出来:
// node允许手动执行垃圾回收机制
node --expose-gc
// 执行一次手动垃圾回收,确保获取内存准确
> global.gc();
// 查看内存初始占用大约为3M
> process.memoryUsage()
{
rss: 26243072,
heapTotal: 4780032,
heapUsed: 3286240,
external: 1573692,
arrayBuffers: 9408
}
> let wm = new WeakMap();
> let key = new Array(5 * 1024 * 1024);
> wm.set(key, 1)
// 此时,key引用的数组被引用了两次,key变量以及WeakMap的弱引用
// 但对引擎而言,引用计数为1
> global.gc()
// 内存占用为45M
> process.memoryUsage()
{
rss: 68538368,
heapTotal: 46727168,
heapUsed: 45177528,
external: 1573729,
arrayBuffers: 9405
}
// 清除key对数组的引用,但没有清楚wm键名对数组的引用
> key = null
> global.gc()
// 内存占用恢复至3M
> process.memoryUsage()
{
rss: 26570752,
heapTotal: 4780032,
heapUsed: 3084240,
external: 1573732,
arrayBuffers: 9408
}由此可见,只要外部的引用消失,WeakMap 内部的引用就会被垃圾回收机制清除。
Chrome 浏览器的 Dev Tools 的 Memory 面板,有一个垃圾桶的按钮,可以强制垃圾回收(garbage collect)。
WeakMap 应用的典型场合就是 DOM 节点作为键名以及部署私有属性。
Proxy
Proxy 可以在目标对象外层搭建一层拦截,外界对目标对象的某些操作,必须经过这层拦截。
// target: 目标对象
// handler: 配置对象
const proxy = new Proxy(target, handler);
// 设置一个拦截器,拦截对象phone属性并处理成三段手机号码形式
const pHandler = {
set(target, propKey, value, reciver) {
if (propKey === "phone") {
target[propKey] = value.match(/[0-9]/g).join("");
} else {
Reflect.set(target, propKey, value, reciver);
}
},
get(target, propKey, reciver) {
if (propKey === "phone") {
return target[propKey].replace(/(\d{3})(\d{4})(\d{4})/, "$1-$2-$3");
}
return Reflect.get(target, propKey, reciver);
},
};
const formatPhone = new Proxy({}, pHandler);
formatPhone.phone = "13432119667x";
formatPhone.phone; // '134-3211-9667'拦截操作(13 个)
- target: 目标对象
- propKey: 属性名称
- value:属性值
- receiver: proxy 实例本身(严格地说,是操作行为所针对的对象)
get(target, propKey, receiver)
拦截对象属性的读取,如obj.foo
set(target, propKey, value, receiver)
拦截对象属性的设置,返回布尔值,如obj.foo = 1
has(target, propKey)
拦截prop in obj操作,但不能拦截for-in,返回布尔值
deleteProperty(target, propKey)
拦截 delete obj[propKey]操作,返回布尔值
ownKeys(target)
拦截getOwnPropertyNames、getOwnPropertSymbols、Object.keys、for-in操作,返回对象自身所有属性(包括 Symbol 属性,不含继承),若拦截Object.keys则只返回自身可遍历属性
getOwnPropertyDescriptor(target, propKey)
拦截Object.getOwnPropertyDescriptor(obj, propKey)操作,返回属性的描述对象。
defineProperty(target, propKey, propDesc)
拦截Object.defineProperty、Object.defineProperties操作,返回布尔值
preventExtensions(target)
拦截Object.preventExtensions操作,返回一个布尔值
getPrototypeOf(target)
拦截Object.getPrototypeOf,返回一个对象
isExtensible(target)
拦截Object.isExtensible,返回一个布尔值。
setPrototypeOf(target)
拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
apply(target, object, args)
拦截 Proxy 实例作为函数调用操作,如proxy(...args)、proxy.call(obj, ...args)和proxy.apply(...)
construct(target, args)
拦截 Proxy 实例作为构造函数调用,比如new Proxy(...args)
Reflect
- 从
Reflect对象上可以拿到语言内部的方法 - 修改某些
Object方法的返回结果,让其变得更合理 - 让
Object操作都变成函数行为 Reflect对象的方法与Proxy对象的方法一一对应,也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为
静态方法(13 个)
get(target, name, receiver)
Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined。
如果name属性部署了读取函数(getter),则读取函数的this绑定receiver。
// baz属性设置了读取函数
const obj = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
const receiverObj = {
foo: 3,
bar: 4,
};
Reflect.get(obj, "baz", receiverObj); // 7set(target, name, receiver)
Reflect.set方法设置target对象的name属性等于value。
如果name属性设置了赋值函数,则赋值函数的this绑定receiver。
// bar属性设置了赋值函数
const obj = {
foo: 1,
set bar(value) {
return (this.foo = value);
},
};
const receiverObj = {
foo: 0,
};
Reflect.set(obj, "bar", -1, receiverObj);
obj.foo; // 1
receiverObj.foo; // -1has(obj, name)
Reflect.has方法对应name in obj里面的in运算符。
let obj = {
foo: 1,
};
// ES5
"foo" in obj; // true
// Reflect
Reflect.has(obj, "foo"); // truedeleteProperty(obj, name)
Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。
onst obj = { foo: 1, bar: 2}
// ES5
delete obj.foo
// ES6·Reflect
Reflect.deleteProperty(obj, 'bar')ownKeys (target)
Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。
const obj = {
foo: 1,
bar: 2,
[Symbol.for("baz")]: 3,
[Symbol.for("bing")]: 4,
};
// ES5
Object.getOwnPropertyNames(obj);
// ['foo', 'bar']
Object.getOwnPropertySymbols(obj);
//[Symbol(baz), Symbol(bing)]
// 新写法
Reflect.ownKeys(myObject);
// ['foo', 'bar', Symbol(baz), Symbol(bing)]getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor
用于得到指定属性的描述对象,将来会替代掉后者。
var obj = {};
Object.defineProperty(obj, "hidden", {
value: true,
enumerable: false,
});
// ES5
Object.getOwnPropertyDescriptor(obj, "hidden");
// {value: true, writable: false, enumerable: false, configurable: false}
// ES6·Reflect
Reflect.getOwnPropertyDescriptor(obj, "hidden");
// {value: true, writable: false, enumerable: false, configurable: false}defineProperty(target, propertyKey, attributes)
Reflect.defineProperty方法基本等同于Object.defineProperty(将被废除),用来为对象定义属性。
const student = {};
// ES5
Object.defineProperty(student, "name", { value: "Mike" }); // {name: "Mike"}
// ES6·Reflect
Reflect.defineProperty(student, "age", { value: 23 }); // truepreventExtensions(target)
Reflect.preventExtensions对应Object.preventExtensions方法。
用于让一个对象变为不可扩展,返回一个布尔值,表示是否操作成功。
const obj = {};
// ES5
Object.preventExtensions(obj); // 返回obj本身即:{}
// ES6·Reflect
Reflect.preventExtensions(obj); // true
obj.name = "change";
console.log(obj); // {}getPrototypeOf(obj)
Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)。
function Fancy(name) {
this.name = name;
}
const obj = Reflect.construct(Fancy, ["Tom"]);
// ES5
Object.getPrototypeOf(obj) === Fancy.prototype;
// true
// ES6·Reflect
Reflect.getPrototypeOf(obj) === Fancy.prototype;
// trueisExtensible (target)
Reflect.isExtensible方法对应`Object.isExtensible 返回一个布尔值,表示当前对象是否可扩展。
const obj = {};
// ES5
Object.isExtensible(obj); // true
// ES6·Reflect
Reflect.isExtensible(obj); // truesetPrototypeOf(obj, newProto)
Reflect.setPrototypeOf方法用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功。
const myObj = {};
// ES5
Object.setPrototypeOf(myObj, Array.prototype);
// ES6·Reflect
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length; // 0apply(func, thisArg, args)
Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。
construct(target, args)
Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。
function Greeting(name) {
this.name = name;
}
// new写法
const Foo = new Greeting("Tom");
// construct写法
const Bar = Reflect.construct(Greeting, ["Jerry"]);Promise 实例
Promise 的出现是为了解决回调地狱 一个 Promise有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
Promise对象的状态改变,只有两种可能:
- 从
pending变为fulfilled - 从
pending变为rejected
一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
resolve函数的作用是,将Promise对象的状态从 pending 变为 resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
const p = new Promise((resolve, reject) => {
console.log(`Promise新建后立即执行`);
setTimeout(() => {
const res = [{}, {}];
resolve(res);
console.log(`成功返回`);
}, 1000);
});
p.then((res) => console.log(res));
// Promise新建后立即执行
// 成功返回
// [{}, {}]Promise 实现 ajax
const aPromimse = (url) => {
return new Promise((resolve, reject) => {
const handler = function () {
if (this.readyState === 4) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = handler;
xhr.send();
});
};
const url = "https://jsonplaceholder.typicode.com/todos/1";
aPromimse(url).then((res) => console.log(`res: ${res}`));Promise.prototype.then()
then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数
Promise.prototype.catch()
用于指定发生错误时的回调函数。
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject(`some error`);
console.log(`返回失败`);
}, 1000);
});
p.catch((err) => console.error(err)); // some error如果 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise(function (resolve, reject) {
resolve("ok");
throw new Error("test");
});
promise
.then(function (value) {
console.log(value);
})
.catch(function (error) {
console.log(error);
});
// okPromise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作.
promise.finally(() => {
// 语句
});
// 等同于
promise.then(
(result) => {
return result;
},
(error) => {
throw error;
}
);
// resolve 的值是 undefined
Promise.resolve(2).then(
() => {},
() => {}
);
// resolve 的值是 2
Promise.resolve(2).finally(() => {});
// reject 的值是 undefined
Promise.reject(3).then(
() => {},
() => {}
);
// reject 的值是 3
Promise.reject(3).finally(() => {});上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
Promise.all()
// Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
const p = Promise.all([p1, p2, p3]);p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);
const p3 = Promise.resolve(3);
const p = Promise.all([p1, p2]);
p.then((r) => console.log(`r: ${r}`)).catch((e) => console.error(`e: ${e}`));
// e: 2
const _p = Promise.all([p1, p3]);
_p.then((r) => console.log(`r: ${r}`)).catch((e) => console.error(`e: ${e}`));
// [1, 3]
const p4 = new Promise((resolve, reject) => {
throw new Error("4");
})
.then((r) => r)
.catch((e) => e); // 返回一个新的Promise,执行完catch后变为resovled
// p1会resolved,p2首先会rejected,catch返回的新Promise执行完catch后为resolved
// 故Promise.all的结果为resolved
const pc = Promise.all([p1, p4]);
pc.then((r) => console.log(`r: ${r}`)).catch((e) => console.error(`e: ${e}`));
//r: 1,Error: 4Promise.race()
const p = Promise.race([p1, p2, p3]);只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.allSettled()
const p = Promise.race([p1, p2]);只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例p才会结束。
Promise.all无法确定所有请求都结束。
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);
// 返回的数组对象,status值为fulfilled(value)或者 rejected(reason)
const p = Promise.allSettled([p1, p2]);
p.then((r) => console.log(r));
// [{status: "fulfilled", value: 1}, {status: "rejected", reason: 2}]Promise.resolve()
将现有对象转为 Promise 对象
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
Iterator 迭代器
- 为各种数据结构提供统一、简便的访问接口
- 提供
for-of使用
next方法返回的对象的结构是{value, done},其中value表示当前的数据的值,done是一个布尔值,表示遍历是否结束。
const it = new makeIterator(["a", "b"]);
function makeIterator(arr) {
let i = 0;
return {
next() {
return {
value: i > arr.length ? undefined : arr[i++],
done: i > arr.length,
};
},
};
}
it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: undefined, done: true}原生具备 Iterator 接口(即部署了Symbol.iterator属性)的数据结构:
- Array
- String
- Map
- Set
arguments参数NodeList对象
const arr = ["a", "b"];
const it = arr[Symbol.iterator]();
it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: undefined, done: true}调用Iterator接口的场合
for-ofArray.fromMap()|Set()|WeakMap()|WeakSet()Proimise.race()|Promise.all()解构赋值扩展运算符yield*
function* A() {
yield 1;
yield 2;
return 3;
}
function* B(count) {
for (let i = 0; i < count; i++) {
yield "re";
}
}
function* C() {
let result = yield* A(); // 执行到 return 语句才返回
console.log(result);
yield* B(result);
}
const iterator = C();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
// 3
console.log(iterator.next()); // { value: 're', done: false }Generator 函数 实现 Iterator 接口
// 对象原生未部署 Iterator 接口
let Iterable = {
[Symbol.iterator]: function* () {
yield 1;
yield* [2, 3]
}
}
[...Iterable] // [1, 2, 3]for-of 遍历
ES6 的 Array, Map, Set接口默认部署了以下三个方法,调用后返回遍历器对象。
entries(): 返回[key, value]组成的数组keys():返回一个遍历器对象,包含所有keyvalues(): 返回一个遍历器对象,包含所有value
// 使用 for-of 遍历对象
let obj = {age: 12, name: 'Jack'}
for (ler key of Object.keys(obj)) {
console.log(key, obj[key])
}
// age 12
// name jack对比其他遍历方法
for 写法麻烦
forEach: 中途无法跳出循环。break 命令或者 return 命令都无效
for-in: 以任意顺序遍历自身及原型链上所有键名,返回字符串格式的键名('0')
异步遍历器
Iterator 遍历器的next方法必须是同步的,只要调用必须立刻返回值。
但如果next()返回一个 Promise 对象(异步操作),这样就不符合Iterator协议
next()异步操作
目前解决的方法是将next ()返回值的value包装为 Promise 对象,等待真正的值返回,
而done属性则同步返回。
const it = makeAsyncIterator();
function makeAsyncIterator() {
let i = 0;
return {
next() {
return {
value: new Promise((resolve, reject) => {
setTimeout(() => resolve(i++), 100);
}),
done: false,
};
},
};
}
it.next().value.then((r) => console.log(r)); // 100ms左右后返回 0for await…of
(async function () {
for await (const x of ["a", "b"]) {
console.log(x);
}
})();
// a
// b
// Promise {<fulfilled>: undefined}异步 Generator 函数
async function* gen() {
yield 1;
}
const ait = gen();
ait.next().then((r) => console.log(r)); // {value: 1, done: false}
ait.next().then((r) => console.log(r)); // {value: undefined, done: true}Class 类
constructor方法即构造方法,也就是说 ES5 的构造函数 Point,对应 ES6 的Point类的构造方法
一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
constructor方法默认返回实例对象(即this)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
print() {
console.log(`x:${this.x}, y: ${this.y}`);
}
}
const p = new Point(1, 2);
p.print(); // x:1, y: 2
// 类数据类型是函数,类本身就指向构造函数
typeof Point; // 'function'
(p.constructor === Point) === Point.prototype.constructor; // true
// constructor函数返回一个全新的对象,结果导致实例对象不是 Foo类的实例
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo; // fasle事实上,类的所有方法都定义在类的prototype属性上面。
class Ball {
print() {
console.log(`球`);
}
}
Ball.print(); // Ball.print is not a function
Ball.prototype.print(); // 球
// 由于类的方法定义在其prototype对象上,
// 类的新方法可以利用Object.assign 向【类的prototype】添加
Object.assign(Ball.prototype, {
toString() {},
});类的内部所有定义的方法,都是不可枚举的,这一点与 ES5 行为不一致
class A {
toString() {}
}
A.prototype.print = () => {};
Object.keys(A.prototype); // ["print"]不会把类的声明提升到代码头部
在 “类” 的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
class MyClass {
get name() {
return "getter";
}
set name(val) {
console.log(`setter: ${val}`);
}
}
let cn = new MyClass();
cn.name = "中文"; // setter: 中文
cn.name; // getter如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数
class Test {
constructor(...args) {
this.args = args;
}
*[Symbol.iterator]() {
for (let a of this.args) {
yield a;
}
}
}
for (let s of new Test("ss", "hh", "ii")) console.log(s);静态方法
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为 “静态方法”。
静态方法可以与非静态方法重名。
class Foo {
static getName() {
return "Foo";
}
getName() {
return "foo";
}
}
const foo = new Foo();
foo.getName(); // foo
Foo.prototype.getName(); // foo
Foo.getName(); // Foo如果静态方法包含this关键字,这个this指的是类,而不是实例
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log(`static`);
}
bar() {
console.log(`bar`);
}
}
Foo.bar(); // static父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return "hello";
}
}
class Bar extends Foo {}
Bar.classMethod(); // hello私有方法和私有属性
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。
- 一种做法是在命名上加以区别, 如
_geName - 将私有方法移出类,因为类内部的所有方法都是对外可见的。
- 利用
Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
new.target 属性
new是从构造函数生成实例对象的命令
该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class PersonClass {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
// 等价于
let PersonClass2 = (function () {
"use strict";
const PersonType = function (name) {
if (typeof new.target === "undefined") {
throw new Error(`必须使用new`);
}
this.name = name;
};
Object.defineProperty(PersonType.prototype, "sayName", {
configurable: true,
writable: true,
enumerable: true,
value: function () {
if (typeof new.target !== "undefined") {
throw new Error(`不能使用new`);
}
console.log(this.name);
},
});
return PersonType;
})();
const x = new PersonClass2("22");
x.sayName();Class 的继承
Class 可以通过extends关键字实现继承
子类必须在constructor方法中调用super方法,否则新建实例时会报错
父类的静态方法,也会被子类继承
Object.getPrototypeOf方法可以用来从子类上获取父类。
class Foo {}
class F extentds Foo {
constructor () {
super()
}
}
Object.getPrototypeOf(F) === Foo // truesuper 关键字
super作为函数调用时,代表父类的构造函数,但是返回的是子类B的实例。
子类的构造函数必须执行一次super函数,才能使用this关键字
super()只能用在子类的构造函数之中,用在其他地方就会报错。
class A {}
class B extends A {
constructor() {
super(); // 相当于 A.prototype.constructor.call(this)
}
}super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class Parent {
static myMethod(msg) {
console.log("static", msg);
}
myMethod(msg) {
console.log("instance", msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg); // 指向父类 Parent.
}
myMethod(msg) {
super.myMethod(msg); // 父类的原型对象 Parent.prototype
}
}
Child.myMethod(1); // static 1
const child = new Child();
child.myMethod(2); // instance 2Module 模块
ES6 模块是编译时加载,使得静态分析成为可能
// some.js 导出
export const first = 1;
const second = 2;
export { second };
// 导入
import { first, second } from "./some";as 别名
// some.js
export function mutiply(x, y) {
return x * y;
}
import { mutiply as muti } from "./some";
muti(2, 2); // 4import 是静态执行,不能使用表达式和变量
模块整体加载
用星号*指定一个对象,所有输出值都加载在这个对象上
// some.js
export Jack = 23
export Jenny = 22
// 整体加载
import * as Age from './some'
console.log(Age.Jack, Age.Jenny) // 23, 22export default 命令
// some.js
export default function muti (x, y) {
return x * y
}
export const num1 = 22
export const num2 = 2
import muti, { num1, num2 } fromn './some'Node.js 的模块加载方法
JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS。
CommonJS 模块使用require()和module.exports,ES6 模块使用import和export。
循环加载
循环加载”(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
Node.js 处理循环加载
CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被 "循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
// a.js
exports.done = false;
var b = require("./b.js");
console.log("在 a.js 之中,b.done = %j", b.done);
exports.done = true;
console.log("a.js 执行完毕");
// b.js
exports.done = false;
var a = require("./a.js");
console.log("在 b.js 之中,a.done = %j", a.done);
exports.done = true;
console.log("b.js 执行完毕");a.js执行完第一行后就等待b.js执行完毕,b.js执行第二行时又会加载a.js
a.js已经执行的部分:
exports.done = false;因此,对于b.js来说,它从a.js只输入一个变量done,值为false。
// main.js
var a = require("./a.js");
var b = require("./b.js");
console.log("在 main.js 之中, a.done=%j, b.done=%j", a.done, b.done);
// 在 b.js 之中,a.done = false
// b.js 执行完毕
// 在 a.js 之中,b.done = true
// a.js 执行完毕
// 在 main.js 之中, a.done=true, b.done=trueES6 处理循环加载
ES6 处理 “循环加载” 与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
// a.mjs
import { bar } from "./b";
console.log("a.mjs");
console.log(bar);
export let foo = "foo";
// b.mjs
import { foo } from "./a";
console.log("b.mjs");
console.log(foo);
export let bar = "bar";
// 输出: ReferenceError: foo is not defined首先,执行a.mjs以后,引擎发现它加载了b.mjs,因此会优先执行b.mjs,然后再执行a.mjs。接着,执行b.mjs的时候,已知它从a.mjs输入了foo接口,这时不会去执行a.mjs,而是认为这个接口已经存在了,继续往下执行。执行到第三行console.log(foo)的时候,才发现这个接口根本没定义,因此报错。