ES6

字面量增强

ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。

属性的简写:Property Shorthand
方法的简写:Method Shorthand
计算属性名:Computed Property Names

// 1.属性的简写
    var obj = {
        name: name
        age: age
    }
    // 增强后
    var obj = {
        name,
        age
    }
    
    // 2.方法的简写
    var obj = {
        foo: function() {}
    }
    // 增强后
    var obj = {
        foo() {},
    }
    
    // 3.计算属性名
    var name = "test"; 
    var obj = {}
    obj[name] = "haha"; //如果key是一个变量 不能用.的方式 要使用[]
    // 增强后
    var obj = {
      [name]: "haha"
    }

解构

ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring
数组解构

var names = ["a", "b", "c"];
 var ages = [1, 2, 3, 4, 5];

 // 取出数组中某一个
 var [item1, item2, item3] = names;

 // 解构出后面两个元素
 var [, item2, item3] = names

 // 解构出第一个元素,后面的元素放到一个新数组中
 var [itemx, ...newNames] = names

 // 解构默认值
 var [item1, item2, item3, item4 = "d"] = names
 console.log(item4)
 
 // 合并两个数组
 var arr = [...names, ...ages]

对象解构

var obj = {
      name: "why",
      age: 18,
      height: 1.88
    };

    var obj1 = {
      name: "zhangsan",
      address: "深圳"
    }


    // 对象解构
    var { name, age, height } = obj;

    // // 解构出某个key,后面的key放到一个新对象中
    var { age, ...newObj } = obj;

    // // 解构出某个key 并且重新命名
    var { name, age: newAge, height } = obj;

    // 不用delete 如何删除 对象的某个属性? delete obj.a
    var { name, ...rest } = obj // 删除了a , c
    obj = rest;
    
    // 合并两个对象 相同的属性会被覆盖
    var obj2 = {...obj, ...obj1}

var/let/const

var

  • 没有块级作用域,是弱类型,支持变量提升。
  • 可以重复声明,没有报错和警告
  • ES5中只有全局作用域和函数作用域,大部分人会采用闭包来解决ES5的问题

let

  • 只有块级作用域 window无法访问
  • 不存在变量提升(解析阶段会被创建出来,但是不能被访问)
  • 存在暂时性死区(TDZ)
//"暂时性死区"也意味着**typeof**不再是一个百分之百安全的操作。
//在没有**let**之前,**typeof**是百分百安全的,现在这一点不成立了.
typeof x; //ReferenceError: a is not defined.
let x;
  • 不允许重复声明

const

  • 只有块级作用域 window无法访问
  • 不存在变量提升(解析阶段会被创建出来,但是不能被访问)
  • 存在暂时性死区(TDZ)
  • 不允许重复声明
  • 声明时必须赋值,且后续不允许修改,如果是对象,对象的属性可以被修改。

实际开发如何选择

首先排除var在let和const之间做选择,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同.

作用域

在ES6之前,只存在全局作用域和函数作用域。

var text = "全局作用域";
function foo() {
   var bar = "函数作用域,只有在这个函数才能访问"
}

在ES6的时候有了块级作用域

{
   var x = 123;
   let y = 456;
   const z = 789;
}
// 只有x 能正常被打印 
console.log(x,y,z);

在ES6中,块级作用域对于var是没有作用的,var只有函数作用域和全局作用域。let、const存在块级作用域、函数作用域、全局作用域。块级作用域对于let、const、function、class等等一些是有效的。

{
    function test() {
        onsole.log("测试块级作用域是否有效")
    }
    class Person{}
}
test(); // 能够正常调用函数,为什么呢?
let p = new Person(); //Person is not defined

上面说块级作用域对于function有效,那为什么在块级作用域之外能够调用函数呢?

不同浏览器有不同的实现(大部分浏览器为了兼容以前的代码),让我们的function是没有块级作用域的。但是在标准中,块级作用域对于function是存在的。但是如果有某一个浏览器 只支持ES6及以上的代码,这个时候test函数是访问不到的。

块级作用域的补充

const names = [1,2,3,4,5]
    
    // 这里是不能 使用const的 虽然const是块级作用域
    // 但是每次在块级作用域里面都在尝试修改 i ---> i++
    for(const i = 0; i < names.length; i++){
        console.log(names[i]);
    }
    
    // {
    //   i = 0;
    //   const i = i;
    // }
    
    // {
    //   i++;
    //   const i = i;  // const是没法被修改的,const初始化也必须要有值
    //   console.log(names[i]);
    // }

    // {
    //   i++;
    //   const i = i;  
    //   console.log(names[i]);
    // }
    
    // ...
    
    
    // for ... of 遍历可迭代对象
    // 这里是可以使用 const的
    for(const item of names) {
        console.log(item);
    }
    
    // 块级作用域
    // {
    //   const item = 1;
    //   console.log(item);
    // }

    // {
    //   const item = 2;
    //   console.log(item);
    // }
    
    ///.....

字符串模板

在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)

const name = "malong";
    const age = 18;
    const height = 1.88;
    
    console.log(`my name is ${name}`);
    console.log(`我是成年人吗? ${ age > 18 ? '是' : '否'}`)
    
    function foo() {
        return "function is foo";
    }
    
    console.log(`mu function ${foo()}`);

标签模板字符串的使用

function foo(...args) {
      console.log(args);
    }
    
    const name = "why";
    const age = 18;
    
    foo`Hello${name}Wor${age}ld`;

如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:

模板字符串被拆分了;
第一个元素是数组,是被模块字符串拆分的字符串组合;
后面的元素是一个个模块字符串传入的内容;

es6 对象某个key是否存在_javascript

函数的默认参数

在ES6之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求

传入了参数,那么使用传入的参数;
没有传入参数,那么使用一个默认值;

// ES5 写法
    function foo( x , y) {
        x = 20 || x;
        y = 30 || y;
        console.log(x, y);
    } 
    
    
    // ES6 写法
    function foo( x = 20 , y = 30) {
        console.log(x , y)
    }
    
    foo(10,20); // 10 20
    foo(); // 20 30
    
    foo(0,"") // es5 有bug  es6没有bug

函数的剩余参数

ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:

如果最后一个参数是 … 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;

// 剩余参数只能放到最后面
    function foo( m, n, ...args) {
      console.log(m, n)
      console.log(args)

      console.log(arguments)
    }
    // args 是一个数组 (ES6新增想要替代arguments)
    // arguments 是伪数组
    // args只包含那些没有对应形参的实参,arguments 对象包含了传给函数的所有实参
    foo(20, 30, 40, 50, 60)

箭头函数

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

var foo = () => {
    console.log("foo");
}
    
console.log(foo.prototype); //undefined
var f = new foo(); //TypeError: foo is not constructor

展开运算符

在函数调用时使用

const names = [1,2,3];
    
    function foo(x, y, z){
        console.log(x,y,z);
    }
    // 展开运算符
    foo(...names);

在数组构造时使用

const names = [1,2,3];
    const address = [1,2,4];
    const newObj = [...names , ...address]; // 1,2,3,1,2,4

在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性

const names = [1,2,3]
    const address = "test";
    
    // {0: 1, 1: 2, 2: 3, address: 'test', age: 18}
    const newObj = { ...names, address, age: 18};

展开运算符浅拷贝

const info = {
        name: "why",
        friend: {name: "kebi"}
    }
    
    const obj = { ...info, name:"coderwhy" };
   
    obj.friend.name = "test";
    console.log(info.friend.name); // 会被改变成test

es6 对象某个key是否存在_ES6_02

Symbol

Symbol在通常在对象中表示唯一的属性名

// 在对象中,属性名key是唯一的,相同的会覆盖,但是Symbol就是用来创建一个独一无二的值
    // 可以让其不被覆盖
    
   const s1 = Symbol("abc");
   const s2 = Symbol("bcd");
   
   const obj = {};
   
   // 1.写法1:属性名赋值 , 不能使用.的语法
   obj[s1] = "abc";
   obj[s2] = "bcd";
   //obj.s1 = "abc" 错误
   
   // 2.写法2 Object.defineProperty
   Object.defineProperty(obj,s1,{
        configurable: true,
        writable: true,
        enumerable: true,
        value: "abc"
   })
   
   
   // 3.写法3 字面量
   const info = {
       [s1]: "abc",
       [s2]: "bcd"
   };
   
   // 获取Symbol的key 有固定的方法 使用循环 Object.keys都是获取不到的
   const symbolKyes = Object.getOwnPropertySymbols(info);

    // symbol 的key
    console.log(symbolKyes)

    // 遍历info for of遍历的是value   ---- for in 遍历的是key
    for (const item of symbolKyes) {
      console.log(info[item])
    }

相同值的Symbol

// 使用for  让描述相同 就可以了
    const s1 = Symbol.for("aaa");
    const s2 = Symbol.for("aaa");
    console.log(s1 === s2); // true
    
    // keyFor 也可以 key要是 Symbol创建出来的
    const s3 = Symbol.keyFor(s1);
    const s4 = Symbol.keyFor(s2);
    console.log(s3 === s4); //true
    
    const s5 = Symbol("aaa");
    const s6 = Symbol("aaa");
    console.log(s5 === s6); // false

Set

在ES6之前,我们存储数据的结构主要有两种:数组、对象。

在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。

Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。

创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):

Set常见的属性和方法

size 返回Set中元素的个数。[属性]
add(value) 添加某个Set元素,并返回对象本身。
delete(key):根据key删除一个键值对,返回Boolean类型。
has(value) 判断某个元素是否存在,返回boolean类型。
clear() 清空所有元素,没有返回值。
forEach() 遍历

使用场景:数组去重

WeakSet

和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?

区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
无法遍历

常见方法

add(value):添加某个元素,返回WeakSet对象本身;
delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
has(value):判断WeakSet中是否存在某个元素,返回boolean类型;

使用场景

// Stack Overflow
  const pwset = new WeakSet();

  class Person {
    // new的时候会执行constructor里面的代码
    constructor() {
      pwset.add(this)
    }

    running() {
      if (!pwset.has(this)) {
        throw new Error('不能通过其他对象来调用running方法')
      }
      console.log("running", this)
    }
  }

  var p = new Person();
  console.log(p.running())
  // 会报错
  p.running.call({ })
  
  // 这里为什么使用WeakSet呢? 
  // 因为WeakSet是弱引用 我们把p = null 后 就会被GC回收
  // 如果使用Set 就会是强引用 ,我们把p = null 后 还要 set.delete(p) 才会被GC回收

强引用和弱引用的区别

弱引用

弱引用与强引用相对, 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。因此使用弱引用可以防止内存泄漏。在JavaScript中弱引用的WeakMap和WeakSet。

es6 对象某个key是否存在_javascript_03

强引用

JavaScript中最常见的就是声明一个变量并且将一个引用类型数据(对象)赋值给这个变量,这种情况就是强引用。只要该对象还被引用,垃圾回收机制GC就不会回收该对象或者属性。对于一个普通对象,将全部引用该对象的变量关系相应设置为null,便能等待垃圾回收机制GC回收。

es6 对象某个key是否存在_javascript_04

map

数据结构Map,用于存储映射关系。

事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;对象的key内部会自动toString();

Map常见的属性和方法

size 返回Set中元素的个数。[属性]
set(key, value):在Map中添加key、value,并且返回整个Map对象。
get(key):根据key获取Map中的value。
has(value) 判断某个元素是否存在,返回boolean类型。
clear() 清空所有元素,没有返回值。
forEach(value,key,map) 遍历

使用场景:缓存

const decorator = (fn) => {
      const mapList = new Map();
      return (x, y) => {
        const sum = `${x}${y}`;
        if (mapList.has(sum)) {
          return mapList.get(sum);
        }
        const res = fn(x, y)
        mapList.set(sum, res);
        return res;
      }
    }

    let work = (a, b) => {
      return a + b;
    }

    work = decorator(work)
    work(1, 2);
    work(1, 2); // 这里实际上用的缓存
    work(3, 4);
    work(3, 4); // 这里实际上用的缓存

测试

// 对比Object,JavaScript对象的key只能是字符串或者是ES6新增的Symbol
    const obj = { name: "why" };
    const obj1 = { name: "coderwhy" };

    const info = {
      [obj]: "zhangsan",
      [obj1]: "lisi"
    };
    console.log(obj); //我们会发现key 被自动toString了,而且也会被覆盖。


    // 使用map作为key
    var map = new Map();
    map.set(obj, "zhangsan"); // 返回整个Map对象
    map.set(obj1, "lisi");  // 返回整个Map对象
    map.set("1", 2); // 返回整个Map对象
    map.set("3", 4); // 返回整个Map对象
    // 通过key 获取 value
    map.get(obj);        // 返回key 对应的 value
    // 获取长度
    console.log(map.size); // 返回长度
    // 通过key 删除
    map.delete(obj); // 返回boolean
    // 判断key 是否存在
    map.has(obj1); // 返回boolean


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

    // for...of 遍历
    // 结构出来
    for (const [key, value] of map) {
      console.log(key, value);
    }

    // for...of 遍历
    // 不解构
    for (const item of map) {
      // 0对应key 1对应value
      console.log(item[0], item[1]);
    }

WeakMap

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类型

Weak使用场景(vue3响应式原理)

const obj1 = {
      name: "why",
      age: 18
    }

    const obj2 = {
      name: "why",
      age: 18
    }

    // 1.创建WeakMap
    const weakMap = new WeakMap();

    // 2. 收集依赖结构
    // 2.1 使用map来收集
    const obj1Map = new Map();
    obj1Map.set("name", [obj1GetName, obj1SetName])
    obj1Map.set("age", [obj1GetAge, obj1SetAge])
    weakMap.set(obj1, obj1Map)


    // 3.如果obj1.name发生改变
    // Proxy/Object.defineProperty
    obj1.name = "test";
    const targetMap = weakMap.get(obj1);
    const fns = targetMap.get("name")
    fns.forEach(item => item())


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

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

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

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

ES7

includes

const names = ["abc", "cba", "nba", "mba", NaN];
        
    if (names.indexOf("abc") !== -1) {
      console.log("包含abc")
    }

    if (names.indexOf(NaN) !== -1) {
      console.log("包含NaN"); //不会打印
    }

    if (names.includes(NaN)) {
      console.log("包含NaN"); //会打印
    }

    if (names.includes("abc")) {
      console.log("包含abc")
    }

指数运算

const result1 = Math.pow(3,3);
    // ES7
    const result2 = 3 ** 3;

ES8

Object.values

之前可以通过Object.keys获取对象(不包括原型)中所有的key,在ES8中提供了Object.values()来获取所有的values。

const obj = {
      name: "zhangsan",
      age: 18,
      address: [1, 2, 3, 4, 5],
      friends: {
        a: 1,
        b: 2,
        c: 3
      }
}

console.log(Object.keys(obj));
console.log(Object.values(obj));

entries

通过Object.entries 可以获取到一个数组,数组中会存放可枚举属性的键值对数组

const obj = {
      name: "zhangsan",
      age: 18,
      address: [1, 2, 3, 4, 5],
      friends: {
        a: 1,
        b: 2,
        c: 3
      }
    }
    
    Object.defineProperty(obj, "name", {
      configurable: true,
      enumerable: false,
      writable: true,
      value: "999"
    })
    // [["age","18"],["address",[1,2,3,4,5]],["friends",{a:1,b:2,c:3}]]
    console.log(Object.entries(obj))

String Padding

某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了 padStart 和 padEnd方法,分别是对字符串的首尾进行填充的。
我们简单举一个应用场景:比如需要对身份证、银行卡的前面位数进行隐藏:

// 银行卡号
    var cardNumber = "23237532458972345234";
    // 保留后4位
    var lastFourNumber = cardNumber.slice(-4);
    // 显示最后4位,前面显示*
    var finalCard = lastFourNumber.padStart(17, "*")

    console.log(finalCard)

日期填充案例

var date = new Date();
    var year = date.getFullYear();
    // 不满2位补充0
    var month = (date.getMonth() + 1).toString().padStart(2, '0');
     // 不满2位补充0
    var day = (date.getDate()).toString().padStart(2, '0')
    console.log(year, month, day)

Object Descriptors

Object.getOwnPropertyDescriptors()用来获取一个对象的所有自身属性的描述符。

const obj = {
        name: "zhangsan",
        age: 18
    }
    
    Object.defineDescriptors(obj)

ES10

flat

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

const nums = [10, 20, [2, 9], [[30, 40], [10, 45]], 78, [55, 88]]

    const flatArr = (arr) => {
      return arr.reduce((prev, cur) => {
        return prev.concat(Array.isArray(cur) ? flatArr(cur) : cur)
      }, [])
    }
    // ES10之前
    console.log(flatArr(nums))
    // flat
    console.log(nums.flat(Infinity))

flatMap

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

flatMap是先进行map操作,再做flat的操作;
flatMap中的flat相当于深度为1;

const message = ["Hello World", "你好啊 李银河", "my name is why"];

    const msg1 = message.map(item => {
      return item.split(' ')
    })

    // [["Hello", "world"], ["你好啊", "李银河"], ["my", "name", "is", "why"]]
    const msg2 = message.flatMap(item => {
      //先map 然后flat 深度为1
      return item.split(' ')
    })

    console.log(msg1, msg2)

es6 对象某个key是否存在_前端_05

fromEntries

const obj = {
      a: 1,
      b: 2,
      c: 3
    }
    // 对象转成entries ---> 遍历
    const entries = Object.entries(obj)
    entries.forEach(entry => {
      console.log(entry[0], entry[1])
    })
    // 对象转成entries ---> 遍历
    for (const [key, value] of entries) {
      console.log(key, value)
    }


    // entries转换成对象 ES10新增
    // Object.fromEntries的应用场景
    const str = "name=why&age=18&height=1.88"
    const params = new URLSearchParams(str)
    // 将entries转换为 对象
    const paramObj = Object.fromEntries(params)
    console.log(paramObj)

trimStart tirmEnd

const message = "   Hello   World   ";
    console.log(message.trim());
    console.log(message.trimStart());
    console.log(message.trimEnd());

ES11

BigInt

在ES11之前大于MAX_SAFE_INTEGER的数值,表示的可能是不正确的。

const maxInt = Number.MAX_SAFE_INTEGER
    console.log(maxInt + 1) // 可能表示出来不正确

    // ES11之后
    const bigInt = 99999999999999999999n;
    console.log(bigInt + 10n); //可以正确表示
    console.log(typeof bigInt); //bigInt

    const num = 10;
    // int 和 bigInt运算需要转换
    console.log(bigInt + BigInt(num));
    // bigInt 和 int运算需要转换
    console.log(num / Number(10n));

??空值合并运算符

空值合并操作符,可以在使用可选链时设置一个默认值

const obj = {
      friend: {
        girlFriend: {
          // name: "luck"
        }
      }
    }
    // 没有可选链操作符之前
    if (obj.friend && obj.friend.girlFriend && obj.friend.girlFriend.name) {
      console.log(obj.friend.girlFriend.name)
    } else {
      console.log(undefined)
    }

    // 可选链操作符 空值合并运算符
    console.log(obj?.friend?.girlFriend?.name ?? "默认值")
    
    // 或者
    const foo = undefined; //当值是0 或者 是"" bar1有bug 
    const bar1 = foo || "default value"; 
    const bar2 = foo ?? "defualt value";

?.可选链操作符

允许读取位于连接对象链深处的属性值,而不必明确验证链中的每个引用是否有效。

const obj = {
        friend: {
            girlFriend: {
                name: "luck"
            }
        }
    }
    // 没有可选链操作符之前
    if( obj.friend && obj.friend.girlFriend && obj.friend.girlFriend.name ) {
        console.log(obj.friend.girlFriend.name)
    } else {
        console.log(undefined);
    }
    
    // 可选链操作符
    console.log(obj?.friend?.girlFriend?.name);

Global This

JavaScript环境的全局对象,不同的环境获取的方式是不一样的。

比如在浏览器中可以通过this、window来获取
比如在Node中我们需要通过global来获取
ES11中对获取全局对象进行了统一的规范:globalThis

console.log(globalThis);
    console.log(this); //浏览器上
    console.log(global); //node中

for…in标准化

在ES11之前,虽然很多浏览器支持for…in来遍历对象类型,但是并没有被ECMA标准化。
在ES11中,对其进行了标准化,for…in是用于遍历对象的key的:

// for...in 标准化: ECMA
    const obj = {
      name: "why",
      age: 18
    }

    for (const item in obj) {
      console.log(item)
    }

ES12

FinalizationRegistry

FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调 用一个清理回调。(清理回调有时被称为 finalizer );
可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;

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

    function newArr() {
      return new Array(1024 * 1024).fill(1)
    }

    //分配20个引用 让其引用保存在数组 不被释放
    var arrList = [];
    for (var i = 0; i < 20; i++) {
      setTimeout(function () {
        arrList.push(newArr())
      }, i * 100)
    }

    setTimeout(() => {
      for (var i = 0; i < 10; i++) {
        //以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值
        finalRegistry.register(arrList[i], i)
      }
    }, 4000)


    // 释放10个引用  然后等待GC回收10个引用
    // 这里要注意, 释放10个 
    setTimeout(function () {
      for (var i = 0; i < 10; i++) {
        arrList.shift()
      }
    }, 5000)

es6 对象某个key是否存在_javascript_06

WeakRefs

如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用。如果我们希望是一个弱引用的话,可以使用WeakRef。

// ES12: WeakRef类
    // WeakRef.prototype.deref: 
    // > 如果原对象没有销毁, 那么可以获取到原对象
    // > 如果原对象已经销毁, 那么获取到的是undefined
    
    let obj = { name: "why" };
    let info = new WeakRef(obj);

    // 置空 info是弱引用 会被回收的
    obj = null

    // 被回收之前 能够打印
    console.log(info.deref()?.name)

    // 等待GC回收obj 我们再来打印弱引用  info.deref()不存在的话会返回 undefined
    // 这里假设是10秒回收,但是具体多久回收 我也不知道
    setTimeout(() => {
      // 用可选链 + 空值合并
      console.log(info.deref()?.name ?? "啥也没有")
      // 如果不用可选链
      console.log(info.deref() && info.deref().name)
    }, 10000)

logical assignment operators

// 1.逻辑或运算符
    let message = ""

    message = message || "Hello World";
    message ||= "Hello World";


    // 2.逻辑与操作符
    let obj = {
      name: "why",
      foo() {
        console.log('123')
      }
    }
    obj0 = obj && obj.foo;
    obj0()
    
    obj &&= obj.name;

    // 逻辑空运算符
    let foo = null;
    foo ??= "默认值"
    console.log(foo)