# ECMAScript - 新特性
// todo
ES2020-ES11 ES2020 是与 2020 年相对应的 ECMAScript 版本 String.protype.matchAll matchAll()方法返回一个正则表达式在当前字符串的所有匹配 不过,它返回的是一个遍历器(Iterator),而不是数组。遍历器转为数组是非常简单的,使用...运算符和 Array.from()方法就可以了。 const string = "test1test2test3"; const regex = /t(e)(st(\d?))/g;
const newdata = string.matchAll(regex);
for (const match of newdata) { console.log(match); } // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"] // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"] // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
// 转为数组的方法一 [...newdata];
// 转为数组的方法二 Array.from(newdata); 复制代码 详细内容参考ES 入门-matchAll Dynamic import import(specifier)函数,支持动态加载模块, import 函数的参数 specifier,指定所要加载的模块的位置。import 命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。 import()返回一个 Promise 对象 const someVariable = "user";
import(./some-modules/${someVariable}.js
)
.then((module) => {
// 业务逻辑
module.loadPageInto(main);
})
.catch((err) => {
// 加载失败
});
复制代码
详细内容参考ES 入门-import
Promise.allSettled
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
有时候,我们不关心异步请求的结果,只关心所有的请求有没有结束。这时,Promise.allSettled()方法就很有用
const promises = [fetch("index.html"), fetch("https://does-not-exist/")];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求 const successfulPromises = results.filter((p) => p.status === "fulfilled");
// 过滤出失败的请求,并输出原因 const errors = results .filter((p) => p.status === "rejected") .map((p) => p.reason); 复制代码 globalThis ES2020 之前获取不同环境的this需要如下封装 const getGlobalThis = () => { // 在 webworker 或 service worker 中 if (typeof self !== "undefined") return self;
// 在浏览器中 if (typeof window !== "undefined") return window;
// 在 Node.js 中 if (typeof global !== "undefined") return global;
// 独立的 JavaScript shell if (typeof this !== "undefined") return this;
throw new Error("Unable to locate global object"); }; const theGlobalThis = getGlobalThis();
if (typeof theGlobalThis.setTimeout !== "function") {
// 此环境中没有 setTimeout 方法!
}
复制代码
现在,globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)
if (typeof globalThis.setTimeout !== "function") {
// 此环境中没有 setTimeout 方法!
}
复制代码
详细内容参考MDN-globalThis
空位合并操作符(Nullish coalescing Operator)
在 JS 中,?? 运算符被称为非空运算符。如果第一个参数不是 null/undefined(这里只有两个假值,但是 JS 中假值包含:未定义 undefined、空对象 null、数值 0、空数字 NaN、布尔 false,空字符串'',不要搞混了),将返回第一个参数,否则返回第二个参数。比如,
null ?? 5; // => 5
3 ?? 5; // => 3
复制代码
给变量设置默认值时,以前常用 ||逻辑或运算符,例如,
const prevMoney = 1;
const currMoney = 0;
const noAccount = null;
const futureMoney = -1;
function moneyAmount(money) {
return money || 账户未开通
;
}
console.log(moneyAmount(prevMoney)); // => 1
console.log(moneyAmount(currMoney)); // => 账户未开通
console.log(moneyAmount(noAccount)); // => 账户未开通
console.log(moneyAmount(futureMoney)); // => -1
复制代码
上面我们创建了函数 moneyAmount,它返回当前用户余额。我们使用 || 运算符来识别没有帐户的用户。然而,当用户没有帐户时,这意味着什么?将无账户视为空而不是 0 更为准确,因为银行账户可能没有(或负)货币。在上面的例子中,|| 运算符将 0 视为一个虚假值,不应该包括用户有 0 美元的帐户。让我们使用?? 非空运算符来解决这个问题:
const currMoney = 0;
const noAccount = null;
function moneyAmount(money) {
return money ?? 账户未开通
;
}
moneyAmount(currMoney); // => 0
moneyAmount(noAccount); // => 账户未开通
复制代码
概括地说 ?? 运算符允许我们在忽略错误值(如 0 和空字符串)的同时指定默认值。
可选链操作符(Optional Chaining)
?. 也叫链判断运算符。它允许开发人员读取深度嵌套在对象链中的属性值,而不必验证每个引用。当引用为空时,表达式停止计算并返回 undefined。比如:
var travelPlans = {
destination: "DC",
monday: {
location: "National Mall",
budget: 200,
},
};
console.log(travelPlans.tuesday?.location); // => undefined
复制代码
现在,把我们刚刚学到的结合起来
function addPlansWhenUndefined(plans, location, budget) {
if (plans.tuesday?.location == undefined) {
var newPlans = {
plans,
tuesday: {
location: location ?? "公园",
budget: budget ?? 200,
},
};
} else {
newPlans ??= plans; // 只有 newPlans 是 undefined 时,才覆盖
console.log("已安排计划");
}
return newPlans;
}
// 对象 travelPlans 的初始值,来自上面一个例子
var newPlans = addPlansWhenUndefined(travelPlans, "Ford 剧院", null);
console.log(newPlans);
// => { plans:
// { destination: 'DC',
// monday: { location: '国家购物中心', budget: 200 } },
// tuesday: { location: 'Ford 剧院', budget: 200 } }
newPlans = addPlansWhenUndefined(newPlans, null, null);
// logs => 已安排计划
// returns => newPlans object
复制代码
上面的例子包含了我们到目前为止所学的所有运算符。现在我们已经创建了一个函数,该函数将计划添加到当前没有嵌套属性的对象 tuesday.location 中。我们还使用了非空运算符来提供默认值。此函数将错误地接受像“0”这样的值作为有效参数。这意味着 budget 可以设置为零,没有任何错误。
BigInt primitive type
旧版本的 JS 标准最大的整数只能是253 - 1, 现在使用BigInt 用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 这是 ECMAScript 的又一种数据类型。
可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991); // ↪ 9007199254740991n 复制代码
ES 入门-BigInt
ES2019-ES10 Array#{flat,flatMap} 数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。 [1, 2, [3, 4]].flat(); // [1, 2, 3, 4] 复制代码 flatMap()只能展开一层数组。 // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat() [1, 2, 3, 4].flatMap((x) => [[x * 2]]); // [[2], [4], [6], [8]] 复制代码 详细内容参考ES 入门-flat Object.fromEntries Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。 Object.fromEntries([ ["foo", "bar"], ["baz", 42], ]); // { foo: "bar", baz: 42 } 复制代码 该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。 // 例一 const entries = new Map([ ["foo", "bar"], ["baz", 42], ]);
Object.fromEntries(entries); // { foo: "bar", baz: 42 }
// 例二 const map = new Map().set("foo", true).set("bar", false); Object.fromEntries(map); // { foo: true, bar: false } 复制代码 String#{trimStart,trimEnd} ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。 const s = " abc ";
s.trim(); // "abc" s.trimStart(); // "abc " s.trimEnd(); // " abc" 复制代码 Symbol#description ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。 // 创建 Symbol 的时候,可以添加一个描述。 const sym = Symbol("foo");
sym.description; // "foo" 复制代码 上面代码中,sym 的描述就是字符串 foo。 try { } catch {} // optional binding 旧版本的try / catch语句中的catch子句需要一个变量。 现在可以不加了 // 旧版本 try { console.log(a); } catch (error) { console.log("报错了"); }
// ES2019-SE10 try { console.log(a); } catch { console.log("报错了"); } 复制代码 U+2028 和 U+2029 在 ES2019 之前的版本中,不接受不转义的
行分隔符U + 2028 段落分隔符U + 2029
ES2019 允许 JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。 /* ES2019之前,下面的代码会报错
ES2019 下面代码不会报错。 */ const PS = eval("'\u2029'"); 复制代码 ES 入门-U+2028 和 U+2029 JSON-stringify-的改造 为了确保返回的是合法的 UTF-8 字符,ES2019 改变了 JSON.stringify()的行为。如果遇到 0xD800 到 0xDFFF 之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。 JSON.stringify("\u{D834}"); // ""\uD834"" JSON.stringify("\uDF06\uD834"); // ""\udf06\ud834"" 复制代码 ES 入门-JSON-stringify-的改造 Array.prototype.sort() 的稳定排序 早先的 ECMAScript 没有规定,Array.prototype.sort()的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。ES2019 明确规定,Array.prototype.sort()的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。 const arr = ["peach", "straw", "apple", "spork"];
const stableSorting = (s1, s2) => { if (s1[0] < s2[0]) return -1; return 1; };
arr.sort(stableSorting); // ["apple", "peach", "straw", "spork"] 复制代码 ES 入门-排序稳定性 revised Function#toString ES2019 对函数实例的 toString()方法做出了修改。 toString()方法返回函数代码本身,以前会省略注释和空格。 function /* foo comment */ foo() {}
// 老版本 foo.toString(); // function foo() {}
// 新版 foo.toString(); // "function /* foo comment */ foo () {}" 复制代码
ES2018-ES9
解除模板字面量限制(Lifting template literal restriction).
ES2018 放松了对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串。
function tag(strs) {
strs[0] === undefined
strs.raw[0] === "\unicode and \u{55}";
}
tag\unicode and \u{55}
复制代码
上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了,JavaScript 引擎将第一个字符设置为undefined,但是raw属性依然可以得到原始字符串,因此tag函数还是可以对原字符串进行处理。
ES 入门-模板字符串的限制 ES 入门-row ES 入门-修饰符:u
正则之 s 修饰符:dotAll 模式-(s (dotAll) flag for regular expressions). ES2018 引入 s 修饰符,使得.可以匹配任意单个字符。 /foo.bar/s.test("foo\nbar"); // true 复制代码 这被称为dotAll模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。 ES 入门-修饰符:dotAll 模式 正则之具名组匹配(RegExp named capture groups) ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec("1999-12-31");
const year = matchObj.groups.year; // "1999"
const month = matchObj.groups.month; // "12"
const day = matchObj.groups.day; // "31"
2
3
4
5
ES 入门-修饰符:具名组匹配 Rest/Spread Properties. ES6 为数组引入了扩展运算符的写法, 在 ES2018 中,为对象也引入了此写法 const obj = { a: "a", b: "b", c: "c", d: "d", e: "e" };
// 对象结构 const { a, b, c, ...rest } = obj;
// 组成新对象 const newObj = { a, ...rest }; 复制代码 正则之后行断言(RegExp Lookbehind Assertions.) ES2018 引入后行断言 “后行断言”指: x只有不在y后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!$)\d+/。 /(?<=$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"] /(?<!$)\d+/.exec('it’s is worth about €90') // ["90"] 复制代码 使用后行断言进行字符串替换。 const RE_DOLLAR_PREFIX = /(?<=$)foo/g; "$foo %foo foo".replace(RE_DOLLAR_PREFIX, "bar"); // '$bar %foo foo' 复制代码 ES 入门-后行断言 Unicode 属性类(RegExp Unicode Property Escapes) ES2018 引入了一种新的类的写法\p{...}和\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。 const regexGreekSymbol = /\p{Script=Greek}/u; regexGreekSymbol.test("π"); // true
// 匹配所有空格 const reg = /\p{White_Space}/;
// 匹配所有的箭头字符 const regexArrows = /^\p{Block=Arrows}+$/u; regexArrows.test("←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩"); // true 复制代码 ES 入门-Unicode 属性类 Promise.prototype.finally. finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 promise .then(result => {···}) .catch(error => {···}) .finally(() => {···}); 复制代码 上面代码中,不管 promise 最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。 ES 入门-finally 按顺序完成异步操作(Asynchronous Iteration) 实际开发中,经常遇到一组异步操作,需要按照顺序完成。比如,依次远程读取一组 URL,然后按照读取的顺序输出结果。 async function logInOrder(urls) { // 并发读取远程URL const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); });
// 按次序输出 for (const textPromise of textPromises) { console.log(await textPromise); } } 复制代码 async function getData() { const promises = [fetch("url1"), fetch("url2"), fetch("url3"), fetch("url4")]; for (const item of promises) { // 打印出promise console.log(item); }
for await (const item of promises) { // 打印出请求的结果 console.log(item); } } 复制代码 ES 入门-顺序异步操作
ES2017-ES8 Object.values/Object.entries Object.values 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。 const obj = { foo: "bar", baz: 42 }; Object.values(obj); // ["bar", 42]
const obj = { 100: "a", 2: "b", 7: "c" };
Object.values(obj);
// ["b", "c", "a"]
复制代码
Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: "bar", baz: 42 };
Object.entries(obj);
// [ ["foo", "bar"], ["baz", 42] ]
复制代码
Object.entries 的基本用途是遍历对象的属性。
let obj = { one: 1, two: 2 };
for (let [k, v] of Object.entries(obj)) {
console.log(${JSON.stringify(k)}: ${JSON.stringify(v)}
);
}
// "one": 1
// "two": 2
复制代码
Object.entries 方法的另一个用处是,将对象转为真正的 Map 结构。
const obj = { foo: "bar", baz: 42 };
const map = new Map(Object.entries(obj));
map; // Map { foo: "bar", baz: 42 }
复制代码
String padding
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
"x".padStart(5, "ab"); // 'ababx'
"x".padStart(4, "ab"); // 'abax'
"x".padEnd(5, "ab"); // 'xabab' "x".padEnd(4, "ab"); // 'xaba' 复制代码 padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。 "1".padStart(10, "0"); // "0000000001" "12".padStart(10, "0"); // "0000000012" "123456".padStart(10, "0"); // "0000123456" 复制代码 另一个用途是提示字符串格式。 "12".padStart(10, "YYYY-MM-DD"); // "YYYY-MM-12" "09-12".padStart(10, "YYYY-MM-DD"); // "YYYY-09-12" 复制代码 Object.getOwnPropertyDescriptors ES2017 引入了 Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。
value — 属性实际的值 writable — 属性的值是否可以被修改 get — 获取函数,在读取属性时调用 set — 设置函数,在写入属性时调用 configurable — 属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性 enumerable — 属性是否可以通过 for-in 循环返回
const obj = { foo: 123, get bar() { return "abc"; }, };
Object.getOwnPropertyDescriptors(obj); // { foo: // { value: 123, // writable: true, // enumerable: true, // configurable: true }, // bar: // { get: [Function: get bar], // set: undefined, // enumerable: true, // configurable: true } } 复制代码 该方法的引入目的,主要是为了解决 Object.assign()无法正确拷贝 get 属性和 set 属性的问题。 Object.getOwnPropertyDescriptors()方法的另一个用处,是配合 Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝。 const shallowClone = (obj) => Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj), ); 复制代码 更多详细内容参考ES 入门教程-getOwnPropertyDescriptors 函数参数的尾逗号 ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。 此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。 function clownsEverywhere(param1, param2,) { /* ... */ }
clownsEverywhere("foo", "bar",); 复制代码 更多详细内容参考ES 入门教程-函数参数的尾逗号 异步函数(Async functions) ES2017 标准引入了 async 函数,使得异步操作变得更加方便。 async 函数是什么?一句话,它就是 Generator 函数的语法糖。 function fakeRequest() { return new Promise((resolve, reject) => { setTimeout(() => { resolve("请求成功"); }, 2000); }); }
async function getData() { console.log("start"); const res = await fakeRequest(); console.log(res); console.log("end"); } getData(); /* 1.start 2.请求成功 3.end */ 复制代码 使用 Atomics 共享内存 Atomics 对象提供了一组静态方法对 SharedArrayBuffer 和 ArrayBuffer 对象进行原子操作。 更多详细内容参考MDN-Atomics
ES2016-ES7 Array.prototype.includes Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。 [1, 2, 3] .includes(2) // true [(1, 2, 3)].includes(4) // false [(1, 2, NaN)].includes(NaN); // true 复制代码 求幂运算符(Exponentiation operator) // 2的平方 2 ** 2; // 4 // 2的三次方 2 ** 3; // 8 复制代码 更多详细内容参考ES 入门教程-指数运算符
ES2015-ES6 推荐阮一峰大佬的ES 入门教程,中文文档没有比他更详细的了 箭头函数(arrows) 箭头函数是使用=>语法的函数简写。与一般函数不同的是
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
this 对象的指向是可变的,但是在箭头函数中,它是固定的。
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
var f = (v) => v;
// 等同于 var f = function (v) { return v; };
function foo() { setTimeout(() => { console.log("id:", this.id); }, 100); }
var id = 21; // 箭头函数导致this总是指向函数定义生效时所在的对象({id: 42}),所以打印出来的是42 foo.call({ id: 42 }); // id: 42
// 对象不构成单独的作用域,使得this指向全局对象 globalThis.s = 21; const obj = { s: 42, m: () => console.log(this.s), };
obj.m(); // 21 复制代码 更多详细内容参考ES 入门教程-箭头函数 类(Class) // ES5 function Point(x, y) { this.x = x; this.y = y; }
Point.prototype.toString = function () { return "(" + this.x + ", " + this.y + ")"; };
var p = new Point(1, 2);
// ES6 class Point { constructor(x, y) { this.x = x; this.y = y; }
toString() { return "(" + this.x + ", " + this.y + ")"; } } 复制代码 更多详细内容参考ES 入门教程-Class 对象的扩展(enhanced object literals) 对象的属性的简洁表示法 const foo = "bar"; const method = function () { return "Hello!"; };
const filed = "name";
const baz = { foo, method, [filed]: "小王", };
// 等同于
const baz = {
foo: foo,
method: function () {
return "Hello!";
},
name: "小王",
};
复制代码
更多详细内容参考ES 入门教程-对象扩展
模板字符串
// 字符串中嵌入变量
let name = "Bob",
time = "today";
Hello ${name}, how are you ${time}?
;
复制代码
更多详细内容参考ES 入门教程-字符串模板
数组解构+扩展运算符
var [a] = [];
a === undefined; // true
var [a = 1] = []; a === 1; // true 复制代码 更多详细内容参考ES 入门教程-数组的扩展运算符 函数默认参数+剩余参数+扩展运算符 //如果没有传递y 或者y===undefined ,则y=12 function f(x, y = 12) { return x + y; } f(3) == 15; 复制代码 function f(x, ...y) { // y 是一个数组 return x * y.length; } f(3, "hello", true) == 6; 复制代码 function f(x, y, z) { return x + y + z; } // Pass each elem of array as argument f(...[1, 2, 3]) == 6; 复制代码 更多详细内容参考ES 入门教程-函数默认参数 块级作用域变量 随着 ES6 中引入 let/const 关键字,JS 才具有函数作用域和全局作用域,现在 JS 也可以有块级作用域了。 function f() { { let x; { // 正常,因为在一个新的块级作用域中 const x = "sneaky"; // const 定义的是常量无法被修改,因此会报错 x = "foo"; } // 在块级作用域中已声明x,因此会报错 let x = "inner"; } } 复制代码 更多详细内容参考ES 入门教程-unicode 遍历/迭代器+for..of(iterators + for..of) 一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员。也就是说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。 for ... of是for ... in和forEach()的替代方法,它循环访问可迭代的数据结构,如数组,映射,集合和字符串。 JavaScript 原有的 for...in 循环,只能获得对象的键名,不能直接获取键值。ES6 提供 for...of 循环,允许遍历获得键值。 var arr = ["a", "b", "c", "d"];
for (let a in arr) { console.log(a); // 0 1 2 3 }
for (let a of arr) { console.log(a); // a b c d }
const str = "helloworld"; for (let a of str) { console.log(a); // h e l l o w o r l d } 复制代码 更多详细内容参考ES 入门教程-iterators 生成器(generators) Generators 使用function *和yield简化了迭代器的创建。 声明为function 的函数一个遍历器对象,也就是说,Generator 函数是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 生成器是迭代器的子类型,因此具有next和throw方法。 yield表达式是暂停执行的标记,而next方法可以恢复执行 注意:ES7 出现后,推荐使用await。 function foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; }
for (let v of foo()) { console.log(v); } // 1 2 3 4 5 复制代码 下面是一个利用 Generator 函数和for...of循环,实现斐波那契数列的例子。 var fibonacci = { [Symbol.iterator]: function* () { let [prev, curr] = [0, 1]; for (;😉 { yield curr; [prev, curr] = [curr, prev + curr]; } }, };
for (var n of fibonacci) { // if (n > 1000) break; console.log(n); } 复制代码 从上面代码可见,使用for...of语句时不需要使用next方法。 利用for...of循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有迭代器接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。 生成器(Generator) 实质上继承了迭代器(Iterator) interface Generator extends Iterator { next(value?: any): IteratorResult; throw(exception: any); } 复制代码 更多详细内容参考ES 入门教程-iterators Unicode ES6 增强了 Unicode 的功能,包括
支持字符的 Unicode 表示法
举例来说,“中”的 Unicode 码点是 U+4e2d,你可以直接在字符串里面输入这个汉字,也可以输入它的转义形式\u4e2d,两者是等价的。 "中" === "\u4e2d"; // true 复制代码
使用/u匹配码点的正则表达式
// new RegExp behaviour, opt-in ‘u’ "𠮷".match(/./u)[0].length == 2; 复制代码
获取 32 位的 UTF-16 字符的码点-codePointAt
"𠮷".codePointAt(0) == 0x20bb7;
let s = "𠮷a"; for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61 复制代码 更多详细内容参考ES 入门教程-unicode 模块化(modules) ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。 使用 export default 或 export 进行导出 // math.js export const pi = 3.141593;
export default function sum(x, y) { return x + y; } 复制代码 使用 import 进行导入 // app.js import sum, { pi } from "./math";
alert("2π = " + sum(pi, pi)); 复制代码 更多详细内容参考ES 入门教程-module 模块加载器规则(module loaders) 模块加载器支持:
异步加载 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。 模块之中,顶层的 this 关键字返回 undefined,而不是指向 window。也就是说,在模块顶层使用 this 关键字,是无意义的
//index.js const x = 1;
console.log(x === window.x); //false console.log(this === undefined); // true 复制代码 利用顶层的 this 等于 undefined 这个语法点,可以侦测当前代码是否在 ES6 模块之中。 const isNotModuleScript = this !== undefined; 复制代码 更多详细内容参考ES 入门教程-module-loader import and export Map + Set + Weakmap + Weakset ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 // Sets var s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true; 复制代码 ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。 // Maps var m = new Map(); m.set("hello", 42); m.set(s, 34); m.get(s) == 34; 复制代码 WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。 WeakMap 与 Map 的区别有两点。
WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名。 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
// Weak Maps var wm = new WeakMap(); wm.set(s, { extra: 42 }); wm.size === undefined; 复制代码 WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
WeakSet 的成员只能是对象,而不能是其他类型的值。 WeakSet 中的对象都是弱引用
// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set
复制代码
更多详细内容参考ES 入门教程-Set 和 Map
代理(proxies)
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改。 可以用于操作拦截,日志记录/分析等。
// 代理一个普通对象
var target = {};
var handler = {
get: function (receiver, name) {
return Hello, ${name}!
;
},
};
var p = new Proxy(target, handler);
// true p.world === "Hello, world!"; 复制代码 下面是 Proxy 所有可以代理的"元操作" var handler = { get:..., set:..., has:..., deleteProperty:..., apply:..., construct:..., getOwnPropertyDescriptor:..., defineProperty:..., getPrototypeOf:..., setPrototypeOf:..., enumerate:..., ownKeys:..., preventExtensions:..., isExtensible:... } 复制代码 MDN-handler.get() // 代理一个函数对象 var target = function () { return "I am the target"; }; var handler = { apply: function (receiver, ...args) { return "I am the proxy"; }, };
var p = new Proxy(target, handler); //true p() === "I am the proxy"; 复制代码 更多详细内容参考ES 入门教程-proxy symbols ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值 Symbol 值通过 Symbol 函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 var MyClass = (function () { // var key = Symbol("key");
function MyClass(privateData) { this[key] = privateData; }
MyClass.prototype = { doStuff: function () { this[key]; }, };
return MyClass; })();
var c = new MyClass("hello"); // true console.log(c["key"] === undefined); 复制代码 创建 Symbol 的时候,可以添加一个描述。 const sym = Symbol("foo"); 复制代码 上面代码中,sym 的描述就是字符串 foo。 Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。 但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。 const obj = {}; let a = Symbol("a"); let b = Symbol("b");
obj[a] = "Hello"; obj[b] = "World";
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols; // [Symbol(a), Symbol(b)] 复制代码 更多详细内容参考ES 入门教程-symbol 期约(promises) Promise 是一个用于异步编程的库,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。 许多现有的 JavaScript 库已经使用了 Promise。 function timeout(duration = 0) { return new Promise((resolve, reject) => { setTimeout(resolve, duration); }); }
var p = timeout(1000) .then(() => { return timeout(2000); }) .then(() => { throw new Error("hmm"); }) .catch((err) => { return Promise.all([timeout(100), timeout(200)]); }); 复制代码 更多详细内容参考ES 入门教程-promise math + number + string + array + object APIs 添加了许多类型的扩展方法,包括:Math ,Array ,String ,Object Number.EPSILON; Number.isInteger(Infinity); // false Number.isNaN("NaN"); // false
Math.acosh(3); // 1.762747174039086 Math.hypot(3, 4); // 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2); // 2
"abcde".includes("cd"); // true "abc".repeat(3); // "abcabcabc"
Array.from(document.querySelectorAll("*")); // Returns a real Array Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior [(0, 0, 0)].fill(7, 1) // [0,7,7] [(1, 2, 3)].find((x) => x == 3) // 3 [(1, 2, 3)].findIndex((x) => x == 2) // 1 [(1, 2, 3, 4, 5)].copyWithin(3, 0) // [1, 2, 3, 1, 2] [("a", "b", "c")].entries() // iterator [0, "a"], [1,"b"], [2,"c"] [("a", "b", "c")].keys() // iterator 0, 1, 2 [("a", "b", "c")].values(); // iterator "a", "b", "c"
Object.assign(Point, { origin: new Point(0, 0) }); 复制代码 更多详细内容参考 ES 入门教程:
Number Math, Array.from Array.of Array.prototype.copyWithin Object.assign
二进制和八进制(binary and octal literals) 两种新的数字表示形式。
二进制: 0b 开头 八进制: 0o 开头
0b111110111 === 503; // true 0o767 === 503; // true 复制代码 reflect api reflect API 公开对象上的运行时级别的元操作。 最重要的目的是配合 Proxy 使用,执行原生行为 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。 // 老写法 "assign" in Object; // true
// 新写法 Reflect.has(Object, "assign"); // true 复制代码 更多详细内容参考ES 入门教程-reflect 尾调用(tail calls)
尾调用:某个函数的最后一步是返回并调用另一个函数 尾递归:函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 尾调用优化
注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。这里就不深入研究了 😁 function factorial(n, acc = 1) { if (n <= 1) return acc; return factorial(n - 1, n * acc); }
// 大多数浏览器中都会出现 堆栈溢出 的错误, // 但是在 ES6的Safari中是安全的 factorial(100000); 复制代码 更多详细内容参考ES 入门教程-尾调用 通过 Intl API 对字符串,数字和日期进行国际化 Intl 对象是 ECMAScript 国际化 API 的命名空间,它提供对语言敏感的字符串比较、支持数字格式化以及日期和时间的格式化。 Intl.Collator 对象 collator 这个单词意思是排序器。Intl.Collator 对象是排序器的构造函数,可以支持对语言敏感的字符串比较。
中文排序
如果我们希望我们的中文按照首字母拼音排序,该怎么处理? 此时,可以使用中文简体的 BCF 47 语言标记字符串 zh 进行排序,代码如下: var arrUsername = [ "陈坤", "邓超", "杜淳", "冯绍峰", "韩庚", "胡歌", "黄晓明", "贾乃亮", "李晨", "李易峰", "鹿晗", "井柏然", "刘烨", "陆毅", "孙红雷", ];
arrUsername.sort(new Intl.Collator("zh").compare); // 结果是:["陈坤", "邓超", "杜淳", "冯绍峰", "韩庚", "胡歌", "黄晓明", "贾乃亮", "井柏然", "李晨", "李易峰", "刘烨", "陆毅", "鹿晗", "孙红雷"] 复制代码 Intl API详细可以参考这篇文章JS Intl 对象完整简介及在中文中的应用
ES2011-ES5 相信大家已经对 ES5 都了然于胸,因此只做简单罗列,就不举例说明了 'USE STRICT' JS 的早期版本允许使用未声明的变量。 但是当使用 es5 "use strict"功能时,会报告错误 // index.js "use strict";
// 报错:a is not defined a = 22; 复制代码 Array Array.isArray Array.forEach Array.map Array.filter Array.reduce Array.reduceRight Array.every Array.some Array.indexOf Array.lastIndexOf JSON JSON.parse JSON.stringify DATE Date.now() Date.now().valueOf() Object.defineProperty()
参考文档
ECMAScript 6 Features es6-features.org ES2021 Features with simple examples 4 个强大 JavaScript 运算符 ES6 核心特性