十一. ES6 ~ ES12
11.1. 字面量增强的写法
ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量),字面量的增强主要包括下面几部分:
- 属性的简写:Property Shorthand
- 方法的简写:Method Shorthand
- 计算属性名:Computed Property Names
let name = "why"
let age = 18
let obj = {
// 属性的简写:Property Shorthand
name,
age,
// 方法的简写:Method Shorthand
foo(){
console.log("foo");
},
// 计算属性名:Computed Property Names
[name+123]:"计算属性名"
}
console.log(obj);
11.2. 解构Destructuring
数组的解构
let names = ["张三", "李四", "王五"]
// 对数字解构
let [item1, item2, item3] = names
// 解构后面的元素
let [, , item4] = names
// 解构出一个元素,后面的元素放到一个新数组
let [item5, ...newNames] = names
// 结构的默认值(没有默认值则是undefined)
let [item6, item7, item8, item9 = "赵六"] = names
对象的解构
let obj = {
name: "why",
age: 18,
height: 1.88
}
// 任意顺序,按key去解构
var { height, name, age } = obj // 1.88 why 18
// 默认值
var { age, address = "成都市" } = obj // 18 成都市
// 重命名
var { name: newName } = obj // why
// 应用场景列举:
function foo({name,age}){
console.log(name,age); // why 18
}
foo(obj)
11.3. let_ const_ var
11.3.1. let/const基本使用
let关键字:
- 从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
const关键字:
-
const关键字是constant的单词的缩写,表示常量、衡量的意思;
-
它表示保存的数据一旦被赋值,就不能被修改;
- 如果赋值的是引用类型(内存地址 ),那么可以通过引用找到对应的对象,修改对象的内容;
注意: 另外let、const不允许重复声明变量;
11.3.2. 作用域提升_ 暂时性死区
var声明的变量是会进行作用域提升的,但let/const没有(暂时性死区)
是不是意味着foo变量只有在代码执行阶段才会创建的呢?
- 事实上并不是这样的,我们可以看一下ECMA262对let和const的描述;
- 这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值;
作用域提升: 在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升;
// let/const是没有作用域提升的,foo解析时是被创建了,但是不能访问
console.log(foo); // Reference(引用)Error: Cannot access 'foo' before initialization(初始化)
let foo="foo"
暂时性死区:使用let、const声明的变量,在声明之前,变量都是不可以访问的;
- let声明的变量是有块级作用域,因此不能访问到全局的foo,由于let不会有提升,所以后面的foo也不能访问到
// 例一
var foo = "foo"
if (true) {
console.log(foo);
let foo = "abc"
}
// 例二
function bar(){
console.log(foo);
let foo="abc"
}
bar()
11.3.3. Window对象添加属性
在全局通过var来声明一个变量,事实上会在window上添加一个属性:
但是let、const是不会给window上添加任何属性的。
11.3.4. 块级作用域
ES5的var没有块级作用域,ES5里只有 全局作用域 和 函数作用域
{
var foo = "foo"
}
console.log(foo); // foo
ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的
- 但我们发现函数拥有块级作用域,但是外面依然是可以访问的
- 这是因为引擎会对函数的声明进行特殊的处理,大部分浏览器为了兼容以前的代码,让function是没有块级作用域的
{ // 除了demo()可以访问,其他的都不能访问
let foo = "aaa"
function demo() { }
class Person {}
}
11.3.5. if_ switch_ for块级代码
if (true) {
var foo = "foo" // 外部能访问
let bar = "bar" // 外部不能访问
}
let color = "red"
switch (color) {
case "red":
var foo = "foo"// 外部能访问
let bar = "bar"// 外部不能访问
}
// 外部能访问i;但如果是let声明的i,外部则不能访问
for(var i=0;i<10;i++){
// i= 10
}
11.3.6. 块级作用域的应用
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
const btns = document.getElementsByTagName('button')
for (var i = 0; i < btns.length; i++) {
btns[i ].onclick = function () {
// 按钮点击时,会去上层作用于找i,var没有块级作用域,所以就找到了全局的i
// 如果是let,上层作用域就是{}了,每个块的i不会相互干扰
console.log(`第${i}个按钮被点击了`);
}
}
11.3.7. var、let、const的选择
优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改;
当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let;
注:var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些
历史遗留问题;
11.4. 模板字符串
模板字符串:使用``代替ES6之前的"+变量+"
const name="why"
console.log(`我的名字是${name}`); // 我的名字是why
let num=10
console.log(`double num is ${num*2}`); // double num is 20
let age=18
function doubleAge(){
return age*2
}
console.log(`double age is ${doubleAge()}`); // double age is 36
标签模板字符串:可以用``来调用函数,并传入参数
// m: 模板字符串中的整个字符串,只是被切成了多块,放到了一个数组中
// n: 第一个${}
// n: 第二个${}
function foo(m,n,x){
console.log(m,n,x); // ['hello', 'wor', 'ld'] 'why' 18
}
const name="why"
const age= 18
foo`hello${name}wor${age}ld`
11.5. 函数的默认参数
// 函数的默认参数
function foo(m = 1, n = 2) {
console.log(m,n); // 1 2
}
foo()
// 对象参数的默认值和解构
function bar({ name, age } = { name: "why", age: 18 }){
console.log(name,age); // zhangsan 20
}
bar( { name:"zhangsan", age:20 } )
// 另一种写法
function info({ name = "lisi", age = 25} = { }) {
console.log(name,age); // lisi 25
}
info()
注意:1. 建议默认值我们通常会将其放到最后,如果非要放到前面,则只能传入undefined,参数就会自动取默认值,但多此一举了
- 默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了。
function bar(name, age = 18, address, height) {
console.log(bar.length); // 1
}
bar()
11.6. 剩余参数
ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
- 如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
function foo(m, n, ...args) {
console.log(m, n); // 1 2
console.log(args); // [3, 4, 5]
}
foo(1, 2, 3, 4, 5)
那么剩余参数和arguments有什么区别呢?
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
- arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
- arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是ES6中提供 并且希望以此来替代arguments的;
注意: 剩余参数必须放到最后一个位置,否则会报错。
11.7. 展开语法
展开语法(Spread syntax):
- 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
- 还可以在构造字面量对象时, 将对象表达式按key-value的方式展开;
展开语法的场景:
- 在函数调用时使用;
- 在数组构造时使用;
- 在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性;
const names = ["a", "b", "c", "d"]
const name = "why"
const info = { name: "coder" , age: 18 }
// 1.函数调用时
function foo(x, y, z) {
console.log(x, y, z);
}
foo(...names) // a b c
foo(...name) // w h y 注:对字符串使用展开语法,会把每个字符串进行展开
// 2.构造数组时
const newNames = [...names, ...name] // ['a', 'b', 'c', 'd', 'w', 'h', 'y']
// 3.ES2018(ES9)添加的新特性,展开运算只能用于可迭代对象,对象的展开不是用迭代器实现的
// {0: 'a', 1: 'b', 2: 'c', 3: 'd', name: 'coder', age: 18, address: '成都市'}
const obj = {...info, address: "成都市" , ...names}
补充:展开运算符其实是一种浅拷贝,将info的内容复制给了obj
const info = {name:"张三",friends:{name:"李四"}}
const obj={...info, name: "王五"}
obj.friends.name="村霸上线了"
console.log(info.friends.name); // 村霸上线了
11.8. 箭头函数
箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象;也没有this 和 arguments
let foo=()=>{}
console.log(foo.prototype); // undefined
new foo() // TypeError: foo is not a constructor
11.9. 数值的表示
const num1=100
// binary 二进制
const num2=0b100
// octal 八进制
const num3=0o100
// hexadecimal 十六进制
const num4=0x100
// 数字分隔符:数字过长时,大的数值可用_连接符分割,ES2021更新的(ES12)
const num5= 10_000_000_000
console.log(num1,num2,num3,num4,num5); // 100 4 64 256 10000000000
11.10. Symbol
基本使用
Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
Symbol即使多次创建值,它们也是不同的: Symbol函数执行后每次创建出来的值都是独一无二的;
我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
注意: Symbol不能通过 .语法 去获取(obj1.s1),js会去找作为字符串的s1的key。只能用obj1[s1]
const obj={
name:"why",
age:18,
friends:{name:"李四"}
}
// 不管定义属性时,key有没加"",在底层解析时,都会给所有的key加上""
console.log(Object.keys(obj)); // ['name', 'age', 'friends']
const s1 = Symbol()
const s2 = Symbol()
const s3 = Symbol("asdf") // ES2019(ES10)中,Symbol还有一个描述(description)
console.log(s3.description); // asdf
// Symbol值作为key
// 1. 在定义对象字面量时使用
const obj1 = {
[s1]: "aaa",
[s2]: "bbb",
}
// 2.新增属性
obj1[s3] = "ccc"
// 3.Object.defineProperty方式
const s4 = Symbol()
Object.defineProperty(obj1, s4, {
enumerable: true,
configurable: true,
writable: true,
value: "ddd"
})
console.log(obj1[s1], obj1[s2], obj1[s3], obj1[s4] ); // aaa bbb ccc ddd
遍历Symbol值的key
- 使用Symbol作为key的属性名,在遍历、Object.keys等中是获取不到这些Symbol值
- 需要使用Object.getOwnPropertySymbols来获取所有Symbol的key
const s1 = Symbol('s1')
const s2 = Symbol('s2')
const obj1 = {
[s1]: "aaa",
[s2]: "bbb",
name:"why"
}
console.log(Object.keys(obj1)); // ['name']
console.log(Object.getOwnPropertyNames(obj1)); // ['name']
console.log(Object.getOwnPropertySymbols(obj1)); // [Symbol(s1), Symbol(s2)]
for (const symbolKey of Object.getOwnPropertySymbols(obj1)){
console.log(obj1[symbolKey]); // aaa bbb
}
相同值的Symbol
Symbol的目的是为了创建一个独一无二的值,如果我们现在就是想创建相同的Symbol应该怎么来做?
- 我们可以使用Symbol.for方法来做到这一点;
- 并且我们可以通过Symbol.keyFor方法来获取对应的key;
// 1.Symbol.for(key) : 创建一个Symbol值; 创建Symbol值时,会先去找之前有没有相同的key('s1'),如果有,就把之前的Symbol值返回
const s1 = Symbol.for('s1')
const s2 = Symbol.for('s1')
console.log(s1 === s2); // true
// 2.Symbol.keyFor(symbol) : 获取对应的key;
console.log(Symbol.keyFor(s1)); // s1
11.11. Set_ WeakSet
11.11.1 认识set和基本API使用
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
-
在ES6中新增了另外两种数据结构:Set(集合类型)、Map(存储映射关系),以及它们的另外形式WeakSet、WeakMap。
-
使用 new 关键字和 Set 构造函数可以创建一个空集合:
-
如果想在创建的同时初始化实例,则可以给 Set 构造函数传入一个可迭代对象,其中需要包含插入
到新集合实例中的元素
Set是一个新增的数据结构,可以用来保存数据,类似于数组, 但是和数组的区别是元素不能重复。
- 创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
- 我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重。
Set常见的属性:
- size:返回Set中元素的个数;
Set常用的方法:
- add(value):添加某个元素,返回Set对象本身;
- delete(value):从set中删除和这个值相等的元素,返回boolean类型;
- has(value):判断set中是否存在某个元素,返回boolean类型;
- clear():清空set中所有的元素,没有返回值;
- forEach(callback, [, thisArg]):通过forEach遍历set;
- for of:通过for of遍历。
// 1.数组去重
const arr = [10, 30, 30, 40]
const newArr = [...new Set(arr)] // [10, 30, 40]
const newArr2 = Array.from(new Set(arr)) // [10, 30, 40]
// 2.常用方法
const set = new Set([1, 2, 2])
console.log(set.size); // 2
console.log(set.add(3)); // Set(3) {1, 2, 3}
console.log(set.delete(1)); // true
console.log(set.has(2)); // true
set.forEach(item => {
console.log(item); // 2 3
});
for (const item of set) {
console.log(item); // 2 3
}
set.clear()
console.log(set); // Set(0) { }
11.11.2. 认识WeakSet和基本API使用
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
- 区别二:Set是强引用,WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
解释下强引用和弱引用:
-
强应用(strong reference):强引用指向着该内存,则不会被回收
-
弱引用(weak reference):GC回收内存时。即使有弱引用指向着该内存,也会被回收
- 作用:我们可以通过弱引用,使用里面的属性,使用方法我暂时没找到(在别的强引用没有解除对该内存的引用前)
WeakSet常见的方法:
- add(value):添加某个元素,返回WeakSet对象本身;
- delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
- has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
注意:WeakSet不能遍历
- 因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
- 所以存储到WeakSet中的对象是没办法获取的;
验证:弱引用指向的内存,会被回收
const finalizationRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalizationRegistry的对象被销毁了", value);
})
let obj = {}
let info = new WeakSet().add(obj)
// 将obj注册到finalizationRegistry对象中
finalizationRegistry.register(obj, "obj对象")
obj = null // 执行到这里是,obj不会立马被回收,GC是不定时回收
11.1.3. WeakSet的应用场景
应用场景(用的场景少):person的runing方法只允许用person的实例对象调用,不允许通过别的对象来调用
疑惑:为什么这个案例不用Set来保存this?
答:Set是强引用,如果p=null时,由于set对p有引用,所以p不会销毁,需要用set.delete(p)来解除引用,太麻烦了
const weakset = new WeakSet()
class Person {
constructor() {
weakset.add(this)
}
runing() {
// 判断this是不是person的实例对象
if (!weakset.has(this)) {
throw new Error("不能通过非构造方法创建出来的对象调用runing方法")
}
console.log(this);
}
}
let p = new Person()
p.runing()
// Error: 不能通过非构造方法创建出来的对象调用runing方法
p.runing.call({ name: "why" })
console.log(weakset);
// node中打印为:WeakSet { <items unknown> }
// window中打印为:WeakSet {Person}
// 原因(WeakSet同理): WeakSet不能进行遍历,打印WeakSet时,需要把里面的东西全部拿到(遍历),然后拼接出来
11.12. Map_ WeakMap
11.12.1 认识map和基本API使用
另外一个新增的数据结构是Map,用于存储映射关系。
那么使用Map和对象来存储映射关系,他们有什么区别呢?
-
事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
-
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;
// 验证:对象中不能使用对象来作为key const obj1 = { name: "zhangsan" } const obj2 = { name: "lisi" } const info = { [obj1]: "aaa", [obj2]: "bbb" } // 疑惑: 打印结果为什么只有一个键值对 // 答:当把obj1作为key时,js会把obj1转成字符串格式,也就是'[Object Object]',obj2也会转成同样的字符串,所以后面的key会覆盖掉前面的key console.log(info); // {'[object Object]': 'bbb'}, window环境打印会省略''
但是Map允许对象类型作为key
const obj1 = { name: "zhangsan" }
const obj2 = { name: "lisi" }
const map= new Map()
map.set(obj1,"aaa")
map.set(obj2,"bbb")
map.set(1,"ccc")
console.log(map);
// Map {
// { name: 'zhangsan' } => 'aaa',
// { name: 'lisi' } => 'bbb',
// 1 => 'ccc'
// }
创建map时可以传入一个数组,(数组里存储的格式必须是entries(ES8))
const obj1 = { name: "zhangsan" }
const obj2 = { name: "lisi" }
const map= new Map([[obj1,"aaa"],[obj2,"bbb"],[1,"ccc"]])
console.log(map);
// Map {
// { name: 'zhangsan' } => 'aaa',
// { name: 'lisi' } => 'bbb',
// 1 => 'ccc'
// }
Map常见的属性:
- size:返回Map中元素的个数;
Map常见的方法:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
- clear():清空所有的元素;
- forEach(callback, [, thisArg]):通过forEach遍历Map;
- Map也可以通过for of进行遍历。
const obj1 = { name: "zhangsan" }
const obj2 = { name: "lisi" }
const map = new Map([[obj1, "aaa"], [obj2, "bbb"], [1, "ccc"]])
console.log(map.get(obj1)); // aaa
map.forEach((value, key, map) => {
console.log(value, key, map);
})
for (const item of map) {
// [{ name: 'zhangsan' }, 'aaa']
// [{ name: 'lisi' }, 'bbb']
// [1, 'ccc']
console.log(item);
}
// for of遍历出来的item是数组,数组里放的是[key,value],可以直接解构
for (const [key, value] of map) {
// { name: 'zhangsan' } aaa
// { name: 'lisi' } bbb
// 1 ccc
console.log(key, value);
}
11.12.2. 认识WeakMap和基本API使用
和Map类型相关的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
- 区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
WeakMap常见的方法有四个:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
注意:WeakMap也是不能遍历的,因为没有forEach方法,也不支持通过for of的方式进行遍历;
11.12.3. WeakSet的应用场景(vue3响应式原理)
vue3响应式原理:监听对象的改变,并且对应的函数做出对应的响应
- 比如obj1的name发生改变,对应的obj1NameFn1和obj1NameFn2函数就执行
const obj1= {
name: "aaa",
age:18
}
function obj1NameFn1(){
console.log('obj1NameFn1');
}
function obj1NameFn2(){
console.log('obj1NameFn2');
}
function obj1AgeFn1(){
console.log('obj1AgeFn1');
}
function obj1AgeFn2(){
console.log('obj1AgeFn2');
}
const obj2= {
name: "bbb",
height: 180,
address: "成都市"
}
function obj2NameFn1(){
console.log('obj2NameFn1');
}
function obj2NameFn2(){
console.log('obj2NameFn1');
}
// 为什么这里用WeakMap,如果obj1=null,WeakMap里面对obj1的引用也会解除
const weakmap= new WeakMap()
// 1.收集依赖关系
// (1).对obj1收集关系
const obj1Map= new Map()
obj1Map.set("name",[obj1NameFn1,obj1NameFn2])
obj1Map.set("age",[obj1AgeFn1,obj1AgeFn2])
weakmap.set(obj1,obj1Map)
// (2).对obj2收集关系
const obj2Map=new Map()
obj2Map.set("name",[obj2NameFn1,obj2NameFn2])
weakmap.set(obj2,obj2Map)
// 2.假如obj1.name发生了改变
// Proxy/Object.defineProperty
obj1.name="张三"
const fns=weakmap.get(obj1).get('name')
fns.forEach(element => element());
⚑ 下面是ES7(2016)
11.13. Array Includes
在ES7之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf 获取结果,并且判断是否为 -1。
在ES7中,我们可以通过includes来判断一个数组中是否包含一个指定的元素,返回布尔值
- includes(item, 0):0表示从索引为0的地方开始检查
- includes() 方法用于判断字符串是否包含指定的子字符串。
const arr=[1,2,3,"a",NaN]
if (arr.indexOf(2)!==-1) {
console.log("cunzai");
}
if (arr.includes(NaN)) {
console.log("youNaN");
}
注意:indexOf 不能检查NaN是否存在,includes是可以检查的
11.14. 指数运算符
在ES7之前,计算数字的乘方需要通过 Math.pow 方法来完成。
在ES7中,增加了 ** 运算符,可以对数字来计算乘方。
// 计算3的四次方
const results= Math.pow(3,4)
const results1= 3**4
⚑ 下面是ES8(2017)
11.15. Object.values
之前我们可以通过 Object.keys 获取一个对象所有的key,在ES8中提供了 Object.values 来获取所有的value值
const obj={ name:"why", age:18 }
console.log(Object.keys(obj)); // [ 'name', 'age' ]
console.log(Object.values(obj)); // [ 'why', 18 ]
// 用的少
console.log(Object.values(['a','ac','zx'])); // [ 'a', 'ac', 'zx' ]
console.log(Object.values('abcd')); // [ 'a', 'b', 'c', 'd' ]
11.16. Object entries
通过Object.entries 接收一个对象,返回它们键/值对的数组,
const obj={ name:"why", age:18 }
console.log(Object.entries(obj)); // [ [ 'name', 'why' ], [ 'age', 18 ] ]
Object.entries(obj).forEach(item => {
// name why
// age 18
const [key, value]= item
console.log(item[0], item[1])
})
// 如果传入一个数组或字符串,会将索引作为key
// [ [ '0', 'abc' ], [ '1', 'cba' ], [ '2', 'cab' ] ]
console.log(Object.entries(["abc","cba","cab"]));
// [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]
console.log(Object.entries("abc"));
11.17. String Padding
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,
ES8中增加了 padStart 和 padEnd 方法,分别是对字符串的首尾进行填充的。
const sum = 'abcdef'
// 在sum前面填充*,填充到总长度为7; 在sum后面填充-,填充到总长度为10;
const nueSum = sum.padStart(7, '*').padEnd(10, '-') // *abcdef---
// 应用场景:隐藏身份证前面数字
const card = '513722199605212816'
const newCard= card.slice(-4).padStart(card.length,'*') // **************2816
11.18. Trailing Commas
Trailing Commas:翻译为结尾的逗号;它允许在函数定义和调用时多加一个逗号:
function foo(m,n,){ }
foo(1,2)
foo(1,2,)
11.19. Object.getOwnPropertyDescriptors
Object.getOwnPropertyDescriptor(对象,属性名);原型里写过了,获取对象属性描述符
⚑ 下面是ES9(2018)
Async iterators:后续迭代器讲解
Object spread operators:前面讲过了(对象的展开语法),注:对象的展开不是用迭代器实现的
Promise finally:后续讲Promise讲解
⚑ 下面是ES10(2019)
Symbol description:已经讲过了
Optional catch binding:后面讲解try cach讲解
11.20. flat flatMap
flat() :降维操作,方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
flatMap() :方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
- 注意一:flatMap是先进行map操作,再做flat的操作;flatMap中的flat相当于深度为1;
- 注意二:flat中的flat默认深度为1;
const nums=[10,20,[30,40],[[50,60],[70,80]]]
const newNums= nums.flat(2) // [ 10, 20, 30, 40, 50, 60, 70, 80 ]
// flatMap应用场景: 对数组每个item做完映射后,自动转为一维数组
const message = ["hello world","helle js", "hello vue"]
const word= message.flatMap(item=>item.split(" ")) // [ 'hello', 'world', 'helle', 'js', 'hello', 'vue' ]
const word1= message.map(item=>item.split(" ")) // [ [ 'hello', 'world' ], [ 'helle', 'js' ], [ 'hello', 'vue' ] ]
// 1.场景:数组扁平化
var arr = [[1, 2, 2, 3], [4, 5, 5, 6], [7, 8, 9, 10, [11, 12, 12, 13, [14]]]]
// 2.实现代码
const set = new Set()
// 递归调用
function foo(arr) {
if (typeof arr === "object") {
arr.flatMap(item => {
foo(item)
})
} else {
set.add(arr)
}
}
const why = arr.flatMap(item => {
foo(item)
}
)
console.log(set); // Set(14) { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }
11.21. Object.formEntries
Object.entries :将一个对象转换成 entries
Object.formEntries:将entries转换成 一个对象
const entries= [ [ 'name', 'why' ], [ 'age', 18 ] ]
// es10之前的做法
const obj={}
for (const iterator of entries) {
obj[iterator[0]] = iterator[1]
}
console.log(obj); // { name: 'why', age: 18 }
// es10
console.log(Object.fromEntries(entries)); // { name: 'why', age: 18 }
11.22. trimStart和trimEnd
去除一个字符串首尾的空格,我们可以通过trim方法
如果单独去除前面或者后面的空格,ES10中给我们提供了trimStart和trimEnd;
const message=" hello world "
console.log(message.trim()); // 去除首尾的空格
console.log(message.trimStart()); // 去除头部的空格
console.log(message.trimEnd()); // 去除尾部的空格
⚑ 下面是ES11(2020)
Dynamic Import:后续ES Module模块化中讲解。
Promise.allSettled:后续讲Promise的时候讲解。
import meta:后续ES Module模块化中讲解。
11.23. 大数字 BigInt
在早期的JavaScript中,最大的安全的int类型的数字是:MAX_SAFE_INTEGER
大于MAX_SAFE_INTEGER的数值,表示的可能是正确,也可能是不正确的,看运气,也就是没有安全保证
const maxInt = Number.MAX_SAFE_INTEGER
console.log(maxInt); // 9007199254740991
console.log(maxInt + 1); // 9007199254740992
console.log(maxInt + 2); // 9007199254740992
在ES11中,引入了新的数据类型BigInt,用于表示大的整数:
- BitInt的表示方法是在数值的后面加上n
const bigInt=9007199254740992n
console.log(bigInt+10n); // 9007199254741002n
console.log(bigInt+BigInt(10)); // 9007199254741002n
11.24. ?? Nullish Coalescing Operator
注意:如果node版本太低,es11以上的部分api是不支持的。如??
Nullish Coalescing Operator增加了空值合并操作符:??
应用场景:如果foo有值,则直接返回foo;无值则返回默认值
-
之前我们通常用的是||,但是有个弊端,''和0的布尔值是false,
- ES11前正确做法:var newFoo = foo !== null && foo !== void 0 ? foo : "defualt value";
-
?? :左侧是undefined 或 null才返回右边的值,则可以弥补||的缺点,foo是''和0的话,直接返回foo
let foo
// const newFoo= foo || "defualt value"
const bar= foo ?? "defualt value"
console.log(newFoo);
11.25. ?. Optional Chaining
Optional Chaining(可选链):也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁
const info = {}
// TypeError: Cannot read properties of undefined,等于在执行undefined.name
console.log(info.friends.name);
// 不确定friends有值的情况下,可以用info.friends?.name
// 如果 ?. 左边部分不存在,直接返回undefined,后面的都不会执行了,
console.log(info.friends?.name); // undefined
11.26. Global This
在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
- 比如在浏览器中可以通过this、window来获取;
- 比如在Node中我们需要通过global来获取;
那么在ES11中对获取全局对象进行了统一的规范:globalThis
- 不管在哪种环境下,globalThis都表示当前环境下的全局对象
console.log(globalThis);
11.27. for..in标准化
在ES11之前,虽然很多浏览器支持for...in来遍历对象类型,但是并没有被ECMA标准化。
- 大部分遍历的是对象的key,小部分遍历的是对象的value
在ES11中,对其进行了标准化,for...in是用于遍历对象的key的
⚑ 下面是ES12(2021)
11.28. FinalizationRegistry
FinalizationRegistry类可以监听对象的销毁过程,创建对象时需要传入一个回调函数,当注册在FinalizationRegistry里面的对象被销毁时,会执行这个回调函数
如果注册了多个对象,可以给回调函数传入一个值,这样就可以区分是哪个对象被销毁了
可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;
const finalizationRegistry = new FinalizationRegistry((value)=>{
console.log("注册在finalizationRegistry的对象被销毁了",value);
})
let obj={}
let info={}
// 将obj注册到finalizationRegistry对象中
finalizationRegistry.register(obj,"obj对象")
finalizationRegistry.register(info,"info对象")
obj=null // 执行到这里是,obj不会立马被回收,GC是不定时回收
info=null
11.29. WeakRefs
如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
- 如果我们希望是一个弱引用的话,可以使用WeakRef;
- 也可以使用WeakSet,这里用WeakRef举例说明
const finalizationRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalizationRegistry的对象被销毁了", value);
})
let obj = {name:"why"}
let info = new WeakRef(obj)
// 将obj注册到finalizationRegistry对象中
finalizationRegistry.register(obj, "obj对象")
obj = null // 执行到这里是,obj不会立马被回收,GC是不定时回收
setTimeout(()=>{
console.log(info.deref()?.name); // undefined
},10000)
11.30. logical assignment operators
logical assignment operators:逻辑赋值操作符
// 1. ||= 逻辑或赋值运算
let message= "aaa"
// 下面代码等同 message = message || "defalut value"
message ||= "defalut value"
// 2. &&= 逻辑与赋值运算 (用的少)
const obj={
name:"why",
foo(){
console.log("sss");
}
}
obj && obj.foo && obj.foo() // 一般&&用的多
// 3. ??= 逻辑空赋值运算
let foo=""
foo ??= "defalut value"
console.log(foo); // ""
Numeric Separator:数字分隔符,数值的表示 里讲过了;
11.31. String.replaceAll:字符串替换;
replaceAll:会把p里面所有的dog替换为money
const p = 'The quick y dog. The If dog reacted';
// The quick y monkey. The If monkey reacted
console.log(p.replaceAll('dog', 'monkey'));
十二. Proxy-Reflect
Proxy-Reflect是es6中一个理解的难点,也是vue2-vue3响应式的原理
12.1. ES6之前的监听对象
我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
- 通过之前的属性描述符中的访问器属性描述符来做到;
但是这样做有什么缺点呢?
-
首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的。
- 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。
-
其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。
-
所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。
let obj ={
name:"why",
age:18
}
Object.keys(obj).forEach(key=>{
let value= obj[key]
Object.defineProperty(obj,key,{
get(){
console.log(`${key}被访问了`);
return value
},
set(newValue){
console.log(`${key}被赋值了`);
value= newValue
}
})
})
obj.name="aa"
console.log(obj.name);
obj.age=32
console.log(obj.age);