ES6

字面量的增强

属性(Property Shorthand)、方法(Method Shorthand)的简写,计算属性名(Computed Property Names)

var name = 'fks'
var age = 20
var obj = {
// 1. 属性的简写
// name: name
name,
age,
// 2. 方法的简写
// foo: function() {},
foo() {
console.log(this);
}, // obj.foo() this是obj

// 这是不一样的
// foo: () => {} // this是全局

// 3.计算属性名
[name + 123]: 'hahahah'
}

obj.foo()
console.log(obj);
// { name: 'fks', age: 20, foo: [Function: foo], fks123: 'hahahah' }

解构Destructuring

  • 解构数组
var names = ["asd", 'fgd', 'dkb']

// 本来根据索引拿值
// var item1 = names[0]
// var item2 = names[1]
// var item3 = names[2]

// 解构
var [item1, item2, item3] = names
console.log(item1, item2, item3);

// 解构后面的元素
var [ , , itemz] = names
console.log(itemz);

// 解构一个元素,后面的元素放到一个新数组
var [itemx, ...newNames] = names
console.log(itemx, newNames) // asd [ 'fgd', 'dkb' ]

// 解构的默认值
var [itema, itemb, itemc, itemd = "ddd"] = names
console.log(itemd);
  • 解构对象

    根据key解构

var obj = {
name: 'asd',
age: 20,
height: 1.88
}

// 不规定顺序,只根据key来解构
var {age, name} = obj
console.log(age, name); // 20 asd

// 可以对key新命名
var { name: newName } = obj
console.log(newName);

// 可以设置默认值,如果属性不存在,会使用默认值
var { address: newAddress = '广州' } = obj
console.log(newAddress); // 广州

// 在函数参数中解构
function foo({name, age}) {
console.log(name, age);
}
foo(obj) // asd 20

let/const

let/const使用

let 声明一个变量

const 声明一个常量、衡量(constant)

var foo = 'foo'
let bar = 'bar' //定义变量
const baz = 'baz' // 常量

// 注意1:const 本质上是传递的值不可以修改,如果传递的是一个引用类型(内存地址),是可以通过引用找到对应的对象去修改内部的属性值的
const obj = {
name: 'aaa'
}
obj.name = 'bbb' //可以改
obj = {} // 不可以改 TypeError: Assignment to constant variable.
// let / const 定义的变量名是不可以重复定义的
let foo = 'foo'
let foo = 'foo2' // Identifier 'foo' has already been declared

let/const作用域提升

  • 使用let声明的变量,在声明之前访问会报错

    console.log(foo);
    var foo = 'foo'

    console.log(bar); // 声明之前访问会报错
    // 在这里bar已经被创建出来了
    let bar = 'bar' // Reference(引用)Error: Cannot access 'bar' before initialization
  • 那么是不是意味着foo变量只有在代码执行阶段才会创建呢?

    • 事实上并不是,我们可以看一下ECMA262对 let 和 const 的描述
    image.png
    • 这些变量会被创建在包含他们的词法环境被实例化的时候,但是是不可以访问它们的,直到词法绑定被求值
  • 那 let/const有没有作用域提升呢?

    作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升

    所以被创建出来了,但不能被访问,不能称之为作用域提升

    总结:let、const没有进行作用域提升,但是会在解析阶段被创建出来、

let/const变量保存

let / const 在全局声明变量保存在哪里了?

var foo = 'foo'
console.log(window.foo); // foo

let bar = 'bar'
console.log(window.bar); // undefined 所以不在window
  • let, const 是不会给window添加任何属性的
  • 最新的ECMA标准中对执行上下文的描述
image.png

也就是说我们声明的变量和环境记录是被添加到变量环境中的(在一个对象保存)

  • 但是标准没有规定这个对象是 window对象或者是其他对象

  • 所以JS引擎在解析的时候,会有自己的实现

  • 比如v8中其实是通过VariableMap的一个hashmap来实现它们的存储的

  • 那么window对象呢?

    window对象是早起的GO对象,在最新的实现中其实是浏览器添加的全局对象,并且 一直保持了window和var之间值的相等性

块级作用域

var 没有块级作用域

  • ES5中,JS只会形成两个作用域:全局作用域函数作用域

    image.png
  • var 没有块级作用域

    ES5中放到一个代码中定义的变量,外面是可以访问的

    {
    var foo = "foo"
    }

    console.log(foo); // foo

let / const 的块级作用域

ES6中新增了块级作用域,通过let、const、function、class 声明的标识符是具备块级作用域的限制的

注意函数,某些引擎会对函数的声明进行特殊处理,允许像var那样提升

{
let foo = "aaa"
function demo() {
console.log('demo function');
}
class Person {}
}

// console.log(foo); // ReferenceError: foo is not defined\

// 对于函数,不同的浏览器有不同实现,(大部分浏览器为了兼容以前的代码.让function没有块级作用域) 所以这里可以访问demo
// demo() // demo function 可以访问

var p = new Person() // ReferenceError: Person is not defined
  • if/switch/for 语句的代码就是块级作用域
// if语句的代码就是块级作用域
if (true) {
let foo = 'foo'
}

console.log(foo); // 不能访问

// switch
switch (color) {
case "red":
let bar = "bar"
}

console.log(bar); // 不能访问

// for
for (var i = 0; i < 6; i++) {
}
// 循环执行结束, 全局中 i 是 6
console.log(i); // 6

// 如果用let
for(let j = 0; j < 6; j++) {
}
// 循环结束后, 全局中并不能访问j
console.log(j); // j is not defined

块级作用域的应用场景

一个循环打印的问题

// 注意setTimeout是宏任务,循环结束后才执行
for (var i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i); // 打印 6 个 6,打印的是全局的 i
})
}

// 一种解决方法,立即执行函数
for (var i = 0; i < 6; i++) {
(function(i) {
// 打印的时候往上层作用域查找,找这里的i
setTimeout(() => {
console.log(i); // 0 1 2 3 4 5
})
})(i)
}

// 如果用let,一次循环一个块,互不影响
for(let j = 0; j < 6; j++) {
setTimeout(() => {
console.log(j); // 0 1 2 3 4 5
})
}

const注意的地方

因为有 i++,第二次循环需要用到第一次循环的i去++,const定义的是常量,是不可以改变的

for (const i = 0; i < 3; i++) {
console.log(i);
}
// TypeError: Assignment to constant variable.

但是ES6新增的 for…of可以

for (const item of names) {
console.log(item);
}

每次都会有一个新的const item,在不同的块级作用域中

暂时性死区

在一个代码中,使用let、const声明的变量,在声明之前都是不可以访问的

这种现象称之为暂时性死区

function foo() {
console.log(bar); // 不可访问
let bar = 'abc'
}

foo()

关于字符串

模板字符串

使用:

const name = 'sfa'
const age = 18

const message = `my name is ${name}, age is ${age}`
console.log(message); // my name is sfa, age is 18

${}里面也可以是表达式,也可以是函数

const str = `age is ${age * 2}`
const str2 = `double age is ${doubleAge()}`

标签字符串

应该很少用到

// 第一个参数是模块字符串中的整个字符串,但是被切成多块放到一个数组
function foo(m, n, x) {
console.log(m, n, x);
// [ 'Hello', 'World', 'aaa' ] yuzi 18
}

// 可以这样去调用一个函数
// foo``
// 当然也可以传参
const name = 'yuzi'
const age = 18
foo`Hello${name}World${age}aaa`

关于函数

函数的默认参数

如果没有传参,就会用默认参数

function foo(m = 'aaa', n = 'bbb') {
console.log(m, n);
}

默认参数也可以是对象,并且可以对对象解构,有两种解构方式

function foo({name, age} = {name: 'asd', age: 18}) {
console.log(name, age);
}

function foo2({name = 'asd', age = 18} = {}) {

}

另外注意:默认形参最好放到最后

function bar(x, y, z = 30) {
console.log(x, y, z)
}
// 获取 函数的length值的时候,只获取默认形参之前的参数个数
console.log(bar.length); // 2

函数的剩余参数

剩余参数必须放到最后

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

foo(1,2,3,4,5,6)

箭头函数的补充

没有自己的this,没有arguments

没有显式原型,所以不能作为构造函数,不能使用new创建对象

var bar = () => {
console.log(this, arguments);
}

const b = new bar() // TypeError: bar is not a constructor

展开语法…

const names = ['aaa','bbb','ccc']
const name2 = 'kkk'

function foo(x, y, z) {
console.log(x, y, z);
}
// 1. 函数调用时使用
foo(...names) // aaa bbb ccc

// 2. 构造数组时
const newNames = [...names, ...name2]
console.log(newNames);
// [ 'aaa', 'bbb', 'ccc', 'k', 'k', 'k' ]
// 注意这里把字符串展开了

// 3.构建对象字面量时 ES2018(ES9)
const info = {name: 'ggg', height: 1.88}
// 拷贝info的属性,并且可以添加新的属性
const obj = {...info, address: '广州市'}
console.log(obj)
// { name: 'ggg', height: 1.88, address: '广州市' }

注意:展开运算符其实是一种浅拷贝

简单来说就是,拷贝的地址,指向的是同一个对象

const info = {
name: 'fff',
hobby: {
h1: 'study',
h2: 'swim'
}
}

const newInfo = {...info}
newInfo.hobby.h1 = 'sss'

console.log(info.hobby.h1); // sss 原来的info也改了

表示数值的方法(进制)

ob开头(binary):二进制,0o开头(octonary):八进制,0x开头(hexadecimal):十六进制

const num1 = 100 // 十进制
const num2 = 0b100 // 二进制
const num3 = 0o100 // 八进制
const num4 = 0x100 // 十六进制
console.log(num1, num2, num3, num4); // 100 4 64 256

另外,大的数值有一个连接符,便于阅读(ES2021 ES12)

const num = 10_000_000_000_000
console.log(num); // 10000000000000

Symbol的使用

主要是用来创建独一无二的key值,注意使用的时候不要用new

创建Symbol

const s1 = Symbol()
const s2 = Symbol() // s1,s2是不一样的
console.log(s1 === s2); // false

// ES2019(ES10)中, Symbol还有一个描述(description)
// 便于标记是哪个symbol,就像是标签
const s3 = Symbol('aaa')
console.log(s3); // Symbol(aaa)
// 可以拿到这个描述
console.log(s3.description); // 'aaa'

在对象中作为key使用

const obj = {
[s1]: 'yyy',
[s2]: 'mmm'
}

// 获取属性值
console.log(obj[s1]); // 'yyy'
// 新增属性值
obj[s3] = 'kkk'
// 也可以Object.defineProperty()方法添加属性
Object.defineProperty(obj, s4, {})

注意:获取属性值的时候不能通过 . 语法获取,因为 . 后面的是会被当做字符串

obj.s1,那么就会去找obj上key为’s1’这个属性值

使用Symbol作为key的属性名,在遍历的时候是获取不到的

console.log(Object.keys(obj)); // []
console.log(Object.getOwnPropertyNames(obj)); // []

如果想要获取的话可以使用 Object.getOwnPropertySymbols()方法

const sKeys = Object.getOwnPropertySymbols(obj)
console.log(sKeys); // [ Symbol(), Symbol(), Symbol(aaa) ]

怎么让两个Symbol相等?

Symbol.for(key) 方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol

注意这跟 Symbol(‘aaa’) 标签是不一样的哦

// 也就是给symbol一个key
const sa = Symbol.for("aaa")
const sb = Symbol.for("aaa")
console.log(sa === sb); // true

根据 symbol值获取key

Symbol.keyFor(sym) 方法用来获取全局symbol 注册表中与某个 symbol 关联的键

const key = Symbol.keyFor(sa)
console.log(key); // aaa

Set

新增的一种数据结构(存储数据的方式)

是集合,里面的元素不重复

  • 创建set和添加元素
// 1. 创建
const set = new Set()

// 添加元素
set.add(10)
set.add(20)
set.add(30)
console.log(set); // Set(3) { 10, 20, 30 }

// 不重复
set.add(10)
console.log(set);// Set(3) { 10, 20, 30 }

注意添加对象时候的一种情况,因为添加的对象的地址是不一样的

set.add({})
set.add({})
console.log(set); // { {}, {} }

// 以下添加的才是同一个对象
const obj = {}
set.add(obj)
set.add(obj)
console.log(set); // { {} }
  • 最常见的应用:对数组去重
// 去重(把数组放进集合)
const arr = [10, 20, 30, 40, 10, 20]
const arrSet = new Set(arr)
console.log(arrSet); // Set(4) { 10, 20, 30, 40 }
  • Set的属性
set.size 返回元素个数
  • Set的方法
add() 添加元素
clear() 清空集合元素
delete(x) 删除某元素
has(x) 是否包含x元素

遍历:forEach
arrSet.forEach(item => {
console.log(item);
})

for (const item of arrSet) {
console.log(item);
}

WeakSet

用的很少,对于WeakSet的应用我就不纠结了

WeakSet跟set的区别:WeakSet只能存放对象类型

WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收

另外,WeakSet不能遍历,因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素那么有可能造成对象不能正常销毁,所以存储到WeakSet中的对象是没办法获取的

const weakSet = new WeakSet()

weakSet.add({name: 'asd'}, {}) { 地址1, 地址2 } 弱引用
console.log(weakSet); // WeakSet { <items unknown> }

我对弱引用的理解

image.png

关于强引用弱引用的一篇文章:https://juejin.cn/post/6993101968545677319#heading-21

Map

一种数据结构,键值对的形式,并且键可以是对象

普通对象的键不能是对象?不能

const obj1 = { name: 'aaa' }
const obj2 = { name: 'bbb' }

const info = {
[obj1]: 'qqq',
[obj2]: 'kkk'
}

console.log(info);
// { '[object Object]': 'kkk' }
// 这下懂了吧,这个键会被转成字符串'[object Object]',key重复了,值覆盖了

怎么使用Map?

// 使用
const map = new Map()
// 添加键值
map.set(obj1, 'aaa')
map.set(obj2, 'bbb')
map.set(1, 'ccc')
console.log(map);
// 打印
// Map(3) {
// { name: 'aaa' } => 'aaa',
// { name: 'bbb' } => 'bbb',
// 1 => 'ccc'
// }

// 另一种添加,把键值对放到数组
const map2 = new Map([[obj1, 'aaa'], [obj2, 'bbb']])
console.log(map2);
// Map(2) { { name: 'aaa' } => 'aaa', { name: 'bbb' } => 'bbb' }

有什么属性和方法?

属性:size:返回map对象的成员数量

方法:

  • set(key, value) :添加,设置键值,返回整个Map对象

  • get(key):根据key获取值

  • has(key):是否存在某一个key,返回布尔值

  • delete(key):删除一个键值对,返回布尔值

  • clear():清空元素

  • 遍历 forEach()

    // 遍历
    map.forEach((item, key) => {
    console.log(item, key);
    })

    // for..of遍历注意
    for (const item of map) {
    // console.log(item); // 这里把一个键值对放到一个数组了
    // [ { name: 'bbb' }, 'bbb' ]
    // 所以可以根据索引值分别拿 key, value
    console.log(item[0], item[1]);
    }

    // 或者进行解构
    for (const [key, value] of map) {
    console.log(key, value);
    }

WeakMap

和Map有什么区别呢?

  • WeakMap的key只能使用对象
  • WeakMap的key对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收

常见方法:

  • set(key, value):添加键值,返回整个对象
  • get(key):根据key获取值
  • has(key):是否包含某个key,返回布尔值
  • delete(key):根据key删除键值对,返回布尔值

WeakMap不能遍历

const obj = {
name: 'asd'
}

const weakMap = new WeakMap()
weakMap.set(obj, '123')

console.log(weakMap); // WeakMap { <items unknown> }

响应式原理中的WeakMap使用

可以看看Vue3的响应式原理,之前实现mini-vue的时候实现过响应式系统

简单的思想就是:

创建一个WeakMap, key是obj1,值是一个Map结构,

这个Map,键是对象的属性,值是有依赖这个属性的函数,

当这个属性发生什么改变的时候,就执行这个属性所有依赖的函数

一个简单的响应式模拟实现

// 应用场景(vue3响应式原理)
const obj1 = {
name: 'qwe',
age: 20
}

function obj1NameFn1() {
console.log('obj1NameFn1被执行');
}

function obj1NameFn2() {
console.log('obj1NameFn2被执行');
}

function obj1AgeFn1() {
console.log("obj1AgeFn1")
}

function obj1AgeFn2() {
console.log("obj1AgeFn2")
}

// 1.创建WeakMap
const weakMap = new WeakMap()
// 2.收集依赖结构
// 2.1.对obj1收集的数据结构
const obj1Map = new Map()
obj1Map.set('name', [obj1NameFn1, obj1NameFn2])
obj1Map.set('age', [obj1AgeFn1, obj1AgeFn2])

weakMap.set(obj1, obj1Map)
// 如果obj1.name发生改变
obj1.name = 'ggg'
const targetMap = weakMap.get(obj1)
const fns = targetMap.get("name")
fns.forEach(fn => {
fn()
});

ES7

Array Includes

判断数组中是否包含某个元素

在之前我们经常使用 indexOf() 判断,该方法返回元素所在的索引值,没有就返回-1

const names = ['aaa', 'bbb', 'ccc', 'ddd']

if (names.indexOf('bbb') !== -1) {
console.log('包含bbb元素');
}

ES7 新增 Includes 方法

if (names.includes('bbb')) {
console.log('包含bbb元素');
}

对于 NaN的处理,indexOf()不能判断,includes可以

const names = ['aaa', 'bbb', 'ccc', 'ddd', NaN]
console.log(names.indexOf(NaN)); // -1
console.log(names.includes(NaN)); // true

指数的运算方法

// 在之前,指数运算通过 Math.pow()
const res1 = Math.pow(3, 3)

// ES7新增:**
const res2 = 3 ** 3

console.log(res1, res2); // 27 27

ES8

Object.values

之前我们可以通过 Object.keys()来获取一个对象的所有key,ES8提供Object.values() 获取所有值

const obj = {
name: 'aaa',
age: 18
}

console.log(Object.keys(obj)); // [ 'name', 'age' ]
console.log(Object.values(obj)); // [ 'aaa', 18 ]

// 用的非常少
console.log(Object.values(['aaa', 'bbb', 'ccc']));
console.log(Object.values('asd')); // [ 'a', 's', 'd' ]

Object.entries

获取到一个数组,数组中存放可枚举属性的键值对数组

const obj = {
name: 'asdf',
age: 20
}

const objEntries = Object.entries(obj)
console.log(objEntries); // [ [ 'name', 'asdf' ], [ 'age', 20 ] ]
// 然后可以继续遍历这个数组,获得属性,值
objEntries.forEach(item => {
console.log(item[0], item[1]);
})

console.log(Object.entries(['aaa','bbb','ccc']));
// [ [ '0', 'aaa' ], [ '1', 'bbb' ], [ '2', 'ccc' ] ] 索引值当做key了

console.log(Object.entries('sdf')); // [ [ '0', 's' ], [ '1', 'd' ], [ '2', 'f' ] ]

padStart和padEnd

根据所给长度,和填充元素来往前或往后填充数组

const message = "hello world"

const newMessage = message.padStart(15, "*").padEnd(20, '-')
console.log(newMessage); // ****hello world-----

小案例:假设要对银行卡号用*隐藏

// 案例
const cardNumber = "123456781231548564321"
const lastFourCard = cardNumber.slice(-4)
const finalCard = lastFourCard.padStart(cardNumber.length, "*")
console.log(finalCard); // *****************4321

slice 可以传入负数的哦,-4,倒数4位

Trailing-Commas

ES8,允许在函数定义和调用时多加一个逗号

没什么,反正我们平时也不会加。trailing:后面的,commas:逗号

function foo(a, b,) {}
foo(10, 20,)

Object Descriptors

ES8中新增对对象的操作,Object.getOwnPropertyDescriptors()

获取一个对象的所有自身属性的描述符

const obj = {
age: 18
}
Object.defineProperty(obj, 'name', {
value: 'hello',
enumerable: true,
writable: true,
configurable: false
})

console.log(Object.getOwnPropertyDescriptors(obj));
// 打印
{
age: { value: 18, writable: true, enumerable: true, configurable: true },
name: {
value: 'hello',
writable: true,
enumerable: true,
configurable: false
}
}

ES8 新增async await 的函数使用,后面另外记

ES9

Async iterators:迭代器,后续另外笔记

Object spread operators:对象展开运算符 const newObj = {...obj, age:18}

Promise finally:后续Promise统一记

ES10

flat flatMap

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

// flat
const nums = [10, 20, [30, 40],[[1, 2],[3, 4]]]
const newNums = nums.flat()
// 默认深度为1
console.log(newNums); // [ 10, 20, 30, 40, [ 1, 2 ], [ 3, 4 ] ]

const newNums2 = nums.flat(2) //
console.log(newNums2); // [10, 20, 30, 40,1, 2, 3, 4]

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组

先进行map,再flat,并且flat的深度为1

const nums2 = [1,2,3,4,5]
const newNums2 = nums2.flatMap(item => {
return item * 2
})
console.log(newNums2); // [ 2, 4, 6, 8, 10 ]

这样看起来跟map没什么区别呀?那flatMap有什么应用场景吗?

处理这样的字符串数组,把字符串分成一个个放到数组

const message = ["Hello World", "hello kk", "my name is xxx"]

const words = message.flatMap(item => {
return item.split(' ')
})
console.log(words);
// ['Hello', 'World','hello', 'kk','my','name','is','xxx']

Object fromEntries

ES8有Object.entries将一个对象转换成entries

ES10有Object.fromEntries 将entries转换成对象

const obj = {
name: 'qws',
age: 18
}
// ES8 entries
const entries = Object.entries(obj)
console.log(entries); // [ [ 'name', 'qws' ], [ 'age', 18 ] ]

const newObj = Object.fromEntries(entries)
console.log(newObj); // { name: 'qws', age: 18 }

有什么应用场景吗?

可以处理query参数,最终转换成对象使用

看到键值对的数组可以想到这个方法

const queryString = 'name=xxx&age=18&height=1.88'
const queryParams = new URLSearchParams(queryString)
console.log(queryParams);
// URLSearchParams { 'name' => 'xxx', 'age' => '18', 'height' => '1.88' }
for (const param of queryParams) {
console.log(param);
}
// 打印
// [ 'name', 'xxx' ]
// [ 'age', '18' ]
// [ 'height', '1.88' ]

// 然后就可以利用fromEntries转成对象
const paramObj = Object.fromEntries(queryParams)
console.log(paramObj);
// { name: 'xxx', age: '18', height: '1.88' }

trimStart trimEnd

单独去除前面或后面的空格

const str = '  hello world   '
console.log(str.trim()); // 去除前后的空格
console.log(str.trimStart()); // 去除前面的空格
console.log(str.trimEnd()); // 去除后面的空格

Symbol description

s1.description 这个属性可以获取symbol的描述(标签),前面symbol的笔记有写到

Optional catch binding

后续在try catch部分笔记

ES11

BigInt

ES11引入的新的数据类型,用于表示大的整数

使用:加个n,或者 BigInt(num)

// ES11前 最大的安全整数
const maxInt = Number.MAX_SAFE_INTEGER
console.log(maxInt); // 9007199254740991
// 超过这个安全整数就可能出现表示错误
console.log(maxInt + 1); // 9007199254740992
console.log(maxInt + 2); // 9007199254740992 (错了)

// ES11之后: BigInt
const bigInt = 900719925474099100n
console.log(bigInt + 10n); // 900719925474099110n

console.log(BigInt(100)); // 100n
console.log(bigInt + BigInt(100));

// 也可以把bigInt类型转Number
console.log(Number(bigInt)); // 900719925474099100

Nullish Coalescing Operator

空值 合并 运算

新增的操作符:??

处理如果为空,那么使用默认值的判断

之前的 || 不能很好的处理空字符串,空串转为false,执行后面的语句

const foo = ""
const res1 = foo || 'default value'
const res2 = foo ?? 'default value'

console.log(res1); // default value
console.log(res2); // ''

Optional Chaining

可选链:让我们的代码在进行 null 和 undefined 判断时更加清晰和简洁

const info = {
name: 'xxx',
// friend: {
// f1: {
// name: 'mm'
// }
// }
}
// 在之前,如果获取不存在的属性值
console.log(info.name.friend); // undefined
// 我们会做这样的判断
if (info && info.friend && info.friend.f1) {
console.log(info.friend.f1.name);
}

// ES11的可选链
console.log(info.friend?.f1?.name);
// info.friend存在吗?存在再继续 .f1
console.log('其他代码逻辑');

Global This

在之前我们想获取JS环境的全局对象,不同环境获取的方式又不一样

比如在浏览器中,通过 this,window来获取

Node中,通过global获取

所以 ES11 中对获取全局对象进行统一的规范:globalThis

// 浏览器下
console.log(this); // Window
console.log(window); // Window

// Node下
console.log(global);

// ES11 各种环境获取全局对象
console.log(globalThis);

for…in标准化

在ES11之前,虽然很多浏览器支持 for…in 来遍历对象,但是并没有被ECMA标准化

ES11中,对其进行标准化,for…in 用于遍历对象的 key

const obj = {
name: 'xxx',
age: 18
}
for (key in obj) {
console.log(key); // name // age
}

Dynamic Import

后续在 ES Module 模块化中笔记

Promise.allSettled

后续Promise中笔记

import meta

后续在 ES Module 模块化中笔记

ES12

FinalizationRegistry

FinalizationRegistry对象可以让你在对象被垃圾回收时请求一个回调

当一个在注册表中注册的对象被回收时,可以请求在某个时间点上调用一个清理回调(清理回调有时被称为 finalizer)

注册:register方法,注册任何想要清理后执行回调的对象,传入该对象,和所含的值

const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象,某个被销毁", value);
})

let obj = { name: 'asd' }
let info = { age: 15 }

finalRegistry.register(obj, 'obj')
finalRegistry.register(info, 'val')

obj = null
info = null
// 注册在finalRegistry的对象,某个被销毁 val
// 注册在finalRegistry的对象,某个被销毁 obj

WeakRefs

如果我们默认将一个对象赋值给另一个引用,那么这个引用是强引用

如果我们希望是弱引用,可以使用 WeakRef

注意这里有一个 deref 方法获取弱引用的值

const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象,某个被销毁", value);
})

let obj = { name: 'asd' }
let info = new WeakRef(obj)

finalRegistry.register(obj, 'obj')

// obj = null
// 注册在finalRegistry的对象,某个被销毁 obj

// 那还能获取info吗?
// 因为回收要时间,所以这里也做一个延迟才获取属性值
setTimeout(() => {
console.log(info.name); // undefined(不能这样获取)
console.log(info.deref()?.name);
// 如果原对象obj没有被销毁, 可以使用deref()来获取
// 如果被销毁了,获取到的是 undefined
}, 10000);

logical assignment operators

logical:逻辑的

几个逻辑赋值运算符 ||=,&&=(很少用),??=

可读性不是很好

  • ||= 逻辑或赋值运算
let message = ''
// message = message || 'default value'
message ||= 'default value'
console.log(message); // default value
// 也是不能判断空串的
  • &&= 逻辑与赋值运算(很少用)
let obj = {
name: 'xxx'
}

// 有值取值
info = obj && obj.name
console.log(info); // xxx

obj &&= obj.name
console.log(obj); // xxx
  • ??= 逻辑空赋值运算 0 “”

感觉是可以填补 || 不能很好的判断"" 0

let message = ""
let msg = 0
message ??= "default value"
msg ??= "default val"
console.log(message); // ""
console.log(msg); // 0

Numeric Separator

Numeric 数值的 Separator 分离器

大的数值有一个连接符 _ ,便于阅读

const num = 10_000_000_000_000
console.log(num); // 10000000000000

String.replaceAll

字符串替换,替换所有匹配条件的地方

const res = 'aabbcc'.replaceAll('b', '.');
console.log(res);// 'aa..cc'

之前replace只换第一处匹配的地方

const res = 'aabbcc'.replace('b', '.');
console.log(res);// aa.bcc