前言
JavaScript在前端编程中占有很重要的位置,然而,作者Brendan Eich创造JS仅仅花了十天, JS用来构建网站很有帮助,但是也遗留了很多问题,es6到es12的诞生就是为了使JS变得更好。
ECMA Next Generation JavaScript
有多少开发者深夜还在解决问题?我们不知道....
尽管有TypeScript帮我们规避JavaScript的缺陷,但是根本问题仍然是JavaScript,因此ES6~ES12发布了一些新特性支持我们开发
ECMAScript
ECMA规范有多方共同维护,包括浏览器厂商,他们一起推进JavaScript提案。
ES6(ES2015)
Class
JavaScript 是一种使用原型链的语言,在早期,类似于 OO 的概念是通过原型链组织的。写起来很复杂。CLASS最终在 ES6中推出
class Animal {
constructor(name, color) {
this.name = name;
this.color = color;
}
// This is a property on the prototype chain
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
class Cat extends Animal {
constructor(action) {
// The subclass must call the super function in the constructor, otherwise an error will be reported when new comes out
// If the constructor was not written originally, the default constructor with super will be automatically generated
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
Module
每一个模块都有独立的命名空间防止冲突, 使用import和export导入和导出
通常把一个.js文件作为一个module
箭头函数
() => {…},函数的简写,最重要的,他能确保指针指向自身。
不需要再写var self = this; var that = this;了
const add = (a, b) => { return a + b};
const res = add(1, 2); // 3
// If the syntax is simple, `{}` and `return` can be omitted. It will look neater
const minus = (a, b) => a - b;
const res1 = minus(3, 1); // 2
函数参数默认值
如果函数参数没有被赋值,那么默认值将被使用
function example(height = 50, width = 40) {
const newH = height * 10;
const newW = width * 10;
return newH + newW;
}
example(); // 900 (50*10 + 40*10)
字符串模板
构造一个长字符串,再过去通常使用+连接 ,这样可读性不太好,使用字符串模板,他就会是易读的。
const firstName = 'Ken';
const lastName = 'Huang';
// not use template literal
const name = 'Hello, My name is' + firstName + ', ' + lastName;
// use template literal
const nameWithLiteralString = `Hello, My name is ${firstName}, ${lastName}`;
解构
非常方便的从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
// To skip certain values
const [first,,,,last] = arr;
console.log(first); // 1
console.log(last); // 5
// Objects can also be destructurized and assigned
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"
扩展操作符
扩展操作符就是...,可以用来展开数组和对象,如果是对象则被解析为key-value的形式
const stuendts = ['Angel', 'Ryan'];
const people = ['Sara', ...stuendts, 'Kelly', 'Eason'];
conslog.log(people); // ["Sara", "Angel", "Ryan", "Kelly", "Eason"]
////
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }
对象属性快捷赋值
如果组成对象的字段名与上文中的变量相同,则赋值可以省略。看起来更简洁。
const name = 'Angel', age = 18, city = 'ChangHwa';
// Before ES6, we must write like this
const customer = {
name: name,
age: age,
city: city
} // // {name: 'Angel', age: 18, city: 'ChangHwa'}
// After ES6, we can do it
const newCustomer = {
name,
age,
city
} // {name: '小明Angel, age: 18, city: 'ChangHwa'}
Promise
promise是一个异步代码的解决方案,比写回调优雅很多。
在早期,他是开源社区的一个方案,后来它被纳入了语言标准。
早期的回调地狱:
有了promise后,回调地狱就不存在了
const waitSecond = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
waitSecond.then( () => {
console.log('hello World after 1 second.');
// output this line after 1 second
return waitSecond;
}).then( () => {
console.log('Hell World after 2 sceond.');
// output this line after 2second
})
在ES8中又新增了更好的async,await 关键字,他可以把一个异步方法变成同步来执行。
缺点是,在复杂的业务逻辑上时,await有时会被忽略,并在运行时报错。
let,const替代var
let:一般变量,可以被重写,
const:一旦定义,值不可以被修改,因为数组和对象是是引用类型,增加或删除属性不会改变指针指向,所以它们的属性可以增加或减少,但不能更改其指针指向。
在早期var的作用域是全局的,也就是说,变量是在您使用它之后声明的。当它被执行时,它将被自动提到顶层,并在以后被分配。(var的变量提升问题),
console.log(a); // undefined
var a = 10;
使用let或者const
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;
ES7
Array.prototype.includes()
用于确定数组是否包含指定的值,如果包含,则返回 true; 否则返回 false。
与之前的 indexOf 用法一样,可以将其视为返回一个布尔值,这在语义上更加清晰。
const arr = [1, 2, 3, 4, 5];
// Check if there is the number 3 in the array
arr.include(3); // true
if (arr.include(3)) { ... }
// ... Equivalent to the previous writing of indexOf
arr.indexOf(3); // 2 (return its array position)
// If you want to write it in the if, you must add `> -1`, which is not as clear as the include in ES7 in terms of semantics
if (arr.indexOf(3) > -1) { ... }
求幂操作符
console.log(2**10); // 1024
// equal to
console.log(Math.pow(2, 10)); // 1024
ES8
async,await
async test() {
try {
const result = await otherAsyncFunction();
console.log(result); // output result
} catch(e) {
console.log(e); // Can catch errors if otherAsyncFunction() throws an error
}
}
Object.values()
返回 Object 自身属性的所有值,不包括继承的值。
const exampleObj = {a: 1, b: 2, c: 3, d:4};
console.log(Object.value(exampleObj)); // [1, 2, 3, 4];
// To do the same thing before, use the following notation. much verbose
const values = Object.keys(exampleObj).map(key => exampleObj[key]);
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 used with for
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
字符串填充函数 padStart() & padEnd()
您可以将其他内容添加到字符串的开头或结尾,并将其填充到指定的长度。
过去,这些函数通常是通过引入三方工具包(如 loash)来实现,现在原生语法也提供该方法
String.padStart(fillingLength, FillingContent);
// If the content to be filled is too much and exceeds the "filling length", it will be filled from the far left to the upper limit of the length, and the excess part will be truncated
最常用的情况应该是数字,填充0到指定的长度。
// padStart
'100'.padStart(5, 0); // 00100
// If the content to be padded exceeds the "padding length". Then fill in from the left to the upper limit of the length
'100'.padStart(5, '987'); // 98100
// padEnd
'100'.padEnd(5, 9); // 10099
// If the content to be padded exceeds the "padding length". Then fill in from the right to the upper limit of the length
'100'.padStart(5, '987'); // 10098
尾后逗号
在向 JavaScript 代码添加元素、参数、属性时十分有用。如果你想要添加新的属性,并且上一行已经使用了尾后逗号,你可以仅仅添加新的一行,而不需要修改上一行。这使得版本控制的代码比较(diff)更加清晰,代码编辑过程中遇到的麻烦更少。
[ "foo",+ "baz", "bar",- "baz", ]
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
SharedArrayBuffer
用来表示一个通用的、固定长度的原始二进制数据缓冲区,类似于ArrayBuffer对象,它们都可以用来在共享内存(shared memory)上创建视图。与ArrayBuffer不同的是,SharedArrayBuffer不能被转移。
/**
* @param length size,unit byte
* @returns {SharedArrayBuffer} the default value is 0。
*/
const sab = new SharedArrayBuffer(length);
Atomics对象
Atomics对象提供了一组静态方法对SharedArrayBuffer和ArrayBuffer对象进行原子操作。
这些原子操作属于Atomics模块。与一般的全局对象不同,Atomics不是构造函数,因此不能使用new操作符调用,也不能将其当作函数直接调用。Atomics的所有属性和方法都是静态的。
多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。
它是 NodeJs 多线程开发中增强响应的一个方法,在前端开发中使用它的概率非常低。
Chrome 已经提供了支持
ES9
await in loop
即for await...of,在一个异步函数中,有时需要在同步 for 循环中使用异步(非同步)函数。
async function process(array) {
for (const i of array) {
await doSomething(i);
}
}
async function process(array) {
array.forEach(async i => {
await doSomething(i);
});
}
上面的代码将不会输出预期的结果。(ps:for...of循环可以正确处理await, forEach不会)
for 循环本身仍然是同步的,将在循环中的异步函数完成之前执行整个 for 循环,然后逐个执行循环中的异步函数。
ES9添加了异步迭代器,允许await与 for 循环一起使用,以逐步执行异步操作。
async function process(array) {
for await (const i of array) {
doSomething(i);
}
}
ps: 关于循环中使用await的表现可以参考:
promise.finally()
无论promise执行成功(.then())还是失败(.catch),finally都会被执行
function process() {
process1()
.then(process2)
.then(process3)
.catch(err => {
console.log(err);
})
.finally(() => {
console.log(`it must execut no matter success or fail`);
});
}
剩余&扩展
在 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
});
正则分组
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
Regexp lookahead Negative
待定
Regexp dotAll
.表示匹配任何一个字符除了回车(\n),正则后加s标志即可匹配换行符
/hello.world/.test('hello\nworld'); // false
/hello.world/s.test('hello\nworld'); // true
ES10 (ES2019)
更加友好的JSON.stringify
最初,如果输入的字符不是 Unicode 字符,那么 JSON.stringify 将返回一个错乱的 Unicode 字符串。
现在,第3阶段的建议是使其成为有效的 Unicode 并以 UTF-8编码返回
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(''));
// ["早", "安", "", "今", "天", "天", "氣", "不", "錯"]
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!";
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 }
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"]
catch参数省略
以前使用catch,无论它是否有用,一定要传入一个 e 参数来表示接收到的错误。
现在如果不使用他,你可以省略它。
try {...} catch(e) {...}
// If e is not used, it can be omitted
try {...} catch {...}
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)
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"
可选链?
在开发过程中,很容易遇到检查数据是否存在的情况。
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';
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'.
动态导入
从字面上看,它应该很容易理解。它是在必要时加载相关的逻辑。
el.onclick = () => {
import(`/js/current-logic.js`)
.then((module) => {
module.doSomthing();
})
.catch((err) => {
handleError(err);
})
}
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)
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
逻辑赋值运算符
在开发过程中,我们可以使用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
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);
这是我的实践和注意事项,如果这是有用的,请点击下面的拍手按钮几次,以显示您的支持!任何错误都可以指出来,并让我知道,谢谢
技术细节参考: www.bookstack.cn/read/es6-3r…