ESNext 新特性

块级作用域 let/const#

  • var 声明会提升
  • let、const 生明不会提升
  • es6 必须先声明变量,再使用变量
  • const 在声明同时必须设置初始值,且不能修改
// const定义变量只要不改变内存指向,可以设置属性
const obj = {};
obj.name = "yue";

建议:主用 const, 配合 let

数组的解构#

const arr = [100, 200, 300];
const [foo, bar, baz = "hello"] = arr; // foo=100 bar=200 baz=300
const [, , baz] = arr; // baz=300
const [foo, ...rest] = arr; // foo=100 rest=[200, 300]
const path = "/abc/ddd/eee";
const temp = path.split("/");
const dir = temp[1]; // dir=abc
const [, dir] = path.split("/"); // dir=abc

对象的解构#

const obj = { name: "zz", age: 39 };
// 解构后重命名
const { name: objName } = obj;
console.log(objName); // 'zz'

模版字符串#

可换行、和插入值

const str = `hello ym`;
const name = "tom";
const msg = `hello ${name}`;

带标签的模版字符串#

const name = "ym";
const gender = "boy";
function myTagFunc(strings, name, gender) {
console.log(strings); // ["hey, ", " is a ", ".", raw: Array(3)]
return strings[0] + name + strings[1] + gender + strings[2];
}
const result = myTagFunc`hey, ${name} is a ${gender}.`;
console.log(result); // hey, ym is a boy.

字符串扩展方法includes/startsWith/endsWith#

const msg = 'Error: foo is not defined.';
// 判断字符串开头
msg.startsWith('Error); // true
// 判断字符串结尾
msg.endsWith('.'); // true
// 判断字符串中是否含有,注意全小写
msg.includes('foo'); // true

参数默认值#

带形参的默认值要放在最后

function foo(e, enable = true) {
console.log(e, enable);
}

剩余参数#

es6 以前arguments表示形参

function foo(a, b, ...args) {
console.log(args); // [1, 2, 3, 4]
}
foo("a", "b", 1, 2, 3, 4);

注意:出现在行参最后一位,只能使用一次

展开数组#

const arr = ["foo", "bar", "baz"];
console.log(...arr); // foo bar baz

箭头函数#

const sum = (n) => n + 1;
const sum1 = (n, m) => {
return n + m;
};
// 对象中使用箭头函数,this 指向不会改变
const person = {
name: "tom",
say1: () => {
console.log(this.name); // undefined
},
say2: function() {
console.log(this.name); // tom
},
say3: function() {
setTimeout(() => {
console.log(this.name); // tom
}, 1000);
},
};
person.say1(); // undefined
person.say2(); // tom
person.say3(); // tom

箭头函数 this 的指向(不仅仅是 this,其实 super,new.target 等)由外围最近一层非箭头函数决定 因此,在使用箭头函数声明 say1 方法的时候,thisperson 的外围决定,而 person 的外围是window

// 示例一 lastName 使用 var 声明, var生命变量会挂载到window上
var lastName = "aaaaa";
const person = {
lastName: "bbbbbb",
getLastName: () => {
console.log(this.lastName);
},
};
person.getLastName(); // aaaaa 注意箭头函数 指向window
// 示例二 lastName 使用 const 声明, 不会挂到window属性下
const lastName = "aaaaa";
const person = {
lastName: "bbbbbb",
getLastName: () => {
console.log(this.lastName);
},
};
person.getLastName(); // undefined

对象字面量增强#

const bar = '123';
const obj = {
foo: '123',
bar,
// method: function() {
// console.log(this)
// },
method() {
console.log(this) // 等价于指向当前对象
}
[Math.random()]: 123 // 计算属性名
}

Object.assign#

将多个源对象的属性复制到一个目标对象中, 后面的属性覆盖目标属性

const target = {
a: 345,
c: 34,
};
const source1 = {
a: 1,
b: 123,
};
const result = Object.assign(target, source1);
console.log(result); // {a: 1, c: 34, b: 123}
console.log(result === target); // true;

Object.is#

NaN === NaN; // false
Object.is(NaN, NaN); // true

Proxy 代理#

专门为对象设置代理器

es6 以前监听某个对象属性读写使用 Object.defineProperty,也是 vue2.0 双向绑定的实现机制

const person = {
name: "zz",
age: 20,
};
// 第一个参数 代理对象,第二个参数,代理设置对象
const personProxy = new Proxy(person, {
get(target, property) {
return property in target ? target[property] : "default";
},
set(target, property, value) {
target[property] = value;
},
});
personProxy.age = 100;
console.log(personProxy.name); // zz
console.log(personProxy.age); // 100

Proxy vs defineProperty#

  • defineProperty只能监视读写;
  • Proxy 可删除对象属性 deleteProperty
  • proxy 非入侵对方式监管对象读写
  • proxy 更好到对数组对象监视
const personProxy = new Proxy(person, {
defineProperty(target, property) {
delete target[property];
},
});
delete personProxy.age;
const list = [];
const listProxy = new Proxy(list, {
// 监视数据写入
set(target, property, value) {
target[property] = value;
return true; // 表示写入成功
},
});
list.push(100);
console.log(list); // [100]

Reflect 属于一个静态类#

封装类一系列对象对底层操作 最大对意义统一一套操作对象 api

Reflect.has(obj, "name"); // 等价于name in obj
Reflect.deleteProperty(obj, "age"); // 等价于delete obj['age']
Reflect.ownKeys(obj); // 等价于Object.keys(obj)

Promise#

class 类#

function Person(name) {
this.name = name;
}
Person.prototype.say = function() {
console.log(this.name);
}
class Person {
constructor(name) {
this.name = name
}
say() {
console.log(this.name)
}
}
cosnt p = new Person('tom');
p.say(); // tom

静态方法 static#

this 不会指向实例对象,指向类型

class Person {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name);
}
static create(name) {
return new Person(name);
}
}
const tom = Person.create("tom");
tom.say();

类的继承 extends#

class Students extends Person {
constructor(name, number) {
super(name);
this.number = number;
}
hello() {
super.say();
console.log(this.number);
}
}
const s = new Student("jsck", "100");
s.hello();

Set 数据结构#

成员不允许重复,多用于去重

const s = new Set();
s.add(1).add(2);
console.log(s.size);
s.has(100);
s.delete(2); // true
// 去重
const arr = [1, 4, 5, 4, 5, 8, 8];
const m = new Set(arr); // {1, 4, 5, 8}
const arr1 = Array.from(new Set(arr)); // [1, 4, 5, 8]

Map 数据结构#

与对象类似,区别在于对象中的键会自动转为toString()类型; new map()类型键不会转换字符串,可以使用任意类型数据结构作为键

const mapObj = new Map();
const a = 123;
const b = "123";
mapObj.set(a, "aaa");
mapObj.set(b, "bbb");
console.log(mapObj); // {123 => "aaa", "123" => "bbb"}

Symbol#

表示独一无二的值 为对象添加独一无二的值 注意:

  • 如果属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序
  • 如果属性名的类型是String,那么Object.keys返回值是按照属性被创建的时间升序排序。
  • 如果属性名的类型是Symbol,那么逻辑同String相同,但其实Object.keys最终会将Symbol类型的属性过滤出去
const cache = {};
cache["foo"] = Math.random();
cache["foo"] = "123";
const s = Symbol(123);
const s1 = Symbol("123");
cache[s] = "555";
cache[s1] = "666";
console.log(cache); // {foo: "123", Symbol(123): "555", Symbol(123): "666"}
console.log(Object.keys(cache)); // ['foo']
console.log(Object.values(cache)); // ['123']

for of 循环#

只能遍历数组结构类型,遍历对象报错

const arr = [100, 200, 300, 400];
for (const item of arr) {
if (item > 100) {
break;
}
}
const m = new Map();
m.set("foo", "123");
m.set("bar", "456");
for (let [key, value] of m) {
console.log(key, value);
}

for of 循环工作原理#

可迭代接口 Iterator

const set = new Set(["foo", "bar", "baz"]);
const iterator = set[Symbol.iterator]();
console.log(iterator.next()); // {value: 'foo', done: false}
// 实现可迭代接口 Iterable
const obj = {
store: ["foo", "bar"],
[Symbol.iterator]: function() {
let index = 0;
const self = this;
return {
next: function() {
const result = {
value: self.store[index],
done: index >= self.store.length,
};
index++;
return result;
},
};
},
};
for (const item of obj) {
}

设计模式-迭代器模式#

对外提供统一遍历接口,让外部不用关心内部结构

const todos = {
life: ['123','345'],
learn: ['语文''数学'],
work: ['喝茶'],
each: function(callback) {
const all = [].concat(this.life,this.learn,this.work);
for(const item of all) {
callback(item);
}
},
[Symbol.iterator]: function() {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0;
return {
next: function() {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
todos.each(function(item) {
console.log(item)
})
for(const item of todos) {
console.log(item)
}

生成器 Generator#

  • 解决异步变成过程中回掉过深嵌套的问题
  • 生成器函数返回一个生成器对象
  • 惰性执行, 抽一下,动一下
function* foo() {
console.log(100);
return 100;
}
const result = foo();
console.log(result.next());
function* foo1() {
console.log(111);
yield 100;
console.log(222);
yield 200;
}
const generator = foo();
console.log(generator.next());

生成器应用

function* createMaker() {
let id = 1;
while (true) {
yield id++;
}
}

ES Modules#

ES2016#

  • 发布与 2016 年 6 月
  • 数组实例对象的 includes
  • 相比与indexOf可以查找NaN

// 增加指数运算符

console.log(Math.pow(2, 10)); // 1024
console.log(2 ** 10); // 1024

ES2017#

  • 发布 2017 年 6 月
  • Object.values // 遍历值
  • Object.entries(obj)

字符串填充方法,尾部加,

const books = {
html: 5,
css: 16,
};

// 参数添加尾逗号

const arr = [100, 200, 300];

Async/Await 本质使用 Promise 语法糖而已

Promise 相关#

Promise 应用#

setTimeout(() => console.log("a"), 0);
setTimeout(() => console.log("b"), 1000);
Promise.resolve()
.then(() => {
setTimeout(() => console.log("c"), 0);
setTimeout(() => console.log("d"), 1000);
console.log("e");
Promise.resolve().then(() => console.log("f"));
})
.then(() => console.log("g"));
setTimeout(() => console.log("h"), 0);
setTimeout(() => console.log("i"), 1000);
// e f g a h c b i d

微任务是本轮任务的末尾, 每个任务执行后会先执行所有微任务,再执行宏任务

Promise.all#

如果 promise.all 通过 catch 处理

const promise = urls.map((item) => axios(item).catch((e) => ({})));

ES2020 确保所有任务都返回,使用 Promise.allSetttled()

Promise 面试题#

console.log("A");
setTimeout(() => console.log("B"), 1000);
const start = new Date();
while (new Date() - start < 3000) {}
console.log("c");
setTimeout(() => console.log("D"), 0);
new Promise((resolve, reject) => {
console.log("E");
foo.bar(100);
})
.then(() => console.log("F"))
.then(() => console.log("G"))
.catch(() => console.log("H"));
console.log("I");
// 答案: A C E I H B D

Async/await Genertaor 语法糖#

扁平化

var foo = 100;
async function main() {
foo = foo + (await Promise.resolve(10));
console.log("main", foo);
}
main();
foo++;
console.log("global", foo); // global 101
// main 110

依次执行异步任务

promises.reduce((prev, current) => {
prev.then(() => current);
}, Promise.resolve());
async function main() {
for (let item of promises) {
await item;
}
}

this 指向#

取决于调用,箭头函数不会改变 this 指向

function foo () {
console.log(this);
}
foo() // 指向全局 ,严格模式下指向undeined
new foo(); // 指向空对象
foo.call(123); // 123
const obj = {
foo: function() {
console.log(this)
},
bar: funtion() {
}
}
obj.foo(); // 通过对象.出来 指向对象
const fn = obj.foo;
fn(); // window

正则表达式增强#

ES2018+

const date = "2020-08-09";
const reg = /(\d{4})-(\d{2})-(\d{2})/;
const reg1 = /(?<year>\d{4})-(?<month>\d{2})-(?<date>\d{2})/;
const res = reg.exec(date); // 第一个成员整个字符串
const res = reg1.exec(date); // 取groups对象
const info = "张三是张三,张三丰是张三丰";
const res = info.replace(/张三/g, "里斯"); // "里斯是里斯,里斯丰是里斯丰"
// 环视 =》向后断言
const res1 = info.replace(/张三(?!丰)/g, "里斯"); // "里斯是里斯,张三丰是张三丰"
const res2 = info.replace(/张三(?=丰)/g, "里斯"); // "张三是张三,里斯丰是里斯丰"

ES2020新特性#

ECMAScript语言规范的第10个版本发布(ES2019)

可选链操作符( ?. )#

允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(nullish) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。

const obj = {
name: 'zhangsan',
cat: {
name: 'li'
},
say() {
console.log('hhh')
}
};
const dogName = obj.dog?.name;
console.log(dogName); // undefined
console.log(obj.cat?.name) // li
console.log(obj?.say()) // 'hhh'
console.log(obj?.say1()) // 报错:Uncaught TypeError: obj?.say1 is not a function
console.log(obj?.say1?.()) // undefined

BigInt#

BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数。而在其他编程语言中,可以存在不同的数字类型,例如:整数、浮点数、双精度数或大斐波数。

let n = BigInt(100)
typeof n // "bigint"
n === 100 // false
n == 100 // true

String.prototype.trimStart() / String.prototype.trimEnd()#

分别去掉首尾字符串空格,ES5的String.prototype.trim()用于去掉头尾空格,而String.trimStart()去掉字符串首空格,String.trimEnd()去掉尾空格

let str = ' asdfg ';
str.trimStart(); // 'asdfg '
str.trimEnd(); // ' asdfg'

Object.fromEntries()#

ES8引入了Object.entries把一个对象转为[key, value]键值对的形式,可以运用于像 Map 这种结构中, Object.fromEntries()用于把键值对还原成对象结构

let arr = [['foo','bar']];
Object.fromEntries(arr) // {foo: "bar"}
let obj = {a: 1, b: 2}
Object.entries(obj) // [["a", 1], ["b", 2]]

Array.prototype.flat() / Array.prototype.flatMap()#

把数组展平是Array原型给我们带来的新特性,通过传入层级深度参数(默认为1),来为下层数组提升层级。如果想提升所有层级可以写一个比较大的数字甚至是Infinity,但不推荐使用

let arr = [1, 2, 3, [4, 5, 6, [8, 9]], 7];
arr.flat(1) // [1, 2, 3, 4, 5, 6, [8, 9], 7]
arr.flat(2) // [1, 2, 3, 4, 5, 6, 8, 9, 7]
arr.flat(Infinity) // [1, 2, 3, 4, 5, 6, 8, 9, 7]

Array.prototype.flatMap()Array.prototype.map()Array.prototype.flat()的组合,通过对map调整后的数据尝试展平操作

[1, 2, [3, 4]].flatMap(v => {
if (typeof v === 'number') {
return v * 2
} else {
return v.map(v => v * 2)
}
})
// [2, 4, 6, 8]

catch 的参数改为可选#

在进行try...catch错误处理过程中,如果没有给catch传参数的话,代码就会报错。新规范中可以省略catch绑定的参数和括号

const isValid = params => {
try {
return true;
} catch {
return false;
}
};

Symbol.prototype.description#

description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

let a = Symbol(123);
console.log(a) // Symbol(123)
typeof a // "symbol"
a.description // "123"
typeof a.description // "string"

动态引入import#

允许js文件动态引入,并且不需要模块处理器可直接使用,动态导入(返回Promise),import命令被JS引擎静态分析,先于模块内的其他语句执行,无法取代require()的动态加载功能,提案建议引入import()来代替require(),require()是同步加载,import()是异步加载,用于按需加载、条件加载、模块路径动态化等场景

const loadJs = async (num) => {
if (num === 1) {
const a = await import('../xxx.js')
}
}
loadJs(1)

Promise.allSettled()#

该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

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)));
// "fulfilled"
// "rejected"

参考文档: MDN