一幅长文细学JavaScript(五)——ES6-ES11新特性

5 ES版本

摘要

ES5的先天不足致使ES后续版本的发展,这也是前端人员绕不开的一个点。如果我们想要在工作和面试中轻松解决问题,那么了解ES6-ES11是必不可少的。

在本文中,我将采用一种更加通俗的方式来讲述这一块知识点,而不是照搬书上概念。

声明:在使用本文的代码时,为了避免文章冗长,我只附上了script标签内的代码供演示,如有需要详细代码可以前往代码仓库获取。学习ES6及以上新特性时,我希望你能对node.js和Vue有一定了解后再来学习,这样能帮助你减轻许多负担!

作者:来自ArimaMisaki创作

5.1 概述

5.1.1 JavaScript版本

说明:JavaScript 由 Brendan Eich 于 1995 年发明,并于 1997 年成为 ECMA 标准。我们在最开始学习时提到的名词ECMAScript 是该语言的官方名称。


5.1.2 ECMAScript版本

说明:ECMAScript 通常缩写为 ES,所有浏览器都完全支持ES3,所有现代浏览器如谷歌、火狐、Edge、Safari、Opera都完全支持ES5。

版本官方名称描述
1ECMAScript 1 (1997)第一版
2ECMAScript 2 (1998)只改变编辑方式
3ECMAScript 3 (1999)添加了正则表达式;添加了 try/catch
4ECMAScript 4从未发布过
5ECMAScript 5 (2009)添加了“严格模式”。 添加了 JSON 支持。 添加了 String.trim()。 添加了 Array.isArray()。 添加了数组迭代方法
5.1ECMAScript 5.1 (2011)编辑改变
6ECMAScript 2015添加了 let 和 const; 添加了默认参数值;添加了 Array.find();添加了 Array.findIndex()
7ECMAScript 2016添加了指数运算符(**)。 添加了 Array.prototype.includes
8ECMAScript 2017添加了字符串填充;添加了新的 Object 属性;添加了异步功能;添加了共享内存。
9ECMAScript 2018添加了 rest / spread 属性;添加了异步迭代;添加了 Promise.finally() 增加 RegExp

5.2 ES6特性

5.2.1 let关键字

<script>
        let a;
        let b,c,d;
        let e = 100;
        let f = 521,g = 'iloveyou',h = [];

        //1 变量名不能重复声明
        // let star = "ArimaMisaki";
        // let star = "憨瓜";

        //2 var不受作用域限制,而let受作用域限制,详见2.9.1
        {
            let girl = "loving girl";
        }
        console.log(girl);
        
        //3 let变量没有声明前不得使用,语法靠近C、C++
        console.log(song);
        // var song = '爱你';
        let song = '爱你';

        //4 不影响作用域链,虽然let不在作用域中却能使用
        {
            let school = "CSDN学院";
            function fn() {
                console.log(school);
            }
            fn();
        }
    </script>

5.2.2 const 常量

说明:与let和var相反,const常用于声明一个常量。const沿袭了C++的关键字的确很舒服,但总感觉js因此变得像个缝合怪。

<script>
        //声明常量、必须有初始值、推荐大写
        const NAME = "ArimaMisaki";

        //常量不可二次赋值
        // NAME = "憨瓜";

        //块级作用域
        {
            const PARTY = "单身派对";
        }
        // console.log(PARTY);

        //const作用于数组等对象的修改,不算做对常量的修改
        const TESM = ['UZI','MLXG','Ming','Letme'];
        TESM.push('Meiko');
    </script>

5.2.3 变量解构赋值

说明:在以前,我们只能通过为变量一一赋值;现在,我们允许使用模式匹配,用于从数据结构中提取值。

解构赋值的含义:解析某一数据结构,将我们想要的东西提取出来,赋给变量或常量。

模式匹配:如果你想要从数组中提取值,则你用于提取的结构必须是个数组;如果你想要从对象中提取值,则你用于提取的结构必须是个对象。

<script>
        /*====== 1.数组的结构赋值 ======*/
        const arr1 = [1,2,3,4];
        // 如果你想要从数组中提取值,则你用于提取的结构必须是个数组
        const [a,b,c,d] = arr1;
        console.log(a); //1
        console.log(b); //2
        console.log(c); //3
        console.log(d); //4

        const arr2 = [1,[2,3,4],5];
        // 如果不想提取,可以用逗号跳过
        const [a1,[,,b1],c1] = arr2;
        console.log(a1); //1
        console.log(b1); //4
        console.log(c1); //5

        const arr3 = [];
        // 解构赋值可以给出默认值,默认值只有在数组成员严格等于(===)undefined时才会生效
        const [a2,b2,c2 = 1] = arr3;
        console.log(a2); //undefined
        console.log(b2); //undefined
        console.log(c2); //1

        function fn1(a,b){
            console.log(arguments);
            // 解构赋值可以是数组提取伪数组
            const [a4,b4] = arguments;
            console.log(a4);
            console.log(b4);
        }
        fn1(1,2)

        /*====== 2.对象的结构赋值 ======*/
        const person = {
            uname: 'ArimaMisaki',
            age: 13,
            say:function(){
                console.log("会说话");
            }
        }
        // 如果你想要从对象中提取值,则你用于提取的结构必须是个对象
        const {uname:name,age:age,say:sayfn} = person; //对应写法
        // const {uname,age,sayfn} = person; //老写法
        console.log(name,age);
        sayfn();
    </script>

5.2.4 模板字符串

说明模板字符串是一种字符串的新写法,他通常用作拼接字符串

<script>
        //字符串的新声明方式:模板字符串
        let str = `字符串`;
        console.log(str,typeof str);

        //模板字符串允许换行写法,原来的String不支持
        let str2 = `<ul>
                <li>ArimaMisaki</li>
                <li>错过</li>
                <li>了你</li>
            </ul>`
        console.log(str2);

        //变量拼接
        let lovest = 'ArimaMisaki';
        let out = `${lovest}好想你`;
        console.log(out);
    </script>

5.2.5 对象简化写法

<script>
        let name = "ArimaMisaki"
        let change = function(){
            console.log('我们可以改变你');
        }

        const school = {
            // 1.可以不用写键值对了
            name,
            change,
            // 2.可以不用写function了
            improve(){
                console.log("我们可以提高你的技能");
            }
        }

        console.log(school);
    </script>

5.2.6 箭头函数

<script>
        //ES6允许使用箭头(=>)来定义函数
        let addFn = (a,b) => {
            return a+b;
        }
        console.log(addFn(2,3));

        //箭头函数的this是静态的
        window.name = '华为';
        const company = {
            name:"小米"
        }

        function getName(){
            console.log(this.Name);
        }

        let getName2 = () => {
            console.log(this.Name);
        }

        

        getName.call(company);
        getName2.call(company);

        //箭头函数不能作为构造函数来实例化对象
        // let Person = (name,age) => {
        //     this.name = name;
        //     this.age = age;
        // }
        // let me = new Person('ArimaMisaki',30);
        // console.log(me);

        //3 箭头函数不能使用arguments变量
        // let fn = () => {
        //     console.log(arguments);
        // }
        // fn(1,2,3);
        
        //4 箭头函数的简写
        //(1) 省略小括号,当形参有且只有一个
        let add = n => {
            return n+n;
        }
        console.log(add(9));
        //(2) 省略花括号,当代码体只有一条语句时
        let pow = n =>  n*n;
        console.log(pow(8));
    </script>

5.2.7 形参初始化

<script>
        //可以给形参赋初始值
        let add = (a,b,c = 3) => {
            return a+b+c;
        }
        console.log(add(1,2));

        //可以和解构赋值结合使用
        function connect({host = "127.0.0.1",username,password,port}){
            console.log(host);
            console.log(username);
            console.log(password);
            console.log(port);
        }

        connect({
            host:'localhost',
            username:'root',
            password:'root',
            port:3306
        })
    </script>

5.2.8 获取实参

<script>
        //ES5 获取实参的方式
        // function date(){
        //     console.log(arguments);
        // }
        // date('1','2','3');

        //ES6 获取实参的方式
        // function date(...args){
        //     console.log(args);
        // }
        // date('1','2','3');

        //rest所使用的...args参数必须放参数列表的最后面
        function fn(a,b,...args){
            console.log(a);
            console.log(b);
            console.log(args);
        }
        fn(1,2,3,4,5,6);
    </script>

5.2.9 扩展运算符

说明:扩展运算符并非是指ES6扩展的运算符,而是指ES6的扩展运算符,它是…运算符的称呼,也叫扩张运算符。扩展运算符的用法十分广泛,通过下面的演示,我们可以更好的了解它的作用。

<script>
        //1 扩展的...运算符可以将数组转换为序列
        const number = ['1','2','3'];

        function print(){
            console.log(arguments);
        }

        print(number);//如果这样的话,arguments获取的是数组实参对象
        print(...number);//如果这样的话,arguments获取的是数组中每一个元素的序列
        //相当于print('1','2','3')

        //2 扩展运算符的应用
        const number1 = ['1','2','3'];
        const number2 = ['4','5','6'];
        //(1)用于数组的合并
        const mergeNumber = [...number1,...number2];
        console.log(mergeNumber);
        //(2)用于克隆数组
        const copyNumber = [...number1];
        console.log(copyNumber); 
        //(3)将伪数组转为数组
        const divs = document.querySelectorAll("div");
        console.log(divs);
        const divArr = [...divs];
        console.log(divArr);
    </script>

5.2.10 Symbol

5.2.10.1 Symbol概述及创建

说明:继js六大数据类型后,Symbol成为第七个数据类型,其表示的值是一个独一无二的值,常用作对象的私有变量。

<script>
        //新数据类型
        let s = Symbol();
        // console.log(s,typeof s);

        //创建Symbol 方法一:
        let s2 = Symbol('ArimaMisaki');
        let s3 = Symbol('ArimaMisaki');
        console.log(s2 === s3);//不同,每个Symbol有唯一标识,括号内不过是值罢了

        //方法二:使用Symbol.for()
        let s4 = Symbol.for('SB');
        let s5 = Symbol.for('SB');
        //如果使用for方法创建,则Symbol创建前会在注册表中搜寻看有无重复值,如果有则直接使用不创建第二个
		//Symbol.for创建的Symbol具有全局登记特性
        console.log(s4 === s5);

        //Symbol不能和其他类型做运算
        let result = s + 100;
        console.log(result);

        // 应用:Symbol常用作对象的私有变量
        let s6 = Symbol('s6');
        console.log(s6);
        let obj = {};
        obj[s6] = 'ArimaMisaki'// 使用Symbol定义的对象中的变量,取值要用中括号
    </script>

5.2.10.2 Symbol的使用场景

说明:在一个对象中如果书写过多的属性,在不一一检查的情况下,我们极有可能将对象的属性的key写重复了。但是Symbol可以很好的避免这个问题,即使描述一样,每个Symbol也是独一无二的。

<script>
        // 应用场景1:已经有同事写出某个对象,但是对象属性过多
        let game = {
            gamename:'原神',
            play:()=>{
                console.log('玩游戏');
            }
        }
        let methods = {
            gamename:Symbol(),
            play:Symbol()
        }
        game[methods.gamename] = 'Genshin Impact'
        game[methods.play] = ()=>{
            console.log('玩原神');
        }

        // 应用场景2:自己写对象,但是属性名很多
        let school = {
            [Symbol("name")]:"ArimaMisaki",
            [Symbol("say")]:()=>{
                console.log("会说话");
            }
        }
    </script>

5.2.10.3 Symbol相关的操作
<script>
        // 1.Symbol.prototype.description 用于返回Symbol的描述
        const sym = Symbol('ArimaMisaki')
        console.log(sym.description);

        // 2.Object.defineProperty()可以将对象的属性名指定为Symbol值
        const mySymbol = Symbol();
        // 添加属性的三种写法
        // 写法一:
        // let a = {};
        // a[mySymbol] = 'Hello!';
        // // 写法二:
        // let a = {
        //     [mySymbol]:'Hello!'
        // };
        // // 写法三:
        // let a = {};
        // Object.defineProperties(a,mySymbol,{value:'Hello!'});

        // 3.Symbol.keyFor()可以返回一个已经登记过的Symbol的key
        let s1 = Symbol("foo")
        console.log(Symbol.keyFor(s1));
        let s2 = Symbol("foo")
        console.log(Symbol.keyFor(s2));
    </script>

5.2.10.4 内置的Symbol值

说明:内置的Symbol值用于扩展对象的功能,我们这里挑两个重要的讲,其他的需要去查手册即可。

<script>
        // 1.对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法
        class Person{
            static [Symbol.hasInstance](){
                console.log("用来检测类型");
            }
        }

        let o = {};
        console.log(o instanceof Person);//用来检测类型 false

        // 2.对象的SymbolinConcatSpreadable可以控制该对象是否可以展开
        let arr1 = [1,2,3]
        let arr2 = [4,5,6]
        arr2[Symbol.isConcatSpreadable] = false;
        let arr = arr1.concat(arr2)
        console.log(arr); //(4) [1, 2, 3, Array(3)]
    </script>

5.2.11 Promise

5.2.11.1 Promise概述

引入:如果我们想要实现一种功能,点击一个东西后执行相应的动作,而后再点击再执行相应的动作,那么我们需要设计函数的嵌套,函数的嵌套如果太深,就会造成回调地域

说明:Promise 是异步编程的一种解决方案,比传统的解决方案(使用回调函数和事件)更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象可看做是一个容器,里面装有异步结果。Promise本意为承诺,他承诺异步结果的状态不会受到外界的影响。Promise对象还可以返回异步操作的结果。

Promise有3种状态,一开始是pending(未完成),执行resolve后就变成fulfilled(resolved,已成功)。执行reject后就变成rejected(失败)。

Promise对象可以传入两个参数resolve和reject来控制异步的状态,通过p.then()来控制成功或失败后执行什么样的函数。若构造器中同时使用了resolve和reject,则看哪个状态最先发生,Promise对象一旦确定了状态就不会发生改变。

<script>
        const p = new Promise((resolve,reject)=>{
            // resolve控制成功状态
            // 下列代码代表1秒后异步任务状态转为成功
            setTimeout(()=>{
                let data = '数据';
                resolve(data);
            },1000);

            // reject控制失败状态
            // 下列代码代表3秒后异步任务状态为失败
            setTimeout(()=>{
                let err = '数据读取失败';
                reject(err);
            },3000);
        });

        p.then((value)=>{
            //成功调这个函数
            console.log(value);
        },(reason)=>{
            //失败调这个函数
            console.log(reason);
        })
    </script>

5.2.11.2 then

说明:调用promise对象提供的then方法可以传入成功态和失败态的promise对象接下来要做的事,并且返回一个默认成功态promise对象。

<script>
        const p = new Promise((resolve,reject)=>{
            let data = '数据';
            resolve(data);
        });

        // 调用p.then()返回是一个promise对象,且默认返回的是一个成功状态的promise对象
        // 如果调用return来返回一个数据对象,则该对象会被自动包装为promise对象,并让resolve返回
        // 如果想要返回一个失败状态的promise对象,自己return一个指向reject的promise对象即可
        console.log(p.then());
        p.then((value)=>{
            //成功调这个函数
            console.log(value);
        },(reason)=>{
            //失败调这个函数
            console.log(reason);
        }).then((value)=>{
            console.log(value);
        },(reason)=>{
            console.log(reson);
        })
    </script>

5.2.11.3 catch

引入:在实际应用中,调用then执行失败的结果很少见,故在then的两个参数中,我们一般只传入成功后promise对象执行的对应函数。

说明promise.catch方法用于执行失败态promise执行的对应函数,其本质是promise.then(null,err=>{})

<script>
        const p = new Promise((resolve, reject) => {
            // resolve('调用成功');
            reject('调用失败');
        });

        p.then((successMessage) => {
            console.log(successMessage);
        })
        p.catch((err) => {
            console.log(err);
        })
    </script>

5.2.11.4 finally

说明:promise.finally()用于执行最终操作。不管是成功态也好失败态也好,finally中的参数所代表的函数都会执行。但finally很少用,我们不必深究。


5.2.11.5 简化声明Promise对象

说明:Promise构造函数提供了Promise.resolve()Promise.reject()来代表成功态和失败态的promise对象。

<script>
        // 传统写法
        const p = new Promise((resolve, reject) => {
            resolve('调用成功');
            // reject('调用失败');
        });
        p.then((successMessage)=>{
            console.log(successMessage);
        })

        // Promise.resolve():是成功态Promise的简写形式
        const p2 = Promise.resolve('调用成功');
        p2.then((successMessage)=>{
            console.log(successMessage);
        })

        // Promise.reject():是失败态的Promise的简写形式
        const p3 = Promise.reject('调用失败');
        p3.catch((err)=>{
            console.log(err);
        })
    </script>

5.2.11.6 Promise.all

说明:Permise.all()可以传入一个Promise对象数组作为参数,如果数组中的promise对象都是成功态,那么promise.all()可以返回一个成功态的promise对象;若数组中有一个promise对象是失败态,那么最终promise.all()返回一个失败态的promise对象。

<script>
        const p1 = Promise.resolve('调用成功');
        const p2 = Promise.reject('调用失败');
        const p = Promise.all([p1,p2]);
        p.then((success)=>{
            console.log(success);
        },(err)=>{
            console.log(err);
        })
    </script>

5.2.11.7 Promise的应用

说明:如果一口气将所有的资源加载到网页上, 那么在网页结构含有较多图片的情况下,加载速度会不尽人意,用户体验差。为此,我们可以加载少部分资源,其他资源异步加载。

    <style>
        .image{
            width: 300px;
        }
    </style>

<body>
    <div>
        <img src="" alt="加载失败" class="image">
    </div>

    <script>
        const loadImgAsymc = url =>{
            return new Promise((resolve,reject)=>{
                const img = new Image();
                img.onload=()=>{
                    resolve(img);
                };
                img.onerror = ()=>{
                    reject(new Error(`Could not load Image at ${url}`))
                }
                img.src = url
            })
        }

        let image = document.querySelector('.image')
        loadImgAsymc('./img_112.jpg').then(img=>{
            setTimeout(()=>{
                image.src = img.src
            },2000)
        })
    </script>
</body>

5.2.12 迭代器

说明:迭代器是一种接口,为Js的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。ES6创建了一种新的遍历命令for…of循环,具备迭代器接口的数据结构均可使用该命令。

具备迭代器的数据结构

  • array
  • Arguments
  • Set
  • Map
  • String
  • TypedArray
  • NodeList

迭代器原理

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一个调用对象的next方法,指针自动指向数据结构的第一个成员
  3. 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
  4. 每调用next方法就返回包含value和done属性的对象

须知:ES6规定默认的Iterator接口部署在数据结构的Symbol.iterator属性中。换而言之,只要数据结构中存在该属性,那就可以使用for…of方法对其进行遍历。

<script>
        const xiyou = ['唐僧','孙悟空','猪八戒','沙僧']

        // 使用for...of遍历数组
        for(let v of xiyou){
            console.log(v);
        }
    </script>

5.2.13 生成器

5.2.13.1 生成器的基本使用

说明:Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

我试图讲的更仔细点:执行生成器函数可以返回一个迭代器对象。在生成器函数中可以定义多条yield语句,当我们调用next()时,迭代器会走完第一条yield语句,此时第一个状态结束,迭代器停止。

<script>
        function* gen(){
            console.log('第一个状态');
            yield 111;
            console.log('第二个状态');
            yield 222;
            console.log('第三个状态');
            yield 333;
        }

        let iterator = gen();
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());
    </script>

image-20220817111858530


5.2.13.2 生成器传参详解

说明:生成器的传参有点反人类。让我们解释一下:如果不经过讲解,你肯定以为下面的代码中,let x = yield '2’的意思是将yield '2’赋给x,可实际并非如此。

yield语句用于停止迭代器的运行,next()用于启动迭代器的运行。从首次next开始,先输出start,而后来到yield,此时迭代器停止。在第二次启动next时,我们将参数20传入迭代器,此时该参数赋给了x并且启动迭代器,故下面打印’one:20’。同理可得继续下去的结果。

<script>
        function* add(){
            console.log('start');
            let x = yield '2';
            console.log('one:'+x);
            let y = yield '3';
            console.log('two'+y);
        }

        const f1 = add();
        console.log(f1.next());
        console.log(f1.next(20));
        console.log(f1.next(30));
    </script>

5.2.13.3 生成器的使用场景

说明:有些数据结构没有迭代器,通过生成器我们可以给这些数据结构提供遍历功能。


5.2.14 集合Set

<script>
        // 1.声明set
        let s = new Set();
        let s2 = new Set(['a','a','b','c'])//满足高中数学集合的三大特性
        // 2.常用方法
        console.log(s2.size());//查看元素个数
        console.log(s2.add('e'))//新增元素
        console.log(s2.delete('e'));//删除元素
        console.log(s2.has('a'));//判断存在性
        s2.clear()//清空数据
    </script>

5.2.15 映射表Map

<script>
        // 1.声明Map
        let m = new Map()

        // 2.添加元素
        m.set('name','ArimaMisaki')
        m.set('say',()=>{
            console.log('说话');
        })
        
        // 3.删除元素
        m.delete('name')

        // 4.get依键寻值
        console.log(m.get('say'));
    </script>

5.2.16 类

5.2.16.1 面向对象

说明:在ES6之前JS并没有类,创建对象可以使用构造器来创建,但是在ES6后,出现了类。


5.2.16.2 类

说明:类封装一类事物共有的特征,通过类可以实例化对象。如封装车类,实例化宝马这种品牌的车。

<script>
        // 1.创建一个Star类
        class Star {
            // 类中的构造函数
            constructor(uname) { 
                this.uname = uname
                //对象私有方法
                this.say = ()=>{
                    console.log(this.uname+'会说话');
                }
            }
            // 对象共有方法
            sing(){
                console.log('明星会唱歌');
            }
        }

        // 2.实例化对象
        let ldh =  new Star('刘德华')
        console.log(ldh.uname);
        ldh.say()
        ldh.sing()
    </script>

5.2.16.3 继承

extend说明:在ES6之前需要使用复杂的方式来实现继承,在ES6中使用extend即可继承类。

super说明:super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数。

<script>
        class Father {
            constructor(x, y) {
                this.x = x
                this.y = y
            }
            sum() {
                console.log(this.x + this.y);
            }
        }

        class Son extends Father {
            constructor(x, y) {
                // this.x = x
                // this.y = y
                super(x, y) //调用父类构造函数
            }
        }
        let son = new Son(1, 2)
        //需使用super才可执行
        son.sum()
    </script>

5.2.16.4 方法重写

说明:子类可以重写父类的方法。重写完成后,子类调用该方法按照就近原则寻找。如果想要使用父类的方法,可以考虑使用super。

<script>
        class Father{
            say(){
                console.log('我是爸爸');
            }
        }
        class Son extends Father{
            say(){
                console.log('我是儿子');
            }
            FatherSay(){
                //调用父类的函数
                super.say()
            }
        }
        let son = new Son()
        son.say()
        son.FatherSay()
    </script>

5.2.16.5 注意事项

提示:在定义子类的私有方法时,需要将super置于this之前。

<script>
        class Father {
            constructor(x, y) {
                this.x = x
                this.y = y
            }
            sum() {
                console.log(this.x + this.y);
            }
        }

        class Son extends Father {
            constructor(x, y) {
                super(x, y)
                this.x = y
                this.y = y
            }
            substract() {
                console.log(this.x - this.y);
            }
        }

        let son = new Son(4, 3)
        son.sum()
        son.substract()
    </script>

5.2.17 数值扩展

<script>
        // 1.Number.EPSILON用于解决浮点数精度问题,其表示JS表示的最小精度
        console.log(0.1 + 0.2 === 0.3);//false,具有机器误差
        function equal(a, b) {
            if(Math.abs(a-b) < Number.EPSILON){
                return true
            }else{
                return false
            }
        }
        console.log(equal(0.1 + 0.2, 0.3));
        console.log('==============');

        // 2.进制的表示
        let b = 0b1010 //二进制
        let o = 0o777 //八进制
        let d = 100 //十进制
        let x = 0xff //十六进制
        console.log(b);// 10
        console.log('==============');

        // 3.Number.isFinite 检测一个数是否是有穷数
        console.log(Number.isFinite(100));//false
        console.log(Number.isFinite(100/0));//true
        console.log(Number.isFinite(Infinity));
        console.log('==============');

        // 4.Number.isNaN 检测一个数值是否为NaN
        console.log(Number.isNaN(123));//false
        console.log('==============');

        // 5.Number.parseInt 转型
        console.log(Number.parseInt(3.06));//3
        console.log('==============');

        // 6.Number.isInteger 判断是否为整数
        console.log(Number.isInteger(3.5));//false
        console.log('==============');

        // 7.Math.trunc 将数字的小数部分抹掉
        console.log(Math.trunc(3.666));//3
        console.log('==============');

        // 8.Math.sign 判断一个数是正数负数还是0
        console.log(Math.sign(100));
        console.log(Math.sign(0));
        console.log(Math.sign(-100));
    </script>

5.2.18 对象方法扩展

<script>
        // 1.Object.is 判断两个值是否完全相等
        console.log(Object.is(120,120));
        // 和全等号的区别
        console.log(Object.is(NaN,NaN));
        console.log(NaN === NaN);
        
        // 2.Object.assign(被覆盖对象,覆改对象) 对象合并
        const person1 = {
            name:'zs',
            age:12
        }
        const person2 = {
            name:'ls',
            age:13
        }
        console.log(Object.assign(person1,person2));
        // 属性必须两者都有才可覆改

        // 3.Object.setPrototypeOf设置原型
        const school = {
            name:'hstc'
        }
        const scities = {
            xiaoqu:['潮州']
        }
        Object.setPrototypeOf(school,cities)
        console.log(school);
    </script>

5.2.19 模块化

说明:模块化是指将一个大的程序文件拆分成多个小的文件,然后将小文件组合起来。

提示:可以将import的代码单独写在一个文件,而后使用src导入。

<script type = "module">
        //1.import 内容 [as 别名] from 文件位置
        import * as m1 from "./Demo5_2_19.js"
        console.log(m1);
        console.log(m1.school);
        console.log(m1.teach);

        // 2.解构赋值 import {内容 as 别名} from 文件位置
        import {school,teach} from "./Demo5_2_19.js"
        console.log(school);
    </script>
/* 1.分别暴露 */
export let school = 'hstc'

export var teach = () =>{
    console.log("开发技能");
}

/* 2.统一暴露 */
let school = 'hstc'
let teach = ()=>{
    console.log("开发技能");
}
export{school,findJob}

/* 3.默认暴露 */
export default{
    school:'hstc',
    teach:()=>{
        console.log("开发技能");
    }
}

5.2.20 Proxy

引入:在ES5中,如果我们创建了一个对象,并且访问其属性,那么即使一个属性在对象中不存在也能访问,只是值为undefined。

说明:让我们看阮一峰的ES6入门对Proxy的解释——Proxy 可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

一言蔽之:Proxy就是一个拦截器,他拥有类似于Java里面的get,set方法,但get和set方法主要作用并不是提供访问和修改数据的方法,而是拦截外界访问和修改数据。

格式:let proxy = new Proxy(拦截对象,拦截行为对象)

拦截行为

  • get(target,propKey):拦截对象属性的读取
  • set(target,propKey):拦截对象属性的设置

提示:如果不设置拦截行为对象,那么proxy代理器相当于没有设置,proxy依然和拦截对象没有区别。

<script>
        let person = {
            name:'ArimaMisaki',
            age:19
        }

        // 1.get用于拦截对目标对象属性的访问,其中target为目标对象,key为访问属性
        // 下面的代码意为:创建了person的代理对象,该对象以person为目标,当外界调用.属性时,key为该属性
        let proxy = new Proxy(person,{get:(target,key)=>{
            // 如果不写return,那么相当于没有设置代理,默认返回undefined
            return 35
        }})
        console.log(proxy.name);

        // 2.我们可以拦截属性的访问
        let proxy1 = new Proxy(person,{get:(target,key)=>{
            if(key === 'name'){
                return "禁止访问该属性"
            }
        }})
        console.log(proxy1.name);

        // 3.set同理,用于拦截对目标对象属性的修改
        let proxy2 = new Proxy(person,{set:(target,key)=>{
            if(key === 'name'){
                return "禁止修改该属性"
            }
        }})
        proxy2.name = "傻蛋"
        console.log(proxy2.name);
    </script>

5.3 ES7特性

<script>
        // 1.indexof 判断是否存在
        const mingzhu = ['西游记','红楼梦','三国演义','水浒传']
        console.log(mingzhu.includes('西游记'));
        console.log(mingzhu.includes('金瓶梅'));

        // 2.** 幂运算
        console.log(2 ** 2); //相当于Math.pow(2,2)
    </script>

5.4 ES8特性

5.4.1 async函数

说明:如果在函数中添加async修饰字,那么此时会有几种情况:

  • 默认该函数返回成功态的promise对象,即使你不写return
  • 如果指定返回一个失败态的promise对象,则async失去了意义,因为async就是为了返回一个成功态promise对象而生
  • 如果指定抛出一个异常,则返回的是一个失败态的promise对象

也就是说,async函数的返回值为promise对象,promise对象的状态依return结果而定,但默认是成功态。


5.4.2 await表达式

说明:await修饰字常用于表达式中,且后跟一个promise对象,表示接收其成功态promise对象的结果,相当于then方法中resolve的结果。

<script>
        // 1.then写法
        const p = new Promise((resolve,reject)=>{
            resolve("生成一个成功态对象")
        })
        p.then((msg)=>{
            console.log(msg);
        })

        // 2.await写法
        const p2 = new Promise((resolve,reject)=>{
            resolve("生成一个成功态对象")
        })
        async function fn(){
            let result = await p2
            console.log(result);
        }
        fn()
    </script>

5.4.3 async和await的应用

5.4.3.1 应用一:结合node.js来读取文件信息
const fs = require('fs');

const readtext = () => {
    return new Promise((resolve, reject) => {
        fs.readFile('./Demo5_4_3_1.txt','utf-8', (err, dataStr) => {
            if (err) reject(err)
            else resolve(dataStr);
        })
    })
}

async function main(){
    let text = await readtext();
    console.log(text);
}
main();

5.4.3.2 应用二:发送Ajax请求
<script>
        let sendAjax = (url) => {
            return new Promise((resolve, reject) => {
                const x = new XMLHttpRequest();
                x.open('GET', url);
                x.send();
                x.onreadystatechange = () => {
                    if (x.readyState === 4 && x.status === 400) {
                        resolve(x.response);
                    } else {
                        reject(x.status);
                    }
                }
            })
        }

        async function main(){
            await sendAjax('https://api.apiopen.top/getJoke');
            console.log(result);
        }
        main()
    </script>

5.4.4 对象方法扩展

<script>
        const person = {
            name:"ArimaMisaki",
            age:12
        }

        // 1.获取对象所有的键
        console.log(Object.keys(person));
        
        // 2.获取对象所有的值
        console.log(Object.values(person));
        
        // 3.获取对象中的每条键值对
        console.log(Object.entries(person));
        const map = new Map(Object.entries(person));
        console.log(map);//可用于创建map

        // 4.获取对象属性的描述性对象
        console.log(Object.getOwnPropertyDescriptors(person));
    </script>

5.5 ES9特性

5.5.1 扩展运算符

说明:在ES6中我们曾经学过…运算符,它有诸多用途,如合并数组、复制数组、伪数组转换为数组等作用。在ES9中,…运算符再次被赋予了强大的特性,它可以将一个对象的多个属性塞到一个变量中。

<script>
        const person = {
            name:"ArimaMisaki",
            age:13,
            money:100
        }
        const person1 = {...person};
        console.log(person1);
    </script>

5.5.2 命名捕获分组

说明:命名捕获分组是正则表达式在ES9的一个扩展功能。当我们使用一条正则表达式提取字符串内容时,我们可能想要使用一条正则提取多个字符串部分,命名捕获分组允许我们对这些部分进行命名,格式为?<groupname>

<script>
        /* 老式写法 */
        let str = '<a href="https://www.baidu.com">百度</a>'

        // 1.提取url和标签文本
        const reg = /<a href="(.*)">(.*)<\/a>/
        
        // 2.执行
        const result = reg.exec(str);

        console.log(result[1]);
        console.log(result[2]);


        /* 分组写法 */ 
        // 1.提取url和标签文本并分组
        const reg2 = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;
        // 2.执行
            const result2 = reg2.exec(str)
        console.log(result2.groups.url);
        console.log(result2.groups.text);
    </script>

5.5.3 反向断言

说明:如果我们想要通过正则提取一个片段,若根据片段的后半部分来提取则为正向断言,根据片段的前半部分来提取则为反向断言

<script>
        // 声明一个字符串
        let str = '5201314我爱你999男神';

        // 1.正向断言:根据字符串的后面部分来确定字符串片段
        const reg = /\d+(?=男)/;
        const result = reg.exec(str);
        console.log(result);
        // 2.反向断言:根据字符串的前面部分来确定字符串片段
        const reg1 = /(?<=么)\d+/;
        const result1 = reg.exec(str);
        console.log(result);
    </script>

5.7 ES11

5.7.1 私有属性

说明:ES11允许使用井号#来定义私有属性,私有属性无法被类外访问。

<script>
        class Person {
            name;
            #age;
            #weight

            constructor(name,age,weight){
                this.name = name;
                this.#age = age;
                this.#weight = weight
            }
            getter(){
                console.log(this.name);
                console.log(this.#age);
                console.log(this.#weight);
            }
        }

        const girl = new Person('晓红',18,'45kg');

        console.log(girl.name); 
        console.log(girl.#age); 
        console.log(girl.#weight); 
        girl.getter()
    </script>

5.7.2 Promise.allSettled

说明:ES11中的Promise.allSetted()和ES6的Promise.all()方法类似,用于处理批量异步任务。但不同的是,前者接收promise数组,但返回值永远都是成功态的promise对象;后者也接收promise数组,但返回值需要依靠promise数组的状态而定。

<script>
        //声明两个promise对象
        const p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("商品数据 - 1");
            }, 1000);
        });

        const p2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                // resolve("商品数据 - 2");
                reject("出错啦!");
            }, 1000);
        });

        //调用 allsettled 方法
        const result1 = Promise.allSettled([p1, p2]);
        console.log(result1);
    </script>

5.7.3 String.prototype.matchAll

说明:matchAll方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器,如果我们想要看到所有匹配的结果,可以使用for...of

<script>
        let str =
            `<ul>
    <li>
        <a>肖生克的救赎</a>
        <p>上映日期: 1994-09-10</p>
    </li>
    <li>
        <a>阿甘正传</a>
        <p>上映日期: 1994-07-06</p>
    </li>
</ul>`;

        //声明正则
        const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg;

        //调用方法
        const result = str.matchAll(reg);
        for (let v of result) {
            console.log(v);
        }
    </script>

5.7.4 可选链操作符

说明:对函数进行传参时,我们的参数很有可能是对象或者数组,当我们想要在函数内部使用对象属性或数组元素时,我们总希望判断它们是否存在。判断的方式通常是使用&&确定多重条件。这样虽然能够解决问题,但总被人家嫌弃使用&&层层判断写出来的语句可能会过长。

为此,在ES11中采取了可选链操作符?.

<script>
        let main = (config)=>{
            // 我们需要判断参数传进来了没
            // 1.传统写法
            // const dbHost = config && config.db && config.db.username
            // 2.可选链操作符写法
            const dbHost = config?.db?.username
        }

        main({
            db:{
                host:'192.168.1.1',
                username:'root'
            }
        })
    </script>

5.7.5 动态Import

说明:在ES6中,我们使用import可以导入包,但这么做会导致网页加载时耗费大量的时间在导包上,我们需要的是:当我们使用这个包时,我们再动态地导入。

ES11提供了动态导包机制,使用import(包路径)可以返回一个promise对象,通过then我们可以导入对应包中module暴露的部分。

const btn = document.getElementById('btn')
btn.onclick = ()=>{
	// 动态导包
    import('./hello.js').then(module =>{
        module.hello();
    })
}

5.7.6 BigInt

说明:继ES5的number,boolean,undefined,null,string,object之后,ES6出现了Symbol,在ES11中又引入了BigInt作为第八个数据类型。BigInt用于一些较大数值的操作。

格式:let a = 12n

<script>
        // 1.bigint的声明
        let a = 12n
        console.log(a,typeof a);

        // 2.bigint转换
        let b = 123
        // 不可强转浮点数
        console.log(BigInt(b),typeof BigInt(b));
    </script>

5.7.7 绝对全局变量

说明:我们在构造函数中常常使用this代指使用该构造函数生成的对象。在ES11中,引入了globalThis来代指window对象。

<script>
        console.log(globalThis);
    </script>

5.8 编程风范

5.8.1 let取代var

记得我们曾经说过,使用var来声明变量会有以下几个问题:

  1. 不赋初值也可以使用,值默认为undefined
  2. 存在变量提升

使用let可以避免以上的问题,且无副作用。


5.8.2 全局常量

在一个数据为常量时,我们应该尽可能使用const而不是let,因为const底层存在优化机制,且可以很好的告诉代码的阅读者,该数据为常量不可修改。


5.5.3 解构赋值

在对数组成员或者是对象成员进行赋值时,应该尽量使用解构赋值。


5.8.4 字符串

如果想要动态地修改字符串,我们应该使用模板字符串,反之我们应该使用引号字符串来提高安全性。


5.8.5 浅拷贝

如果想要使用浅拷贝来拷贝一个数组,应该优先使用扩展运算符进行拷贝。


5.8.6 Map

只有在模拟现实对象时我们才会使用Object,但如果只是需要key:value的数据结构来存放数据,优先使用Map,因为Map里面存在迭代器方便遍历。


5.8.7 Class

在构造函数生成对象和Class生成对象之间,我们总是选择Class,因为其不具有prototype等繁琐的知识点。


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ArimaMisaki

如果知识有用请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值