ES6 到 ES13常见用法总结

ES6-ES13

ES6(部分内容)

import() 动态导入方法[23.7.28]

动态导入 是JavaScript ES2019中新增的语法特性,它可以通过将代码 按需导入,从而实现更加 高效的加载方式。动态导入允许用户在运行时动态地加载模块,这是ES6中静态导入所无法实现的

基本语法:

  •   /* import(模块路径) 动态导入返回一个 promise
          + 当模块导入成功后,我们就可以通过 .then 方法继续执行后续的操作
      */
      import(模块路径).then(模块 => {
          // 使用导入的模块
      }).catch(err => {
          // 捕获错误
      })
    

对象字面量的增强写法

1.属性的简写(property shorthand)

// 按照以前的写法,时属性名与属性值的名称都一样时也是要两个都写上
    var name = "kong";
    var age =18;
    var obj = {
        name:neme,
        age:age
    }

// ES6后,当属性名与属性值的标识符时一样的时候,可以只书写属性名
    var obj = { // 该obj与上面的obj是一样的
        name,
        age
    }

2.方法的简写(method shorthand)

    var obj = {
        foo:function (){
            // 在ES6之前书写对象书写的方式
        },
        fun() {
            // 在ES6后,可以直接书写标识符 + 方法体即可 (更加的简便)
        }
    }
    // 需要注意的是,该简写方式是 function 写法的语法糖 -- 不是箭头函数的语法糖(将头函数不绑定this)

3.计算属性名(computed property name)

// ES6 运算计算属性名的定义 - 语法,使用中括号[]
    var obj = {
        ["name" + 1]:"kong",
        ["name" + 2]:"wang"
    }
    obj["name" + 3] = "deng";
    // obj对象就有三个属性

let 与 const

1.let-const和window的关系

    var a = 0; // 通过var声明的变量,会存在在window里面
    consoli.log(a);
    consoli.log(window.a);
    // 上面两个输出的是完全一样的
    // 当window与var变量中任意一个改变了变量的值,另一边都会跟着改变
    window.a = 5;
    conshilo.log(a); // 5 

/* 在以前,每一个执行上下文会关联到一个变量环境(variable object,VO),在代码的变量和函数声明会作为属性添加到VO中 
*  对于函数来说,参数也会添加到VO中 */

/* 现在,每一个执行上下会关联到一个变量环境(variableEnvironment`s,VE)中,在执行代码变量和函数声明会作为环境记录(Environment Record)添加到变量环境中
*  对于函数来说,参数也会作为环境记录添加到变量环境变量中 */

// let与const不会添加到window对象之中

2.新增块级作用域

在ES6之前只有两个作用域的存在,一个是全局作用域(window),一个是函数作用域(function)

{
    // 块代码 - 在ES6之前没有任何作用  --  ES6块代码(有块级作用域)

    /* 对let/const/function/class声明的类型有效
    *  function比较特殊 - (不同点浏览器有不同的实现效果)- 大部分浏览器为了兼容以前的代码,让function是没有块级作用域
    */
}

// 在块代码中有着块级作用域 - 多以只能在内部使用
{
    var a = 0; // var除外 - 因为块级作用域针对于var是无效的 
    let b = 0;
}
console.log(a);
console.log(b);

其它块级作用域…

// if switch for ...
    if(true){
        var a = 0;
        let b = 1; // 外部不可访问
    }

    switch (color){
        case "red":
            var a = 0;
            let b = 1; // 外部不可访问 
    }

// for语句也是块级作用域
    for(var i=0;i<10;i++>){

    }
    consoli.log(i); // 可以访问

    for(let i=0;i<10;i++>){

    }
    consoli.log(i); // 不可访问

应用

<!-- 没有块级作用域的情况 -->
    <!-- html代码 -->
        <button>1</button>
        <button>2</button>
        <button>3</button>
        <button>4</button>
    <!-- js代码 -->
        <script>
            /*
            *   因为var没有块级作用域所以外部可以访问到var - 并且var后存放在window之中(并两者是一一对应的)
            *   所以当循环完了之后 var i的值等于4,
            *   因为点击的时候才输出 i 
            *   所以无论点击的是哪一个按钮,打印 i 的值都为4
            *   4 + 1 == 5 --> 5
            */
            const button = document.getElementsByTagName('button');
            for(var i=0; i<button.length; i++){
                button[i].addEventListener('click',function(){
                    console.log("点击的是第 " + (i+1) + " 按钮");
                })
            }
        </script>

<!-- 有块级作用域的情况 -->
    <!-- html代码 -->
        <button>1</button>
        <button>2</button>
        <button>3</button>
        <button>4</button>
    <!-- js代码 -->
        <script>
            /*
            *   只需将var声明改为let声明即可
            *   因为let声明既有块级作用域,所以它每一次循环都相当于是一个块级作用域
            *   所以每次 i 都有着单独对应的值
            *   执行如下
            */ 
            /*
            *   第一次执行
            *   {let i = 0}
            *   第二次执行
            *   {let i = 1}
            *   第三次执行
            *   {let i = 2}
            *   第四次执行
            *   {let i = 3}
            *   每一次执行都会有单独的一个块级作用域
            */
            const button = document.getElementsByTagName('button');
            for(let i=0; i<button.length; i++){
                button[i].addEventListener('click',function(){
                    console.log("点击的是第 " + (i+1) + " 按钮");
                })
            }
        </script>

<!-- 在以前没有块级作用域的使用 - 通常是使用一个立即执行函数来存储i的值(可以理解为是利用闭包) -->

3.let-const其它知识

    const arr = ["a","b","c","d"];
    for(let i=0;i<arr.length;i++){
        consoli.log(arr[i]);
    }

// 执行过程
    // 它的每一次执行,都会有一个独立的块级作用域来储存 let
    {
        let = 0;
        consoli.log(arr[i]);
    }
    {
        let = 1;
        consoli.log(arr[i]);
    }
    {
        let = 2;
        consoli.log(arr[i]);
    }
    {
        let = 3;
        consoli.log(arr[i]);
    }
    // 当使用的是 var 时 - 因为var没有块级作用域所以 var 是没有独立存储的,后面的值会覆盖掉前面的值
    {
        // 它是在自身覆盖的
        var = 0; // var = 1 // var = 2 // var = 3
        consoli.log(arr[i]);
    }
// for of - 遍历数组(对象)
    const arr = ["a","b","c","d"];
    for(let item of arr){ // 为了有块级作用域,通常使用let - const
        consoli.log(item);
    }
    /* for of可以使用 const  -- for不能使用 const
    *  因为for中有个 i++ 会改变变量的值 - 所以不能使用const
    *  而for of中没有 i++ 不会改变变量的值 - 所以亏可以使用const
    */

4.暂时性死区(temporal dead zone)

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

// 示例
    // 可以访问
    var a = 0;
    {
        console.log(a);
    }
    // 不可以访问 - 形成暂时性死区
    var a = 0;
    {
        // 因为在let或const变量声明前是不可以访问的
        console.log(a); // 所以这里就会形成一个暂时性死区,就会访问不到外部的 var a
        let a = 1; 
    }

5.var、let、const的选择

对于var的使用:

  • 我们需要明白一个事实,var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题
  • 其实是JavaScript在设计之初的一种语言缺陷
  • 当然目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对JavaScript语言本身以及底层的理解
  • 但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了

对于let、const :

  • 对于let和const来说,是目前开发中推荐使用的
  • 我们会优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改
  • 只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let
  • 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范

模板字符串

1.模板字符串

// 语法 ``
    // 插值 ${}
    // 使用 - 通常配合着插值来使用
    const name = "kong";
    const age = 19;
    consoli.log(`My name is ${name},age is ${age}`); // My name is kong,age is 19
    // 当热插值里面也可以,运算或函数的调用等..
    var test = `H${age + 2}el${MyFun()}lo`;

2.标签模板字符串

// 标签模板字符串 - 函数调用时不是使用中括号(),而是使用模板字符串的语法来调用的
    function myFun(){
        // 语句
    }
    myFun``; // 这就称 标签模板字符串 

// 参数
    /*
    *   同过标签模板字符串传参
    *   第一个参数是一个数组
    *   第二个参数是第一个 插值${} 中的内容
    *   第三个参数是第二个 插值${} 中的内容
    *   所有不属于插值的,都是属于第一个参数的,并且插值相当于是将参数截断在存入数组之中
    */
    function myFun (a,b,c){
        console.log(a); // [hello world] 
        console.log(b); // undefined
        console.log(c); // undefined
    }
    myFun`hello world`; // 该参数时第一个参数

    // 插值
    function myFun (a,b,c){
        console.log(a); // [he,llo,wo,rld]  -- 长度为4的数组
        console.log(b); // 1 - 第一个插值
        console.log(c); // 2 - 第二个插值
    }
    myFun`he${1}llo wo${2}rld`; // 该参数时第一个参数

/*  很少使用 - 可以了解
*   因为在React中使用js书写css样式,就是使用的是这个原理(styled-components库)
*/

展开语法与特性

  • 可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开

  • 还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开

  • 构造字面量对象时,进行克隆或者属性拷贝(ECMAScript 2018 规范新增特性)

// 语法 ...
    const name = "kxh";
    const names = ["kong","deng","wang"];
    // 1.调用函数时
        function foo (x,y,z){
            consoli.log(x,y,z);
        }
        // foo.apply(null,names); // 以前将数组中的值作为参数传入函数中的方式 - 不易于代码阅读
        // 在ES6之后,可以使用展开运算符来将数组中的值,作为参数传入函数中
        foo(...names); // kong deng wang
        // 不仅仅可以展开数组,也可以展开字符串
        foo(...name); // k x h
    
    // 2.构造数组时
        const newNames = [...names]; // ["kong","deng","wang"]
        const newName = [...name];  // [k,x,h]

    // 3.ES2018(ES9) - 构造对象时
        const obj = {name:"kong",age:18}
        const newObj = {
            ...obj,
            addreese:"HUAIJI"
        }
        consoli.log(newObj); // {name:"kong",age:18,addreese:"HUAIJI"}
        // 也可以将数组展开到对象中,会以键与键值展开
    
    // 展开运算符,相当于是一个浅拷贝(即当拷贝的对象中,有的属性它的属性值又是一个对象或数组时,这时拷贝的是引用值) - 如
        const obj = {
            name:"kong",
            age:19,
            arr:[0,1,2],
            objSon:{
                name:"xiaokong"
            }
        }
        const newObj = {...obj}
        // 当你改了某一个对象的属性中值中的对象或数组的值后,另一个也会跟着改变(拷贝的是引用值)
        obj.arr[0] = 10;
        consoli.log(newObj.arr[0]); // 10 - 因为是引用值,所以一个改变了,另一个也跟着改变

    /*  浅拷贝概述
    *   当为常量的时候直接拷贝
    *   为对象{}或数组[]时拷贝的时引用值
    *   注意 null 属于对象(做深克隆的时候可以需要注意,浅克隆可忽略)
    */ 

【理解】八进制-二进制-连接符

在ES6中规范了二进制与八进制的写法:

// 十进制
    const num = 100;

// 二进制 - 0b开头表示 binary
    const num = 0b10; // 10 --> 2 

// 八进制 - 0o开头表示 octonary
    const num = 0o100; // 100 --> 64

// 十六进制 - 0x开头表示 hex
    const num= 0x100; // 100 --> 256

连接符

// 比较大的数值连接符 _ (ES2021 ES12)
    /* 通常数值比较大的,会很不好查看(会将数值每3个用一个逗号隔开,但在js中会报错)
    *  所以就有了连接符 _
    *  使用连接符不会有任何的效果,只是可以在阅读代码时可以更好的阅读体验
    */
    const num = 100_000_000_000; 
    consoli.log(num); // 100000000000  -  s实际打印没有变化

新增Symbol类型

Symbol是ES6中新增的一个基本数据类型,翻译为符号

为什么需要Symbol

  • 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突口
  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下很容易造成冲突,从而覆盖掉它内部的某个属性
  • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉

Symbol就是为了解决上面的问题,用来生成一个独一无二的值

  • Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
  • 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值

Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的

我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性

// 在ES6之前
    var obj ={
        name:"kong",
        "age":19 // 因为在js中对象的键(key),不管有无引号,在底层都会是字符串
    }
    // 可以打印obj中的key来看效果
    consoli.log(Object.keys(obj)); // ['name', 'age']
    // 所以在我们赋值的时候就很容易将某些值覆盖掉
    obj["name"] = "kxh"; // 此时就将obj中的name覆盖掉了
    // 所以Symbol就可以为我们创建一个独一无二的key值

// Symbol的基本使用 Symbol()
    const s1 = Symbol(); // 每一个创建的Symbol值都是独一无二的
    const s2 = Symbol();
    consoli.log(s1 === s2); // false

    // 在ES2019(ES10)中我们可以在Symbol()中传入一个描述 description
        const s3 = Symbol("MiaoShu");
        consolo.log(s3.description); // 'MiaoShu'
    
    /*  若想创建相同的Symbol函数(非独一无二)
    *   可以使用Symbol中的 for(key) 语法
    *   当有两个for(key)中的key值是相等的话,那么它两就是相同的Symbol函数
    *   如
    */
        const sa = Symbol.for('public');
        const sb = Symbol.for('public');
        consoli.log(sa === sb); // true

// 使用Symbol创建对象的key值 - 使用之前讲的【属性名的计算】的方法
    // 先创建4个Symbol值 - 用于演示
        const s1 = Symbol();
        const s2 = Symbol();
        const s3 = Symbol();
        const s4 = Symbol();
    // 在定义对象字面量时使用
        const obj = {
            [s1]:"a",
            [s2]:"b"
        }
        consoli.log(obj); // {Symbol(): 'a', Symbol(): 'b'} -- 虽然里面的key值都是Symbol(),但它们每一个都是独一无二的
    // 新增属性
        obj[s3] = "c";
    // 通过Object.defineProperty()方法 -- 使用方法下方
        Object.defineProperty(obj,s4,{
            enumerable:true,
            configurable:true,
            writable:true,
            value:"d"
        });
    /*  需要注意的是,使用Symbol函数创建的键值,是无法遍历与使用 Object.keys()方法的   
    *   不过可以通过
    *   Object.getOwnPropertySymbols(obj) -- 获取所有的 Symbol 类型的键值
    *   Object.getOwnPropertyNames(obj) -- 获取所有不是 Symbol 类型的键值
    *   Object.getOwnPropertyDescriptors(obj) -- 获取整个对象的所有值
    * 
    *   所以我们就可以通过 Object.getOwnPropertyDescriptors(obj) 的方式获取所有值
    *   或者遍历所有值
    */ 


// Object.defineProperty()方法的使用  【后续学习-目前未学】
    
    /*  Object.defineProperty(obj,prop,descritor)
    *   obj -- 要定义属性的对象
    *   prop -- 要定义或修改的属性的名称或 Symbol
    *   descriptor -- 要定义或修改的属性描述符
    */

    /*  🔺在对象中,无论写不写 Object.defineProperty() 方法,其内部都会有默认的 description 值...
    *   不过默认情况下是不展示出来的,只有通过  Object.getOwnPropertyDescriptors(obj) 的方法调用才会显示
    *   
    */

    /*  属性描述符
    *   拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false
    *   属性值和函数的键 value、get 和 set 字段的默认值为 undefined
    */

    // 详细文档学习 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty 

新增数据结构Set

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

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

1.新增数据结构Set

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

创建Set需要通过Set构造函数(暂时没有字面量创建方法)

// 创建Set
    const set = new Set(); 

// 方法
    // Set中添加元素的方法 - add
        set.add(10);
        set.add(20);
        set.add(20); // 与上一个是相同的,只会保留一个(不能有重复的数据)
        set.add(30);
        console.log(set);
    // Set中删除元素的方法 - delete
        set.delete(30); // 书写的是所要删除的元素(非索引)
        console.log(set);
    // Set中清空元素方法 - clear
        set.clear();
        console.log(set);
    // 判断Set中 是否存在某一个元素 - has
        set.has(10); // false
    // forEach遍历集合
        ...
// 属性
    // Set中获取长度属性 - size
    console.log(set.size)

// 在创建时,在Set()参数中,可以直接添加数据 -- 使用中括号
    const s1 = new Set([1,2]); // 添加数据
    // 也可以添加已有的数组(数据)
        const arr = [1,2,3];
        const s2 = new Set(arr);

数组去重

// 定义去重的数据 - public
    const arr = [10,20,30,40,50,20,20,30,40,];

// 在之前是使用for 或 forEach...
    var newArr = [];
    arr.forEach( item => { // forEach里面是一个函数,参数未arr的value(当两个参数时,第二个为key/index)
        if(newArr.indexOf(item) === -1){
            newArr.push(item);
        }
    });

// 现在可以利用Set中的 (不能存在重复的元素这一特性) 来实现数组去重
    const setArr = new Set(arr); // 将已有 arr 数据添加进Set中
    const newArr = [...setArr];  // 使用扩展运算符,将setArr中的数据添加到对应的数组 - 即完成了去重
    const newArrFun = Array.from(set); // 或者使用 Array中的 from 方法存入数据(两者都可以)


//  弃用-过于复杂
    // 现在可以利用Set中的 (不能存在重复的元素这一特性) 来实现数组去重
        const set = new Set();  // 创建Set集合
        const newArrSet = [];   // 去重后的数据
        for(item of arr){       // 遍历arr中每一个数据,再将每一个数据都存放入Set集合中
            set.add(item);      // 存放数据,重复的Set会自动忽略
        }
        newArrSet.push(...set); // 使用扩展运算符,将set集合中的数据,添加进newArrSet中,完成去重

2.新增数据结构WeakSet

与Set类似,也数数据不能重发的数据结构

  • 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型
  • 区别二:WeakSet元素的引用是弱引用,如果没有其它引用对某个对象引用,那么GC可以对该对象进行回收
// 创建方法与Set一致(只能使用构造方法)
    const weakSet = new WeakSet(); 
    /* WeakSet也可以在参数中直接书写值,与set不同的是,因为WeakSet不能书写基本数据类型
    *  所以,[[],[],[]] -- 每一个值都必须是对象类型的(不是方对象数组,放的是对象对象会忽视)
    */  const weakSetTest = new WeakSet([[1,2]["a","b"]]);

// WeakSet 方法
    const arr1 = [10,20,30];
    const arr2 = [40,50,60];
    const weakSet = new WeakSet([arr1,arr2]);  // 建立的是弱引用
    // has 判断是否包含某一对象(数组)
        weakSet.has(arr1); // true
    
    // 添加数据 - add
        weakSet.add(arr1);

    // 删除数据 - delete
        weakSet.delete(arr1);

// WeakSet 数据结构不能使用 forEach等进行遍历

3.WeakSet使用场景

不常用

// 像下例中,调用方法的时候可以将里面的this指向,通过call来改变掉
    class Person {
        constructor(){

        }
        running (){
            console.log("【going running】",this);
        }
    }
    const p = new Person(); // 创建 Person 类的实例化对象
    p.running(); // 【going running】 Person{}
    // 让this指向另一个对象
    p.running.call({name:"kong"}); // 【going running】 {name: 'kong'}


/*  如果有一个需求,就是不能向上面的一样可以改变this指向
*   相当于是,调用方法时,方法中的this指向,只能是本主体构造函数才能正常调用
*   否则,抛出错误
*   这里就可以用到 WeakSet 数据结构
*   具体如下
*/
    const weakSet = new WeakSet();
    class Person {
        constructor(){
            /*  因为每次调用方法时,构造函数都会创建一个 this对象
            *   所以可以将该构造函数创建的 this对象 存储到 WeakSet中
            *   用于后续在调用方法时,来判断 thhis 是否是该构造函数创建的 this
            */ 
            weakSet.add(this);
        }
        running (){
            /*  当调用该方法时,会生成一个 this
            *   下面的条件语句,就是用来判断该方法在调用的时候,this有没有发生改变
            */ 
            /*  当该方法生成的 this 不在weakSet,则相当于该 this 不是构造函数创建的 this
            *   抛出错误
            */ 
            if(!weakSet.has(this)){ 
                throw new Error("不能通过非构造方法创建出来的对象来调用running方法");
            }
            console.log("【going running】",this);
        }
    }
    const p = new Person();
    /*  报错 - 因为调用的时候,使用了call改变了this对象 
    *   所以调用方法的this不属于构造函数创建出来的对象
    */
    p.running.call({name:"kong"});


/*  上面为什么不用Set,而用WeakSet
    1.因为使用Set时,Set时属于强引用,当我们将 p=null 给清空时
      因为Set时强引用,所以 GC 是没有办法清理掉的
      除非在你清理掉 p 后,在调用 weakSet.clear() - 将Set清理掉
      这样不仅多了一个步骤,在比较大的项目或是其它比较复杂的情况的时候 - 很难保证会记得清除
      从而就会造成 GC 无法清理掉 p -- 存在内存泄漏

    2.但使用 WeakSet 就不一样了,因为 WeakSet 是弱引用
      所以只要当 p=null 也就是说那一个对象就只剩下WeakSet引用着
      又因为 WeakSet 是弱引用,所以 GC 会当做没有对象引用该存储空间,会直接给清除
*/ 

新增数据结构Map

1.新增数据结构Map

用于存储映射关系(可以理解为键值对),与对象差不多

  • 事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)

  • 某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key

Map

  • 使用Map数据结构,就可以传入其它类型作为key值
// 创建map对象
    const map = new Map();

// 基本方法
    // 创建时添加数据
        const map = new Map([[key1,value1]],[key2,value2]]);
    // 添加键值对 - set
        map.set(key,value);
    // 获取 value - get
        map.get(key);
    // 删除值方法 - delete
        map.delete(key);
    // 清空值 - clear
        map.clear();

// 获取长度属性 - size
    map.size;


// 使用其它类型来作为key值 -- 使用对象如下
    const map = new Map();
    const obj = {
        name:"kong",
        age:19
    }
    map.set(obj,"kxh"); // 可以使用对象来当键值(映射关系)
    console.log(map); // Map(1) {{…} => 'kxh'} -- 打印出来有个箭头,是为了与对象做一个区分

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

    // 使用for of遍历出来的item是一个数组,里面又分别放着key与value
        for(const item of map){
            // console.log(item);
            console.log(item[0],item[1]);
        }
        // 也可以在遍历的同时做一个解构
        for(const [key,value] of map){
            console.log(key,value);
        }

2.新增数据结构WeakMap

和Map的区别

  • 区别一:WeakMap的key只能使用对象,不接受其它类型作为key
  • 区别二:WeakMap的key对对象的引用时弱引用,如果没有其它引用引用这个对象,那么GC可以回收该对象
  • 区别三:WeakMap不能进行遍历
// 创建WeakMap
    const weakMap = new WeakMap();

// 常见方法
    // set - 添加值set(key,value)
    // get - 获取值get(key)
    // had - 判断对象是否存在某个属性
    // delete - 删除某个数据delete(key)

// 基本使用
    const obj = {
        name:"kong"
    }
    const weakMap = new WeakMap();
    weakMap.set(obj,"kong");
    console.log(weakMap); // WeakMap {{…} => 'kong'}

3.WeakMap的应用🔺

// vue3响应式原理(得学完先【proxy reflect】,在写vue响应式源代码)
    ........
     ......
      ....
       ..

ES7

includes方法和指数运算符【array】

includes

    const names = ["kong","xinag","huang"];
// ES7之前,判断数组是否包含某数据的方式
    /* 因为该方式,返回的是索引值(相当于是用于查看某一个数据的位置的)
    *  所以借助它来判断数组是否包含某一数据,只是看它又这一特性
    *  ES7之后就出现了一个比较好语义等的 includes【返回的是true或false】
    */
    if(names.indexOf("kong") !== -1){  // 判断数组是否包含某数据
        console.log("包含");    
    }


// ES7 - includes[判断是否包含某一数据]
    const names = ["kong","xinag","huang"];
    /* includs() - 可以有两给参数(第一个参数为所向想要判断是否包含的值 - 第二个参数是书写整数类型的参数,变数从第一个楷书查找)
    *  name.includes("kong",2) -- 表示从数组name的第二项开始查找
    */
    console.log(names.includes("kong")); // true
    consoli.log(names.includs("kong",2)); // 当又两个参数的时候,第二个参数表示从第几个开始判断

    // 使用includes判断数组是否包含某一个值
    if(names.includes("kong")){
        console.log("包含"); 
    }

指数运算符 - **

// ES7之前
    const num1 = Math.pow(3,3); // 表示3的3次方

// ES7新增
    const mun2 = 3 ** 3; // 表示3的3次方

ES8

values-entries-pad等

1.Object.values - 获取说有value值(返回的是一个数组)

// 基本使用
    const obj = {
        neme:"kxh",
        age:19
    }
    console.log(Object.keys(obj)); // Object.keys之前有过 - 表示获取所有key值(键值)(2) ['neme', 'age']
    console.log(Object.values(obj)); // 表示后去所有的内容(value)(2) ['kxh', 19]

// 也可以传入其它类型的参数(很少会使用)
    // 数组
        console.log(Object.values(["kong","wang","deng"])); // ['kong', 'wang', 'deng']
    // 字符串
        console.log(Object.values("kong")); // ['k', 'o', 'n', 'g']

2.Object…entries -获取一个数组,数据中会存放可枚举的键值对数组

// 基本使用 - 放回的是一个数组(里面存放发一样是数组,分别存放着键值对)
    const obj = {
        neme:"kxh",
        age:19
    }
    console.log(Object.entries(obj));

// 也可以传入其它类型的参数(很少会使用)
    // 数组
        console.log(Object.entries(["kong","wang","deng"])); // ['kong', 'wang', 'deng']
    // 字符串
        console.log(Object.entries("kong")); // ['k', 'o', 'n', 'g']

在这里插入图片描述

3.String.padding - 字符串填充

// 填充字符出,其中有两个属性,分别是向前填充(padStrat)与项后填充(padEnd)
    const str = "Hello world";
    // padStrat - 在前面进行填充
        const newStrStratPad = str.padStart(15); // 表示将字符串扩大至15个字符的大小(宽度/空格),在最前面进行填充(使用空格)
    // padEnd - 在后面进行填充
        const newStrEndPad = str.padEnd(15); // 表示将字符串扩大至15个字符的大小(宽度/空格),在最后面进行填充(使用空格)
    // 打印
        console.log(newStrStratPad);
        console.log(newStrEndPad);

在这里插入图片描述

// 默认以空格填充,若想使用其它符号来进行填充 - 可以书写第二个参数(表示以什么填充)
// 也可以多次调用该方法,多次斤西瓜填充
    const str = "Hello world";
    const newStrStratPad = str.padStart(15,"-"); // 向前填充,以(-)来填充
    const newStrEndPad = str.padEnd(15,"."); // 向前填充,以(.)来填充
    const newStrContinuous = str.padEnd(15,".").padEnd(20,"--"); // 多次填充,分别以(-)与(.)来进行填充
    // 打印
    console.log(newStrStratPad);
    console.log(newStrEndPad);
    console.log(newStrContinuous);

在这里插入图片描述

小案例

// 将身份证前面的隐藏(hide)掉,保留后四位
    const myIdentity = "441224202201025418"; // 某某身份证
    const myIdentityLastFourBit = myIdentity.slice(-4); // 表示从第4位开始截取(雄厚向前数)
    const myIdentityHide = myIdentityLastFourBit.padStart(myIdentity.length,"*"); // 将身份证前面使用星号填充,保留后四位
    console.log(myIdentityHide); // 打印

在这里插入图片描述

4.trailing-commas

// 只是修改了一点与法问题
    function fun (n,m,){}
    fun(1,2,);
    // 在ES8之前,是不允许向上面一样在函数与调用函数中的最后一个参数后加逗号(,)会报错
    // ES8中就增加了该与法,相当于在ES8之后允许项上面一样的写法(不会报错)

5.Object.getOwnPropertyDescriptors - 获取对象的所有值 - 前面已经写过(详细往前看)

ES9

Async iterators(后续 - 迭代器讲解先)

Object spread operators(后续在学)

Promise finally(后续学 - Promise)

ES10

flat-flatMap(降维)

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

// 基本使用
    const nums = [10,20,30,[100,200],[[1000,2000],[3000,4000]]]; // 三维数组
    const newNums1 = nums.flat(); // 降维 - 默认为1(即降一个维度)
    const newNums2 = nums.flat(2); // 降维 - 深度为2
    console.log(newNums1); // [10,20,30,[100,200],[1000,2000],[3000,4000]] - 二维
    console.log(newNums2); // [10,20,30,100,200,1000,2000,3000,4000] - 一维

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

  • 注意一:flatMap是先进行map操作,在做flat操作
  • 注意二:flatMap中的flat相当于深度为1
// 基本使用 - 可传入一个函数(里面的参数可以接收flatMap每次遍历数组的值)
    const nums = [10,20,30];
    const newNums = nums.flatMap(item => {
        return item * 2;
    });
    console.log(newNums); // [20, 40, 60]

    // 基本应用场景
        const messages = ["Hello world","hello dcl","my name is kong"];
        const words = messages.flatMap( item => {
            return item.split(" "); // 在每个空格处进行拆分
            /*  拆分后会性成一个二维数组
            *   因为flatMap也会将数组进行降维,所以直接使用flatMap可以不用再重新遍历成一位数组
            */
        });
        console.log(words); // [Hello,world,hello,dcl,my,name,is,kong]
    
    /*  为什么不直接用flat
    *   因为flat会直接进行降维操作 - 遍历遍历出来后是个二维数组(还需要再次的给其手动实现降维效果)
    *   又因为flatMap是先进行map操作再进行flat的降维操作
    *   福哦一这里不是用flat而是使用flatMap
    */

fromEntries

// 基本使用 - 参数传入的是可迭代的数据
    const obj = {
        name:"kong",
        age:19
    }
    // 前面讲过,该方法是将对象分别以键值对生成一个子数组,包含在一个大数组之中
    const entries = Object.entries(obj);
    // 当项将该数组再转换成数组
        // 1.以前的方法
        const newObj = {} // 创建一个新的对象,用于存储数组中的值
        for(const item of entries){ // 遍历改采那个数组
            newObj[item[0]] = item[1]; // 最后再将里面两个值,分别对应的添加进对象之中作为key与value
        }
        // 2.ES10新增方法Object.fromEntries
        const fromObj = Object.fromEntries(entries); // 与墙面所讲到的entries是恰好相反的

trimStrat与trimEnd

// 基本使用
    const str = "   hello world   ";
    // 1.trim -- 去除字符串前后多余的空格
    const newStr = str.trim();              // "hello world"

    // 2.trimStrat -- 去除字符串前面多余的空格
    const newStrStrat = str.trim();         // "hello world   "

    // 3.trimEned -- 去除字符串后面多余的空格
    const newStrEnd = str.trim();           // "   hello world"

catch binding(学习try cach时再学)【抛出异常】

ES11

新增大整数数据类型BigInt

再早期JavaScript中,是不能正确的表示过大的数字 - ES11之前最大的int数值为

// ES11之前 MAX_SAFE_INTEGER
    // Number中的一个方法,获取整数能够正常表示的最大值 - 当然也可以更大,但不安全
    const maxInt = Number.MAX_SAFE_INTEGER; 
    console.log(maxInt); // 9007199254740991

// 基本使用
    // Number中的一个方法,获取整数能够正常表示的最大值 - 当然也可以更大,但不安全
        const maxInt = Number.MAX_SAFE_INTEGER; 
        console.log(maxInt); // 9007199254740991

    // ES11之后:BigInt -- 语法:在数字的最后加一个n(就表示是大数字数据类型)
        const bigInt = 900719925474099100000034789n;

    // 注意:大数没有隐式类型转换 - 所以大数只能跟大数进行运算 - 否则报错
        console.log(bigInt + 10); // 报错
        console.log(bigInt + 10n); // 正确做法

    // 大数字类型转换 - BigInt() - 方法
        const num = 10;
        console.log(bigInt + num); // 报错
        console.log(bigInt + BigInt(num)); // 不会报错,因为使用了BigInt函数进行了类型转换(大整数类型)
    
    // 大整数转成小整数 - 可直接使用Number数字类型转换即可
        const smallNum = Number(bigInt);

空值合并运算符使用

?? : 判断某一变量是否为空(使用方式与或运算符差不多)

// 属于一个运算符 - 语法 ??
    // 以前 - 当项将某一个变量作为另一个变量的值,担忧不确定该变量是否存有数据,就会使用或运算符做一下判断
        const foo = undefined;
        // 不过或运算符有一个弊端,就是当那一个变量是一个空的字符串、0、null - 它也会判断成假(即也会使用已定义的默认值)
        const bar = foo || "default value"; 

    /* ES11空值判断运算符 ?? (特惠明确的判断该值是不是 undefined 或 null)
    *  若不是 undefined 或 nul 就直接使用那个变量中的值
    *  反之,就使用定义好的默认值
    */
        const newBar = foo ?? "default value";

可选链的使用和场景

主要作用是为了让代码在进行null盒undefined判断是更加的清晰和简洁

// 判断属性是否是undefined
// 基本使用
    const info = {
        name:"kong",
        // 当他不再是怕朋友了,即没有了friend整个对象,那么下面的输出语句就会报错
            // friend:{
            //     name:"xiang",
            //     girlFriend:{
            //         name:"huang"
            //     }
            // }
    }
// info.undefined.girlFriend.name -- (在undefined处就已经报错了,所以后面的就不会在执行 - 就只有第一个是undefined会显示)
    console.log(info.friend.girlFriend.name); 

// 在之前通常就会先判断一下它里面是否有值 - 不过这种写法会比较混乱,不好维护
    if(info && info.friend && info.friend.girlFriend && info.friend.girlFriend.name){
        console.log(info.friend.girlFriend.name); 
    }

// ES11提供了可选链(optional chainling) 语法:?.
    /* 更加的安全更加的严谨 - ?. 判断属性是否是undefined
    *  是 - 返回undefined
    *  不是,就继续执行
    *  这样就各异很好的容错了
    */
    console.log(info.friend?.girlFriend?.name); // undefined

GlobalThis和for-in标准化

1.GlobalThis

// 获取某一个环境下的全局对象(Global Object)
    // 在浏览器下
        console.log(window);
        // console.log(this);
        // 在node中,或报错 - 因为node中没有window的变量定义
            // console.log(this);
    
    // 在node下 - 有一个global,表示全局变量(注意实在node下,没有node的环境配置,下面会报错)
        console.log(global);

    // 在以前想要无论是在浏览器中还是在node中都能执行,通常会进行一个判断 - 判断某一个值是否为undefined
    /*  ES11中的 GlobalThis - 它在不同的环境下指向的东西是不一样的
    *   当在浏览器的环境下,就指向的是window
    *   当在node的环境下,就指向的是global
    *   所以使用 GlobalThis 可以实现无论在哪一个环境下全局变量
    *   所以在日常的开发中,如果需要用到全局变量的,最好就是使用 GlobalThis
    */
        console.log(globalThis);

// 如下 - 就是将window替换成globalThis
    globalThis.setInterval(() => {
        console.log("哈哈")
    },1000);

2.for in标准化

/* 在以前使用for in,可能会在不同的浏览器中该item值获取的内容是不一致的
   有的浏览器获取的key,有的获取的是value */
// ES11后就做了个标准化 - for in,白能力出来的item都为 key
    const obj = {
        name:"kong",
        age:19
    }
    for( const item in obj){
        console.log(item);
    }

3.Dynamic Import (后续ES Module模块化中讲解)

4.Promise.allSettled (Promise中的内容)

5.import mete (后续ES Module模块化中讲解)

ES12

FinalizationRegistry和WeakRef

1.FinalizationRegistry 对象(监听GC销毁某一个对象)

// 一般情况的基本使用
    let obj = {
        nema:"kong"
    }
    /*  创建FinalizationRegistry对象,参数可以传入一个回调函数
        FinalizationRegistry对象,是用于监听某一对象被GC销毁的进程
        不过需要注意的是,在js中的GC销毁时每隔一段时间进行依次销毁 - 不是实时的(不定时)
    */
        const finalRegistry = new FinalizationRegistry(() => {
            console.log("注册在finalRegistry的对象,某一个被销毁了");
        }); 

        finalRegistry.register(obj); // 注册对象方法 - register

    /* 让obj对象为null,因为obj没有指向 {name:"kong"}对象,所以会被销毁
    系统就会给该对象打上一个标签,GC回收的时候就会根据这些标签来进项销毁
    因为 GC 销毁不是实时的,所以不时null就会立即销毁
    所以就需要等一小会才会销毁,这样就能知道GC的的销毁进程了
    */
        obj = null; 


// 注册两个或多个对象的情况
    let obj1 = {
        nema:"kong"
    }
    let obj2 = {
        name:"deng"
    }
    /*  创建FinalizationRegistry对象,参数可以传入一个回调函数
        FinalizationRegistry对象,是用于监听某一对象被GC销毁的进程
        不过需要注意的是,在js中的GC销毁时每隔一段时间进行依次销毁 - 不是实时的(不定时)
    */
        const finalRegistry = new FinalizationRegistry((value) => { // value 拿到用户注册时绑定的值
            console.log(`注册在finalRegistry的对象的${value}被销毁了`);
        }); 

        /* 若项知道时哪一个对象被销毁了,其实可以在注册的时候在绑定一个值(value) - 它会通过参数的方式传入到回到函数中
        与注册对象以逗号隔开,填写绑定内容valu/自定义名称...
        如下就分别给其绑定的时对应的对象名 
        */ 
        finalRegistry.register(obj1,"obj1"); // 注册obj1对象的监听
        finalRegistry.register(obj2,"obj2"); // 注册obj2对象的监听
        obj1 = null;
        obj2 = null;

        /* 从下图中就可以看到,上面虽然时先对obj1进行了null,在对obj2进行null的
        但销毁却是obj2先被销毁掉,从而也可以看出
        所以 GC 的的销毁机制是不定时的,也不缺定会先销毁哪一个对象
        */

2.WeakRef - 表示弱引用

// 创建 WeakRef 与使用
    let info = new WeakRef(obj); // 表示将obj赋值给info,以弱引用的方式
    
// 查看 WeakRef 中的属性(value)的方法 - deref();
    console.log(info.deref().name); // 在WeakRef中必须通过 deref() 方法才能查看里面的内容(value)

// 基本使用
    let obj1 = {
        nema:"kong"
    }
    /* obj1赋值给obj2时是一个弱引用 - 属于一个类,所以也是需要使用构造方法创建(参数为传入发对象...)
       注意:使用lWeakRef赋值的,是不能直接查看里面的内容的 - 需要使用 deref() 方法 - 【表示查看参数的主体内容】
       如下查看nema就需要 --> obj2.deref().name
    */
    let obj2 = new WeakRef(obj1);

    const finalRegistry = new FinalizationRegistry((value) => { // value 拿到用户注册时绑定的值
        console.log(`注册在finalRegistry的对象的${value}被销毁了`);
        // 可以使用可选链,来避免错误 - 因为GC回收后 obj2.deref() = undefined - undefined.name会发生错误
        console.log(obj2.deref()?.name); // undefineed - 因为使用了WeakRef就相当于是以弱引用的方式进行赋值
    }); 
    finalRegistry.register(obj1,"obj1"); // 注册obj1对象的监听
    obj1 = null;

逻辑赋值运算符的使用

1. ||= - 逻辑或赋值运算

// 基本使用
    let message = undefined;
    message = message || "default value"; // ES12之前
    message ||= "default value"; // ES12新增(逻辑或赋值运算符)

2. &&= - 逻辑与赋值运算

// 【理解逻辑与的使用】易班逻辑与(&&)一般会用于判读某一对象、属性、方法.. - 看是否存在(存在就使用)
    const obj = {
        name:"kong",
        foo(){
            console.log("foo函数被调用");
        }
    }
    obj && obj.foo && obj.foo(); // 通常会这样使用
    // 所以一般来说是用不傲等号的 ,所以逻辑与赋值运算符,目前来说用的还是比较少的

// &&= 基本使用 (很少会这样子去写)
    const info = {
        name:"kong",
    }
    // 判断info是否有值,有只的情况下,就去取处info中的name(赋值给info)
    info = info && info.name; // ES12之前
    info &&= info.name; // ES12新增(逻辑与赋值运算符)

3. ??= - 逻辑空赋值运算

// 基本使用 ??= 
    // 与逻辑或 ||= 相似 - 区别在于逻辑或在遇到空的字符串时也会直接判断成 false,而 ?? 逻辑空不会
        let message = "";
         // 使用逻辑或,它就会直接把 message 当成时一个空的值(随后直接赋值 default value)
            message ||= "default value";
         // 使用逻辑空,它会判断实实在在的值,即只有undefined与null才为空(这样就能保留本身的空的字符串了)
            message ??= "default value";

4.Number 数字的分割符(-)【前面已经讲过】

5.String,replaceAll 字符串替换方法(与replace相识)【区别:它可以替换掉所有匹配的字符串】

ES13

method .at()【数组的方法】

表示查找数组或字符串某一索引的值,区别可以书写负值

// 数组
    const arr = [1,2,3,4];
    console.log(arr.at(1)); // 2
    console.log(arr.at(-1)); // 4

// 字符串
    const str = "Hello world";
    console.log(str.at(1)); // e
    console.log(str.at(-1)); // d

// 注意:负值的话是从-1开始就相当于是最后一位(因为没有-0)

对象属性判断 hasOwn 方法

// ES13之前判断对象是否有某一个属性 obj.hasOwnProperty() 方法
    const obj = {
        name:"kong",
        age:19,
        /*/ __proto__ 属性是表示原型(相当于是在原型上设置属性)
            相当于是:obj.__proto__.address = "HUAIJI"; 【在obj原型上设置address属性】
        */
        __proto__:{ 
            address:"HUAIJI"
        }
    }
    
    console.log(obj.name,obj.age); // kong 19
    console.log(obj.address); // HUAIJI - 根据原型链,是从原型上找到
    // 在以前判断对象是否有某一个属性
        console.log(obj.hasOwnProperty("name")); // true
        console.log(obj.hasOwnProperty("address")); // false - 因为address是在原型上设置的所以,在obj对象中是没有的


// ES13中的 Object.hasOwn方法,用来替代 hasOwnProperty()方法
    const obj = {
        name:"kong",
        age:19,
        /*/ __proto__ 属性是表示原型(相当于是在原型上设置属性)
            相当于是:obj.__proto__.address = "HUAIJI"; 【在obj原型上设置address属性】
        */
        __proto__:{ 
            address:"HUAIJI"
        }
    }
    
    // ES13 Object.hasOwn(objName,property)
        // 需要传入两个值,第一个是需要判断的对象,第二个为判断的属性
        console.log(Object.hasOwn(obj,'name')); // true
        console.log(Object.hasOwn(obj,'address')); // false


/*  与原来的区别
    区别一:【可避免方法的重写】 -  因为obj.hasOwnProperty()方法有可能会被重写,导致使用的混乱(防止对象中也有自己对应的方法)
    区别二:【防止对象上的原型的指向变化后出现问题】
*/
    // 区别二演示
        const info = Object.create(null); // 创建一个对象,并将原型(__proto__)指向null
        info.name = "kong";
        /* 这个时候调用 hasOwnProperty() 方法来判断就会报错
        因为 hasOwnProperty() 方法是放在 __proto__(原型)上的,上面在创建对象的时候将对象的原型指向了 null
        所以原型上就已经没有了 hasOwnProperty() 方法
        故:掉哟个会报错  (这时就可以使用ES13中的方法了)
        */
        console.log(info.hasOwnProperty("name")); // 报错
        console.log(Object.hasOwn(info,"name")); // ES13

类中增加的新成员和私有属性

// 1.实例属性
    class Person {
        // 【ES13】对象的属性也可以在 constructor 外面书写 - 如
            height = 1.65; // 对象的属性 - public[公共的属性]

        /* 私有对象属性 private 私有:程序员之间的约定
        外部也能访问,不过时为了方便开发,程序员们之间的一个约定【或者说时指定的规则】
        */
            _intro = "neme id kong";

        // 【ES13】- 就增加了真正的私有属性 语法:以井号(#)开头的  - #
            #wife = "deng";

        constructor(name,age,address){
            // 【以前】对象的属性:咋construct通过this来设置
            this.name = name;
            this.age = age;
            this.address = "HUAIJI";
        }
    }
    const p = new Person("kong",19);
    console.log(p,p.name,p.age,p.address,p.height); // kong 19 HUAIJI 1.65
    console.log(p.#wife); // 报错 - 因为井号开头的代表着时私有属性


// 2.类属性(static)
    class Person {
    // 属性
        /*  类属性:默认 public
            也可以设置成私有(private)的,一样加一个井号开头
        */
        static totalCount = "70亿"; // public
        static #manTotalCount = "20亿"; // public

        constructor(name,age,address){
            // 【以前】对象的属性:咋construct通过this来设置
            this.name = name;
            this.age = age;
            this.address = "HUAIJI";
        }
    
    // 静态代码块 语法:static {}
        /* 第一次加载这类的时候,就会执行这里面的东西(并且只执行一次)
            在new之前就会先执行静态代码块中的内容(多数用在后端,如Java)
           一般用于做一些初始化的东西
        */
        static {
            console.log("Hello world");
            console.log("Hello dcl");
        }
    }
    const p = new Person("kong",19);
  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值