前言
Javascript 是前端位置最重要的三个编程之一。然而,作者 Brendan Eich 只用了 10 天时间。它在技术上对网站有很大帮助,但它也导致了许多问题的修复,这就是为什么从 ES6(2015) 到 ES12(2021) 诞生以修复更好的 JavaScript。
JavaScript 缺陷示例
虽然有 TypeScript 可以帮助避免 JavaScript 的一些缺陷,但根本问题仍然是 JavaScript。所以现在有一些新的脚本来支持 ES6 ~ ES12 的软件工程师。
ECMAScript
ECMA 规范由各方组成,包括浏览器供应商,他们开会推动 JavaScript 提案。
ES6 (ES2015)
1. class
JavaScript 是一种使用原型链的语言
早期,类似OO的概念是通过原型链做出来的。写作相当复杂。 class 终于在 ES6 中推出
class Animal {
constructor(name, color) {
this.name = name;
this.color = color;
}
toString() {
console.log('name:' + this.name + ', color:' + this.color);
}
}
var animal = new Animal('myDog', 'yellow'); // instantiate
animal.toString(); // name: myDog, color: yellow
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
2. 模块
每个模块都有自己的命名空间,为避免冲突,使用import和export来导入导出。
基本上将 .js 文件视为一个模块。
3.箭头功能
() => {…},函数的缩写,最重要的是,他可以确保这始终指向自己。
不再写 var self = this、var that = this 等等
const add = (a, b) => { return a + b};
const res = add(1, 2); // 3
const minus = (a, b) => a - b;
const res1 = minus(3, 1); // 2
4.函数参数默认值
如果函数不传递参数,则使用默认值。更简洁的写作。
function example(height = 50, width = 40) {
const newH = height * 10;
const newW = width * 10;
return newH + newW;
}
example(); // 900 (50*10 + 40*10)
5. 模板字面量
过去,长字符串的组合是通过 + 连接的。
它的可读性很差。使用模板字符串,它更容易阅读。
const firstName = 'Ken';
const lastName = 'Huang';
const name = 'Hello, My name is' + firstName + ', ' + lastName;
const nameWithLiteralString = `Hello, My name is ${firstName}, ${lastName}`;
6.解构赋值
允许 JavaScript 轻松地从数组和对象中获取内容。
const arr = [1, 2, 3, 4, 5];
const [one, two, three] = arr;
console.log(one); // 1
console.log(two); // 2
console.log(three); // 3
const [first,,,,last] = arr;
console.log(first); // 1
console.log(last); // 5
const student = {
name: 'Ken Huang',
age: 38,
city: 'Taipei'
};
// 即使乱序也不会影响数据引用
const {name, age, city} = student;
console.log(name); // "Ken Huang"
console.log(age); // "38"
console.log(city); // "Taipei"
7.扩展运算符
也就是……,Array是可以扩展的,如果是Object,会按照key-value进行扩展。
const stuendts = ['Angel', 'Ryan'];
const people = ['Sara', ...stuendts, 'Kelly', 'Eason'];
conslog.log(people); // ["Sara", "Angel", "Ryan", "Kelly", "Eason"]
8. 对象属性简写
如果构成对象的字段名称与前面段落中的变量相同,则可以省略该值。看起来更流线型。
Before ES6
const customer = {
name: name,
age: age,
city: city
}
After ES6
const newCustomer = {
name,
age,
city
}
9. promise
Promise 是一种异步(非同步)写法的解决方案,比原来的回调写法更加优雅。
早期是开源社区的套件,后来被纳入语言标准。
早期回调地狱……
JavaScript 的回调地狱
使用 Promise 后,回调地狱扁平化
const waitSecond = new Promise((resolve, reject) => {
setTimeout(resolve(99), 1000);
});
const res = waitSecond.then( (res) => {
return res;
}).then( data => {
return data+1 // 100
})
并且ES8(ES2017)发布了更完美的async-await(原理:Generator+Promise),直接让异步写得像同步一样。
缺点是当思路落到复杂的业务逻辑上时,有时会错过await,在运行时发现错误。
10. let, const 替换 var
let:通用变量,可以被覆盖
const:一旦声明,其内容不可修改。因为数组和对象都是指标,所以它们的内容可以增加或减少。但不改变其指标
早期,js的var作用域是全局的。
也就是说,变量是在使用后声明的。执行的时候会自动提到顶层,后面会赋值。
更容易受到污染。
console.log(a); // undefined var a = 10;
使用 let 或 const
console.log(a); // ReferenceError: Cannot access 'a' before initialization let a = 10;
ES7 (ES2016)
1. Array.prototype.includes()
用于判断数组是否包含指定值,如果是,则返回true;否则,返回假。
和之前indexOf的用法一样,可以认为是返回一个布尔值,语义上更加清晰。
const arr = [1, 2, 3, 4, 5];
arr.include(3); // true
if (arr.include(3)) { ... }
arr.indexOf(3); // 2
if (arr.indexOf(3) > -1) { ... }
2. 幂运算符
console.log(2**10); // 1024
console.log(Math.pow(2, 10)); // 1024
ES8 (ES2017)
1. 异步,等待
异步函数是使用 async 关键字声明的函数,并且允许在其中使用 await 关键字。 async 和 await 关键字使异步的、基于 Promise 的行为能够以更简洁的方式编写,避免了显式配置 Promise 链的需要。
async test() {
try {
const result = await otherAsyncFunction();
console.log(result);
} catch(e) {
console.log(e);
}
}
2. Object.values()
返回对象自身属性的所有值,不包括继承的值。(for in会遍历继承的可枚举属性)
const exampleObj = {a: 1, b: 2, c: 3, d:4};
console.log(Object.value(exampleObj)); // [1, 2, 3, 4];
// before
const values = Object.keys(exampleObj).map(key => exampleObj[key]);
3. Object.entries()
返回可枚举键,即传入对象本身的值。
const exampleObj = {a: 1, b: 2, c: 3, d:4};
console.log(Object.entries(exampleObj)); // [["a", 1], ["b", 2], ["c", 3], ["d", 4]];
// Usually
for (const [key, value] of Object.entries(exampleObj)) {
console.log(`key: ${key}, value: ${value}`);
}
// key: a, value: 1
// key: b, value: 2
// key: c, value: 3
// key: d, value: 4
4. 字符串 padStart() & padEnd()
您可以在字符串的开头或结尾添加其他内容,并将其填充到指定的长度。
过去,这些功能通常是通过通用的辅助工具包(如 lodash)引入的,并将它们放在一起。
最常用的情况应该是金额,填写指定长度,不足加0。
// padStart
'100'.padStart(5, 0); // '00100'
'100'.padStart(5, '987'); // '98100'
// padEnd
'100'.padEnd(5, 9); // '10099'
'100'.padStart(5, '987'); // '10098'
5.尾随逗号
允许在函数参数列表末尾使用逗号
const test = [ "foo", "baz", "bar", "baz", ]
6. Object.getOwnPropertyDescriptors()
获取您自己的描述符。一般的开发业务需求通常不会用到。
const exampleObj = {a: 1, b: 2, c: 3, d:4};
Object.getOwnPropertyDescriptors(exampleObj);
// {a: {…}, b: {…}, c: {…}, d: {…}}
// a: {value: 1, writable: true, enumerable: true, configurable: true}
// b: {value: 2, writable: true, enumerable: true, configurable: true}
// c: {value: 3, writable: true, enumerable: true, configurable: true}
// d: {value: 4, writable: true, enumerable: true, configurable: true}
// __proto__: Object
7. 共享数组缓冲区
SharedArrayBuffer 是一个固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer。
可用于在共享内存上创建数据。与 ArrayBuffer 不同,SharedArrayBuffer 不能分离。
/**
* @param length size,unit byte
* @returns {SharedArrayBuffer} the default value is 0。
*/
const sab = new SharedArrayBuffer(length);
8. 原子对象
Atomics 对象,它提供了一组静态方法来对 SharedArrayBuffer 执行原子操作。
原子的所有属性和函数都是静态的,就像数学一样。出不来新的。
如果一个多线程同时在同一个位置读写数据,原子操作保证了正在操作的数据如预期的那样:即在上一个子操作结束后执行下一个。操作不中断
可以说是针对Node.Js中多线程Server的开发而加强的功能,在前端开发中使用的机会相当低。
chrome 已经提供了支持
ES9 (ES2018)
1. 循环等待
在异步函数中,有时需要在同步 for 循环中使用异步(非同步)函数。
async function process(array) {
for (const i of array) {
await doSomething(i);
}
}
async function process(array) {
array.forEach(async i => {
await doSomething(i);
});
}
上面的代码不会像预期的那样输出期望的结果
for循环本身还是同步的,会在循环中的异步函数完成之前执行整个for循环,然后将里面的异步函数一一执行。
ES9 增加了异步迭代器,允许 await 与 for 循环一起使用,逐步执行异步操作。
async function process(array) {
for await (const i of array) {
doSomething(i);
}
}
2.promise.finally()
无论是成功(.then())还是失败(.catch()),Promise 后面都会执行的部分。
function process() {
process1()
.then(process2)
.then(process3)
.catch(err => {
console.log(err);
})
.finally(() => {
console.log(`it must execut no matter success or fail`);
});
}
3.剩余&扩展
在 ES2015中,剩下的不确定长度参数通过... 可以转换成一个数组并传入。
function restParams(p1, p2, ...p3) {
console.log(p1); // 1
console.log(p2); // 2
console.log(p3); // [3, 4, 5]
}
restParams(1, 2, 3, 4, 5);
并且扩展运算正好于剩余操作相反,将数组转换为一个个单独的参数。
例如:Math.max ()返回传入数字中的最大值
const values = [19, 90, -2, 6, 25];
console.log( Math.max(...values) ); // 90
它还提供了对象解构赋值的功能。
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...r } = myObject;
// a = 1
// r = { b: 2, c: 3 }
// Can also be used in function input parameters
function restObjectInParam({ a, ...r }) {
console.log(a); // 1
console.log(r); // {b: 2, c: 3}
}
restObjectInParam({
a: 1,
b: 2,
c: 3
});
4.正则分组
RegExp可以返回匹配的部分
const regExpDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const match = regExpDate.exec('2020-06-25');
const year = match[1]; // 2020
const month = match[2]; // 06
const day = match[3]; // 25
5.Regexp dotAll
.表示匹配任何一个字符除了回车(\n),正则后加s标志即可匹配换行符
/hello.world/.test('hello\nworld'); // false
/hello.world/s.test('hello\nworld'); // true
ES10 (ES2019)
1.更加友好的JSON.stringify
最初,如果输入的字符不是 Unicode 字符,那么 JSON.stringify 将返回一个错乱的 Unicode 字符串。
现在,第3阶段的建议是使其成为有效的 Unicode 并以 UTF-8编码返回
2.Array.prototype.flat() & Array.prototype.flatMap()
数组打平
const arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat(); // [1, 2, 3, 4, [5, 6]]
// Pass in a number in flat, representing the flattening depth
arr2.flat(2); // [1, 2, 3, 4, 5, 6]
LatMap ()等效于用 concat 实现 reduce,它可以打平一个维度
let arr = ["早安", "", "今天天氣不錯"]
arr.map(s => s.split(""))
// [["早", "安"], [""], ["今", "天", "天", "氣", "不", "錯"]]
arr.flatMap(s => s.split(''));
// ["早", "安", "", "今", "天", "天", "氣", "不", "錯"]
3.String.prototype.trimStart() & String.prototype.trimEnd()
trimStart()方法移除字符串开头的空格,trimLeft()是这个方法的一个别名
const greeting = ‘ Hello world! ‘;
console.log(greeting);
// expected output: “ Hello world! “;
console.log(greeting.trimStart());
// expected output: “Hello world! “;
trimEnd()方法移除字符串开头的空格,trimRight()是这个方法的一个别名
const greeting = ' Hello world! ';
console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimEnd());
// expected output: " Hello world!";
4.Object.fromEntries()
Object.fromEntries()方法转换一个键值对为一个对象
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
const obj = Object.fromEntries(entries);
console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }
5.String.prototype.matchAll
matchAll()方法返回一个匹配到所有结果的迭代器,这些结果与正则表达式(包括捕获组)匹配。
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
6.catch参数省略
以前使用catch,无论它是否有用,一定要传入一个 e 参数来表示接收到的错误。
现在如果不使用他,你可以省略它。
try {...} catch(e) {...}
// If e is not used, it can be omitted
try {...} catch {...}
7.BigInt(新的数字类型)
BigInt 值,有时也称为 BigInt,是一个bigint 基元,它可以通过将 n 追加到整数后或者调用 BigInt ()函数(不使用 new 运算符)并给它一个整数值或字符串值的入参来声明。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n
const hugeString = BigInt("9007199254740991");
// ↪ 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");
// ↪ 9007199254740991n
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111");
// ↪ 9007199254740991n
ES11 (ES2020)
1.Promise.allSettled
Promise.allSettled()方法接受一组Promise实例作为参数,包装成一个新的Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
它通常用于多个异步任务,这些任务的执行并不依赖于另一个任务执行成功(即如果有任务失败了,allSettled不会终止执行),或者您总是希望知道每个Promise的结果。
相比之下,如果任务之间是相互依赖的/如果你想立即终止执行其中任何一个执行失败的话,那么 Promise.all ()可能更合适。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"
2.可选链?
在开发过程中,很容易遇到检查数据是否存在的情况。
const isUserExist = user && user.info;
if (isUserExist) {
username = user.info.name;
}
如果返回数据是null或者对象的内部属性不存在,则会抛出异常:Uncaught TypeError: Cannot read property...
使用?就会变的很简单
const username = user?.info?.name;
如果存在就是读取name的值,如果不存在就返回undefined
如果配合||,也就一行代码
const username = user?.name || 'guest';
3.Null 判断运算符
在js中,如果遇到0,null, undefined ,程序将会自动返回false
但是0通常是一个正常的值,我们只想对null和undefined 进行容错
/**
* user = {
* level: 0
* }
*/
const level = user.level || 'no level'; // output as no level instead of expected 0
// to fix it, it must use if simple processing
const level = user.level !== undefined && user.level !== null ? user.level : 'no level';
使用??,你就可以得到正确的结果
const username = user.level ?? 'no level';
// output 0. if level is not available, it becomes 'no level'.
4.动态导入
从字面上看,它应该很容易理解。它是在必要时加载相关的逻辑。
el.onclick = () => {
import(`/js/current-logic.js`)
.then((module) => {
module.doSomthing();
})
.catch((err) => {
handleError(err);
})
}
5.GlobalThis
引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis
const getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
var globals = getGlobal();
现在,我们可以这样做
function canMakeHTTPRequest() {
return typeof globalThis.XMLHttpRequest === 'function';
}
console.log(canMakeHTTPRequest());
// expected output (in a browser): true
ES12(ES2021)
1.Promise.any
Promise.any()方法接受一组Promise实例作为参数,包装成一个新的Promise实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('p1 resolved value')
}, 1000)
})
const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve('p2 resolved value')
}, 500)
})
const p3 = new Promise((resolve) => {
setTimeout(() => {
resolve('p3 resolved value')
}, 1800)
})
Promise.any([p1, p2, p3]).then(value=>{
console.log(value)
}) // p2 resolved value
2.逻辑赋值运算符
在开发过程中,我们可以使用ES2020提出的逻辑运算符 | | ,& & 和? ?,解决了一些问题
在ES2021中提供了||=,&&=和??= 操作符,类似于+=
let b = 2
b += 1
// equal to b = b + 1
let a = null
a ||= 'some random text' // a become to'some random text'
// equal a = a || 'some random text'
let c = 'some random texts'
c &&= null // c become to null
// equal to c = c && null
let d = null
d ??= false // d become to false
// equal to d = d ?? false
3.WeakRef
WeakRef 对象包含对对象的弱引用,这个弱引用被称为该 WeakRef 对象的 target 或者是 referent。对对象的弱引用是指当该对象应该被 GC 回收时不会阻止 GC 的回收行为(意味着释放内存可以由你自己把控)。而与此相反的,一个普通的引用(默认是强引用)会将与之对应的对象保存在内存中。只有当该对象没有任何的强引用时,JavaScript 引擎 GC 才会销毁该对象并且回收该对象所占的内存空间(可以联想到闭包形成的缘由)。如果上述情况发生了,那么你就无法通过任何的弱引用来获取该对象。
这个示例启动一个在 DOM 元素中显示的计数器,当该元素不再存在时停止:
class Counter {
constructor(element) {
// Remember a weak reference to the DOM element
this.ref = new WeakRef(element);
this.start();
}
start() {
if (this.timer) {
return;
}
this.count = 0;
const tick = () => {
// Get the element from the weak reference, if it still exists
const element = this.ref.deref();
if (element) {
element.textContent = ++this.count;
} else {
// The element doesn't exist anymore
console.log("The element is gone.");
this.stop();
this.ref = null;
}
};
tick();
this.timer = setInterval(tick, 1000);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = 0;
}
}
}
const counter = new Counter(document.getElementById("counter"));
setTimeout(() => {
document.getElementById("counter").remove();
}, 5000);