本文以倒序的顺序并通过代码示例或简单的罗列展示所有 ECMAScript 版本提供的功能。 旨在为大家在编码时提供 ECMAScript 特性速查表
ES2021-ES12
String.protype.replaceAll
在 ES2021 之前,要替换掉一个字符串中的所有指定字符,我们可以这么做:
const str = "a+b+c+";
const newStr = str.replace(/\+/g, "?");
console.log(newStr); //a?b?c?
ES2021 则提出了 replaceAll
方法,并将其挂载在 String 的原型上,可以这么用:
const str = "a+b+c+";
const newStr = str.replaceAll("+", "?");
console.log(newStr); //a?b?c?
Promise.any
Promise.any
- 接收一个 Promise 可迭代对象,只要其中任意一个 promise 成功,就返回那个已经成功的 promise
- 如果所有的 promises 都失败/拒绝,就返回一个失败的 promise
Promise.race
的对比:
- 只要任意一个 promise 的状态改变(不管成功 or 失败),那么就返回那个 promise
Promise.all()
的对比
- 只要任意一个 promise 失败,则返回失败的 promise
- 当所有异步操作都成功后,才返回 promise,返回值组成一个数组
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
// 使用 .then .catch
Promise.any([pErr, pSlow, pFast])
.then((value) => {
// 返回最先成功的一个promise ,即: pFast-"很快完成"
console.log(value);
})
.catch((err) => {
// 所有的 promise 都失败时触发
});
// 使用 async-await
try {
const first = await Promise.any(promises); // 任何一个 promise 成功返回。
console.log(first);
} catch (error) {
// 所有的 promise 都失败了
console.log(error);
}
WeakRef
WeakRef
提案主要包含两个新功能:
- 可以通过
WeakRef
类来给某个对象创建一个弱引用 - 可以通过
FinalizationRegistry
类,在某个对象被垃圾回收之后,执行一些自定义方法
上述两个新功能可以同时使用,也可以单独使用,取决于你的需求。一个 WeakRef
对象包含一个对于某个对象的弱引用,被称为目标或引用。通过弱引用一个对象,可以让该对象在没有其它引用的情况下被垃圾回收机制回收。WeakRef
主要用来缓存和映射一些大型对象,当你希望某个对象在不被其它地方引用的情况下及时地被垃圾回收,那么你就可以使用它。
function toogle(element) {
const weakElement = new WeakRef(element);
let intervalId = null;
function toggle() {
const el = weakElement.deref();
if (!el) {
return clearInterval(intervalId);
}
const decoration = weakElement.style.textDecoration;
const style = decoration === "none" ? "underline" : "none";
decoration = style;
}
intervalId = setInterval(toggle, 1000);
}
const element = document.getElementById("link");
toogle(element);
setTimeout(() => element.remove(), 10000);
FinalizationRegistry
接收一个注册器回调函数,可以利用该注册器为指定对象注册一个事件监听器,当这个对象被垃圾回收之后,会触发监听的事件,具体步骤如下。首先,创建一个注册器:
const registry = new FinalizationRegistry((heldValue) => {
// ....
});
接着注册一个指定对象,同时也可以给注册器回调传递一些参数:
registry.register(theObject, "some value");
逻辑赋值运算符
详细信息参考ts39-proposal-logical-assignment
逻辑赋值运算符结合了逻辑运算符和赋值表达式。逻辑赋值运算符有两种:
- 或等于(
||=
) - 且等于(
&&=
) ??=
||=
const giveKey = () => {
return "somekey";
};
let userDetails = { name: "chika", age: 5, room: 10, key: "" };
userDetails.key ||= giveKey();
console.log(userDetails.key);
//output : somekey
&&=
const deleteKey = () => {
return " ";
};
let userDetails = { name: "chika", age: 5, room: 10, key: "990000" };
userDetails.key &&= deleteKey();
console.log(userDetails.key);
//output : ""
??= 空赋值运算符
??=
也被称为空赋值运算符,与上面的非空运算符相关。看看它们之间的联系:
var x = null;
var y = 5;
console.log((x ??= y)); // => 5
console.log((x = x ?? y)); // => 5
仅当值为 null
或 undefined
时,此赋值运算符才会赋值。上面的例子强调了这个运算符本质上是空赋值的语法糖(类似的语法糖:a = a + b
可写成 a += b
)。接下来,让我们看看这个运算符与默认参数(默认参数是 ES6 引入的新语法,仅当函数参数为 undefined
时,给它设置一个默认值)的区别:
function gameSettingsWithNullish(options) {
options.gameSpeed ??= 1;
options.gameDiff ??= "easy";
return options;
}
function gameSettingsWithDefaultParams(gameSpeed = 1, gameDiff = "easy") {
return { gameSpeed, gameDiff };
}
gameSettingsWithNullish({ gameSpeed: null, gameDiff: null }); // => {gameSpeed: 1, gameDiff: 'easy'}
gameSettingsWithDefaultParams(undefined, null); // => {gameSpeed: null, gameDiff: null}
上述函数处理空值的方式有一个值得注意的区别。默认参数将用空参数(这里的空参数,只能是 undefined
)覆盖默认值,空赋值运算符将不会。默认参数和空赋值都不会覆盖未定义的值。MDN 官方文档
const getKey = () => {
return "somekey";
};
let userDetails = { name: "chika", age: 5, room: 10 };
userDetails.key ??= getKey();
console.log(userDetails.key);
//output : "somekey"
数字分隔符
通过这个功能,我们利用 \_,U+005F
分隔符来将数字分组,提高数字的可读性:
1_000_000_000; // 十亿
101_475_938.38; // 亿万
const amount = 12345_00; // 12,345
const amount = 123_4500; // 123.45 (保留 4 位小数)
const amount = 1_234_500; // 1,234,500
0.000_001; // 百万分之一
1e10_000; // 10^10000
//
const binary_literals = 0b1010_0001_1000_0101;
const hex_literals = 0xa0_b0_c0;
//
const bigInt_literals = 1_000_000_000_000n;
//
const octal_literal = 0o1234_5670;
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"
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“严格使用”功能时,会报告错误
// 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 核心特性
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!