ES6-ES12学习笔记

113 阅读40分钟

ES介绍

ES就是ECMAScript简称,JS就是ES的一个实现,这里ECMA(欧洲计算机制造商学会)每年发布一个ES新版本,2020年到了ES11,前置内容: JS,Ajax,Promise

为什么从ES6开始: 变动多,有里程碑意义,新增语法特性

ES兼容性:查询网站,即使不兼容我们也可以通过编译器编译成ES5

ES6新特性

let关键字

  • let不可以重复声明,但是var可以
var varIns = "A";
var varIns = "B"; // 合法
let letIns = "A";
let letIns = "B"; // 不合法
  • let具有块级作用域,var没有块作用域,JS的作用域有

    • 全局作用域(例如a=1,那么a就是window的成员)
    • 函数作用域(例如在函数中var a=1, 那么出了函数a就没了)
    • eval作用域
    • 块作用域(例如{a=1},if(){},while(){}…,那么在括号外面a就不可用)
  • let不存在变量提升,例如

console.log(a);       // 输出undefined
var a=1;
console.log(a);       // 输出1

console.log(b);       // 报错
let b=1;
console.log(b);
  • **let不影响作用域链 **

    • 虽然是块级作用域,但是不影响作用域链 。如果函数内部没有变量会自动往外找

应用场景:以后声明变量使用 let 就对了

实例

// 我有5个button
var btns = document.querySelectorAll("button");
for(var i = 0;i < btns.length;i++){
    btns[i].onclick = function(){
        console.log(i);  //5 5 5 5 5
    }
}

解决:

// 使用this
var btns = document.querySelectorAll("button");
for(let i = 0;i < btns.length;i++){
    btns[i].onclick = function(){
        console.log(this.i); // 0 1 2 3 4
    }
}
//增加参数
var btns = document.querySelectorAll("button");
for(var i = 0;i < btns.length;i++){
    btns[i].idx = i;
    btns[i].onclick = function(){
        console.log(this.idx); // 0 1 2 3 4
    }
}
// 修改为let
var btns = document.querySelectorAll("button");
for(let i = 0;i < btns.length;i++){
    btns[i].onclick = function(){
        console.log(i); // 0 1 2 3 4
    }
}

const关键字

  • 必须赋初值
  • 建议大写
  • 不允许重复声明
  • 常量值不能修改
  • 有块级作用域
  • 对于数组/对象的元素修改不算修改,不会报错,因为const对象指向的地址没有变

应用场景:声明对象类型使用 const,非对象类型声明选择 let

解构赋值

可以按照一定模式从数组/对象提取值,对变量进行赋值

  1. 对数组进行解构[...]=Array,就是找对应元素分别对应数组

    不是很常用

    let [a,b,c,d]=[111,222,333,"789"];
    console.log(a); //111
    console.log(b); //222
    console.log(c); //333
    console.log(d); //"789"
    
  2. 可以对对象进行解构{...}=Object, 要求变量名一一对应

    解构一般用于提取方法,例如我之前要写t.c()就可以简写成c()

    let t={
        a:111,
        b:222,
        d:333,
        c:function(){console.log("OK")}
    }
    let {a,b,c,d}=t;
    console.log(a); //111
    console.log(b); //222
    console.log(c);  //function(){console.log("OK")}
    c(); //ok
    console.log(d); //333
    d();              // Error
    
  3. 解构时候如果数目不同/不匹配时,会尽量匹配,例如

    let [a,b] = [1,2,3]  // a=1 b=2
    let [a,b,c,d] = [1,2,3]  // a=1 b=2 c=3 d=undefined
    let t={
        x:111,
        y:222,
        z:333,
        w:function(){console.log("OK")}
    }
    let {x,y} = t;      // x=111 y=222
    let {z,w,s} = t;    // z=333 w=[function] s=undefined
    
  4. 解构支持默认值
    JS确认某个参数要使用默认值是这个参数===undefined

    let [m1,m2="S"] = ["A"]   // m1="A" m2="S"
    let [n1,n2="S"] = ["A",undefined]   // n1="A" n2="S"
    let [p1,p2="S"] = ["A",null]   // p1="A" p2=null
    
  5. 字符串的解构赋值
    将字符串转化为数组

    let [a,b,c] = "Liu" // a="L" b="i" c="u"
    
  6. 数值与布尔值解构

    let {toString: s}=123;
    console.log(s===Number.prototype.toString) //true
    let {toString: r}=true;
    console.log(r===Boolean.prototype.toString) //true
    
  7. 函数参数解构

    function foo([a,b]){
      console.log(a,b); // 1,2
    }
    foo([1,2])
    
    function foo2({a=0,b=0}={}){        // 无参时默认{} a,b也有默认值
    //function foo2({a,b}={a=0,b=0}){   // 注意,这两种方法出的结果不同    
      console.log(a,b);
    }
    foo2({a:1,b:2})         // 1,2
    foo2({a:1})             // 1,0
    foo2({a:undefined,b:2}) // 0,2
    foo2()                  // 0,0
    
    - 用处
        - 交换值`[a,b]=[b,a]`
        - 接受函数返回对象/数组
        - 函数参数定义
        - 函数参数默认值
        - 加载模块
    
    const { SourceMapConsumer, SourceNode} = require("source-map")
    
        - 获取map值
    
    let map = new Map();
    map.set("a","A")
    map.set("b","B")
    
    // 获取键值
    for (let [key,value] of map) {
      // ...
    }   
    
    // 获取键名
    for (let [key] of map) {
      // ...
    }   
    
    // 获取键值
    for (let [value] of map) {
      // ...
    }
    
  8. 补充for的遍历

    • for in 便历出来的是属性
    • for of 遍历的是value
    • 手动给对象添加属性后, for in 是可以将新添加的属性遍历出来 但是for of 不行
    • for in 的属性是使用[]不可以使用 “.” eg: data[‘index’] instead of data.index

注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式

模板字符串

模板字符串使用````声明

  1. 声明

    let str = `我也是一个字符串哦!`;
    console.log(str, typeof str); //我也是一个字符串哦!
    
  2. 可以出现换行符

    let str = `<ul>
                <li>沈腾</li>
                <li>玛丽</li>
                <li>魏翔</li>
                <li>艾伦</li>
               </ul>`;
    //es5需要用‘’和+拼接    
    
  3. 变量拼接,使用${}

    let lovest = '魏翔';
    let out = `${lovest}是我心目中最搞笑的演员!!`;
    console.log(out);
    
    //es5:
    let lovest = '魏翔';
    let out = lovest +'是我心目中最搞笑的演员!!';
    console.log(out);
    

注意:当遇到字符串与变量拼接的情况使用模板字符串

对象的简化写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁

let name = '尚硅谷';
let change = function(){
    console.log('我们可以改变你!!');
}

const school = {
    name,
    change,
    //improve= function(){
    improve(){
        console.log("我们可以提高你的技能");
    }
}
console.log(school);


let aaa = "aaa"
let bbb = "bbb"
console.log({aaa,bbb})
console.log({aaa: "aaa", bbb: "bbb"})
console.log({aaa, bbb, foo(){console.log("OK")}})
console.log({aaa: "aaa", bbb: "bbb", foo:function(){console.log("OK")}})

//{aaa: "aaa", bbb: "bbb"}
//{aaa: "aaa", bbb: "bbb"}
//{aaa: "aaa", bbb: "bbb", foo: ƒ}
//{aaa: "aaa", bbb: "bbb", foo: ƒ}

这里要简写的是变量,不能是常量,例如我想构造{"A":"A"}不能写{"A"}

箭头函数

function(a,b)=>{//code}简写为(a,b)=>{//code}

  • 声明一个函数
let fn = function(){

}
let fn = (a,b) => {
    return a + b;
}
  • 调用函数
let result = fn(1, 2);
console.log(result);
  1. this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值(call,apply修改对 他无效)

    function getName(){
        console.log(this.name);
    }
    let getName2 = () => {
        console.log(this.name);
    }
    //设置 window 对象的 name 属性
    
    window.name = '尚硅谷';
    
    const school = {
    
      name: "ATGUIGU"
    
    }
    
    //直接调用
    
    getName(); //尚硅谷
    
    getName2(); //尚硅谷
    
    //call 方法调用
    
    getName.call(school); //ATGUIGU
    
    getName2.call(school); //尚硅谷
    
  2. 不能作为构造实例化对象,因为this不能指向对象,会报错XX不是一个构造函数

    let Person = (name, age) => {
         this.name = name;
         this.age = age;
     }
    let me = new Person('xiao',30); 
    console.log(me); //报错Person不是一个构造函数
    
  3. 不能使用 arguments 变量

    let fn = () => {
    
      console.log(arguments);
    
    }
    
    fn(1,2,3);  //报错,arguments没有定义
    
  4. 箭头函数的简写

    //1) 省略小括号, 当形参有且只有一个的时候
    
    let add = n => {
    
        return n + n;
    
    }
    
    console.log(add(9));
    
    //2) 省略花括号, 当代码体只有一条语句的时候, 此时 return 必须省略
    
    //而且语句的执行结果就是函数的返回值
    
    let pow = n => n * n;
    
    console.log(pow(8));
    
    //3) 特例,我想简写返回对象x => { foo: x }会报错,换成x => ({ foo: x })
    

实例:

箭头函数适合与 this 无关的回调. 如定时器, 数组的方法回调 箭头函数不适合与 this 有关的回调. 如事件回调, 对象的方法

<div id="ad"></div>
<script>
    //需求-1  点击 div 2s 后颜色变成『粉色』
    //获取元素
    let ad = document.getElementById('ad');
    //绑定事件
    ad.addEventListener("click", function(){
        //保存 this 的值
        let _this = this;  //此this为ad
        //定时器
        
        //普通函数
        setTimeout(function() {
            //修改背景颜色 this
            console.log(this);  //此时this为window,所以外层需令_this=this
            _this.style.background = 'pink';
        }, 2000);
        //箭头函数
        setTimeout(() => {
            //修改背景颜色 this
            console.log(this);  //箭头函数的this为静态的,指向函数声明时作用域下的this
            this.style.background = 'pink';
        }, 2000);
    });

    //需求-2  从数组中返回偶数的元素
    const arr = [1,6,9,10,100,25];
    
     //普通函数
    // const result = arr.filter(function(item){
    //     if(item % 2 === 0){
    //         return true;
    //     }else{
    //         return false;
    //     }
    // });
    
    //箭头函数
    const result = arr.filter(item => item % 2 === 0);

    console.log(result);
    
    // 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
    // 箭头函数不适合与 this 有关的回调.  事件回调, 对象的方法

</script>

参数默认值

ES6 允许给函数参数赋值初始值

  1. 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)

    function add(a,c=10,b) {
        return a + b + c;
    }
    let result = add(1,2);
    console.log(result);  //NaN,如果c=10写在最后,就是13
    
  2. 与解构赋值结合

    //原本写法  option写多次
    function connect(option){
        this.host=option.host;
        this.username=option.username;
        this.password=option.password;
        this.port=option.port;
    }
    function connect({host="127.0.0.1", username,password, port}){
        console.log(host)
        console.log(username)
        console.log(password)
        console.log(port)
    }
    connect({
        host: 'atguigu.com',
        username: 'root',
        password: 'root',
        port: 3306
    })
    

rest参数

ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments

  1. ES5 获取实参的方式
function date(){
    console.log(arguments); //对象 0:白芷 1:阿娇 2:思慧
}
date('白芷','阿娇','思慧');
  1. rest 参数

    function date(...args){
        console.log(args);// 数组 ['阿娇','柏芝','思慧']  可以使用数组方法 filter some every map 
    }
    date('阿娇','柏芝','思慧');
    
  2. rest 参数必须要放到参数最后,否则报错

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

注意:rest 参数非常适合不定个数参数函数的场景

spread扩展运算符

『...』 扩展运算符能将『数组』转换为逗号分隔的『参数序列』

  1. 声明一个数组 ...

    const tfboys = ['易烊千玺','王源','王俊凯'];// => '易烊千玺','王源','王俊凯'
    
  2. 声明一个函数

    function chunwan(){
      console.log(arguments);
    }
    chunwan(tfboys); //arguments只有一个数,为数组['易烊千玺','王源','王俊凯']
    chunwan(...tfboys);// 相当于chunwan('易烊千玺','王源','王俊凯'),arguments有三个数分别为'易烊千玺','王源','王俊凯'
    
  3. 和rest参数的区别

    rest:放在形参位置

    spread:放在函数调用位置

实例:

//1. 数组的合并 情圣  误杀  唐探
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
const res1= kuaizi.concat(fenghuang); //['王太利','肖央','曾毅','玲花']
const res2= [...kuaizi, ...fenghuang]; //['王太利','肖央','曾毅','玲花']
console.log(res1);

//2. 数组的克隆
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua];//  ['E','G','M']  //如果有引用数据类型则为浅拷贝
console.log(sanyecao);

//3. 将伪数组转为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr); 

//把argument伪数组转为数组,其实没必要因为rest参数更好用
function foo(){
  let agms = [...arguments]
  console.log(agms)
}
foo(1,2,3,4,5)

symbol

基本使用

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。

  1. Symbol 特点

    1. Symbol 的值是唯一的,用来解决命名冲突的问题
    2. Symbol 值不能与其他数据进行运算
    3. Symbol 定义 的 对象属 性 不能 使 用 for…in 循 环遍 历 ,但 是可 以 使 用Reflect.ownKeys 来获取对象的所有键名
  2. Symbol的创建

    //创建Symbol
    let s = Symbol();  // 不用写new 因为是js的默认数据类型
    console.log(s, typeof s); //Symbol() 'symbol'
    
    //给symbol传入一个注释
    let s2 = Symbol('doing');
    let s3 = Symbol('doing');
    console.log(s2===s3); //false 'doing'只是一个标志,在ES10中会有新方法对注释进行利用
    
    //Symbol.for 创建
    let s4 = Symbol.for('doing');
    let s5 = Symbol.for('doing');
    console.log(s4===s5); //true
    
    //不能与其他数据进行运算  报错
       let result = s + 100;
       let result = s > 100;
       let result = s + s;
    
  3. js类型总结

    USONB you are so niubility u undefined s string symbol o object n null number b boolean

  4. symbol使用

    //向对象中添加方法 up down
    let game = {
        name:'俄罗斯方块',
        up: function(){},
        down: function(){}
    };
    //直接game.up=function(){} 可能会产生错误,因为我们不知道game中是否已经有up这个属性了
    
    //声明一个对象
    let methods = {
        up: Symbol(),
        down: Symbol()
    };
    
    game[methods.up] = function(){
        console.log("我可以改变形状");
    }
    
    game[methods.down] = function(){
        console.log("我可以快速下降!!");
    }
    
    console.log(game);
    
    
    let youxi = {
        name:"狼人杀",
        //Symbol(): function(){   //这么表达不行,Symbol()是一个动态表达式,必须加一个[]
        //},
        [Symbol('say')]: function(){
            console.log("我可以发言")
        },
        [Symbol('zibao')]: function(){
            console.log('我可以自爆');
        }
    }
    
    console.log(youxi)
    
  5. Symbol作用

    Symbol的作用非常的专一,唯一目的——作为对象属性的唯一标识符,防止对象属性冲突发生

    举个例子,你看上了公司前来的前台妹纸,想了解关于她的更多信息,于是就询问Hr同事,扫地阿姨,于是得到类似这样信息:

let info1 = {
    name: '婷婷',
    age: 24,
    job: '公司前台',
    description: '平时喜欢做做瑜伽,人家有男朋友,你别指望了'
}
let info2 = {
    description: '这小姑娘挺好的,挺热情的,嘿嘿嘿……'
}
显然,你需要对这两个数据进行汇总,结果,就会发现,描述都用了同一个对象属性description,于是整合的时候,就容器冲突,覆盖,导致“人家有男朋友”这么重要的信息都没注意到。

但是,如果要是Symbol,则完全就不要担心这个问题了:
let info1 = {
    name: '婷婷',
    age: 24,
    job: '公司前台',
    [Symbol('description')]: '平时喜欢做做瑜伽,人家有男朋友,你别指望了'
}
let info2 = {
    [Symbol('description')]: '这小姑娘挺好的,挺热情的,嘿嘿嘿……'
}
let target = {};
Object.assign(target, info1, info2); 
/*{name: "婷婷", age: 24, job: "公司前台", 
Symbol(description): "平时喜欢做做瑜伽,人家有男朋友,你别指望了", 
Symbol(description): "这小姑娘挺好的,挺热情的,嘿嘿嘿……"}*/

注: 遇到唯一性的场景时要想到 Symbol

内置值

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

作为其他对象的属性来使用

image.png

 class Person{
    static [Symbol.hasInstance](param){
        console.log(param);
        console.log("我被用来检测类型了");
        return false;
    }
}

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


const arr = [1,2,3];
const arr2 = [4,5,6];
arr2[Symbol.isConcatSpreadable] = false; //连接时无法展开
console.log(arr.concat(arr2)); // [1,2,3,Array(3)]

其他Symbol相关

  • Symbol与for…in迭代

    Symbols在for…in迭代中不可枚举,如果想要达到效果,借Object.getOwnPropertySymbols(obj)

var obj = {};

obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";

for (var i in obj) {
   console.log(i);   // 输出 "c" 和 "d"
}
  • Symbol与JSON.stringify()
    当使用JSON.strIngify()时以symbol值作为键的属性会被完全忽略,例如:
JSON.stringify({[Symbol("foo")]: "foo"});    // '{}'              
  • Symbol包装器对象作为属性的键 围绕原始数据类型创建一个显式包装器对象从ECMAScript 6开始不再被支持,所以new Symbol()会报错,然而,现有的原始包装器对象,如 new Boolean、new String以及new Number因为遗留原因仍可被创建。

    此时,如果我们想创建一个Symbol包装器对象 (Symbol wrapper object),你可以使用Object()函数:

var sym = Symbol("foo");
typeof sym;     // "symbol"
var symObj = Object(sym);
typeof symObj;  // "object"
当一个Symbol包装器对象作为一个属性的键时,这个对象将被强制转换为它包装过的symbol值:
var sym = Symbol("foo");
var obj = {[sym]: 1};
obj[sym];            // 1
obj[Object(sym)];    // 还是1

迭代器

遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

基本使用

  1. ES6 创造了一种新的遍历命令 for...of 循环Iterator 接口主要供 for...of 消费

  2. 原生具备 iterator 接口的数据(可用 for of 遍历)

    a) Array

    b) Arguments

    c) Set

    d) Map

    e) String

    f) TypedArray

    g) NodeList

  3. 工作原理

    a) 创建一个指针对象,指向当前数据结构的起始位置

    b) 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员

    c) 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员

    d) 每调用 next 方法返回一个包含 value 和 done 属性的对象

    以Array为例,Iterator保存在Array.prototype.Symbol(Symbol.iterator),也就是实例的[].__proto__.Symbol(Symbol.iterator),他对应的值是一个对象

    //声明一个数组
    const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
    
    //使用 for...of 遍历数组
    for(let v of xiyou){
       console.log(v); //唐僧  孙悟空  猪八戒  沙僧 
    }
    for(let v in xiyou){
       console.log(v); //0 1 2 3
    }
    
    let iterator = xiyou[Symbol.iterator]();
    
    //调用对象的next方法
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    /*
    {value: "唐僧", done: false}     // 返回值是一个对象,包含value和done表示是否结束迭代
    {value: "孙悟空", done: false}
    {value: "猪八戒", done: false}
    {value: "沙僧", done: false}
    {value: undefined, done: true}    // 遍历结束后设置done=true
    
    */
    

    注: 需要自定义遍历数据的时候,要想到迭代器。

自定义迭代器

//声明一个对象
const banji = {
    name: "终极一班",
    stus: [
        'xiaoming',
        'xiaoning',
        'xiaotian',
        'knight'
    ],
    [Symbol.iterator]() {
        //索引变量
        let index = 0;
        //
        let _this = this;
        return {
            next: function () {
                if (index < _this.stus.length) {
                    const result = { value: _this.stus[index], done: false };
                    //下标自增
                    index++;
                    //返回结果
                    return result;
                }else{
                    return {value: undefined, done: true};
                }
            }
        };
    }
}

//遍历这个对象 
for (let v of banji) {
    console.log(v);   //如果不自定义迭代器,此处会报错,for...of只能用于有迭代器的对象
                      //自定义了迭代器,依次输出'xiaoming','xiaoning','xiaotian','knight'
}

生成器

生成器是一个特殊函数,是ES6异步编程解决方案, 之前我们异步编程使用的是回调函数,但容易形成回调地狱

生成器函数的基本使用

  • gen函数可以使用yield进行分割,

    • yield是ES6的新关键字,使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。
    • yield关键字实际返回一个IteratorResult(迭代器)对象,它有两个属性,value和done,分别代表返回值和是否完成。
    • yield无法单独工作,需要配合generator(生成器)的其他函数,如next,懒汉式操作,展现强大的主动控制特性。
//生成器函数与就是在普通函数声明前面加入了*
function * gen(){
    // ====函数第1部分====
    yield '一只没有耳朵';  //yield 函数代码的分隔符
    // ====函数第2部分====
    yield '一只没有尾部';
    // ====函数第3部分====
    yield '真奇怪';
    // ====函数第4部分====
}

//将函数的结果赋值给变量,函数不会立刻运行,变量的值是一个迭代器。
//函数运行,当且仅当变量执行了next(),这个时候变量的值还是迭代器,返回值要另行接受
let iterator = gen();
console.log(iterator.next()); //{value: "一只没有耳朵", done: false}
console.log(iterator.next()); //{value: "一只没有尾部", done: false}
console.log(iterator.next()); //{value: "真奇怪", done: false}
console.log(iterator.next()); //{value: "undefined", done: true}

//遍历
for(let v of gen()){
    console.log(v); //一只没有耳朵  一只没有尾部 真奇怪 (返回yield后的表达式)
}

生成器函数的参数

function * gen(arg){
    console.log(arg);
    let one = yield 111;
    console.log(one);
    let two = yield 222;
    console.log(two);
    let three = yield 333;
    console.log(three);
}

//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next()); //AA {value: "111", done: false}
//next方法可以传入实参
console.log(iterator.next('BBB')); //BB {value: "222", done: false}
console.log(iterator.next('CCC')); //CC {value: "333", done: false}
console.log(iterator.next('DDD')); //DD {value: undefined, done: true}

实例

//需求1
// 1s 后控制台输出 111  2s后输出 222  3s后输出 333 
// 回调地狱
setTimeout(() => {
    console.log(111);
    setTimeout(() => {
        console.log(222);
        setTimeout(() => {
            console.log(333);
        }, 3000);
    }, 2000);
}, 1000);

function one(){
    setTimeout(()=>{
        console.log(111);
        iterator.next();
    },1000)
}

function two(){
    setTimeout(()=>{
        console.log(222);
        iterator.next();
    },2000)
}

function three(){
    setTimeout(()=>{
        console.log(333);
        iterator.next();
    },3000)
}

function * gen(){
    yield one();
    yield two();
    yield three();
}

//调用生成器函数
let iterator = gen();
iterator.next();
//需求2
//变成每秒输出一个前一个数+5的数
function one(){
    setTimeout(()=>{
        let data=1;
        iterator.next(data+5);
    },1000)
}

function two(){
    setTimeout(()=>{
        let data=2;
        iterator.next(data+5);
    },2000)
}

function three(){
    setTimeout(()=>{
        let data=3;
        iterator.next(data+5);
    },3000)
}

function * gen(){
    let o=yield one();
    console.log(o); //一秒后输出6
    let t=yield two();
    console.log(t);//一秒后输出7
    let e=yield three();
    console.log(e);//一秒后输出8
}

//调用生成器函数
let iterator = gen();//若一开始要传参要从这里传
iterator.next();

生成器与Promise结合

  • 生成器可以与Promise结合大大简化代码
  • yield是参考python的语法,在前端中用处不大,在后端中,就显得比较重要了,因为其优越的可控性,可是极大的提升线程的效率

原来:

try{
  const ninjas = syncGetJSON('data/ninja.json');
  const missions = syncGetJSON(ninjas[0].missionsUrl);
  const missionDetails = syncGetJSON(missions[0].detailsUrl);
  //Study the mission description
} catch (e) {
  //Oh no,we were not able to get the mission details
}

尽管这段代码对于简化错误处理很方便,但UI被阻塞了。

用户不希望看到这个结果。则可以使用生成器和promise结合。从生成器中让渡后会挂起执行而不会发生阻塞。而且仅需调用生成器迭代器的next方法就可以唤醒生成器并继续执行。而promise在未来触发某种条件的情况下让我们得到它事先允诺的值,而且当错误发生后也会执行相应的回调函数。

结合:

  • 把异步任务放入一个生成器中,然后执行生成器函数。
  • 所以生成器执行的时候,我们会将执行权让渡给生成器,从而不会导致阻塞。
  • 过一会,当承若被兑现,可继续通过迭代器的next函数执行生成器。只要有需要就可以重复这个过程
console.log('-------------将promise和生成器结合---------');
//返回异步结果的函数在等待异步结果返回时应当能够暂停,注意function*,使用生成器
async(function* () {
  try{
    //对每个任务均执行yield
    const ninjas = yield getJSON('data/ninjas.json');
    const missions = yield getJSON(ninjas[0].missionsUrl);
    const missionDescription = yield getJSON(missions[0].detailsUrl);
    //Study the mission description
  } catch (e) {
    //依旧可以使用标准的语言结构,诸如try-catch语句或循环语句
    //Oh no,we were not able to get the mission details
  }
});

//定义一个辅助函数,用于对我们定义的生成器执行操作
function async(generator) {
  //创建 一个迭代器,进而我们可以控制生成器
  var iterator = generator();

  //定义函数handle,用于对生成器产生的每个值进行处理。
  function handle(iteratorResult) {
    //当生成器没有更多结果返回时停止执行。
    if (iteratorResult.done) {
      return;
    }

    const iteratorValue = iteratorResult.value;
    //如果生成的值是一个promise,则对其注册成功和失败的回调。这是异步处理的部分。如果promise成功返回,则恢复生成器的执行并传入promise的返回结果。如果遇到错误,则生成器抛出异常。
    if (iteratorValue instanceof Promise) {
      iteratorValue.then(res =>{
        handle(iterator.next(res));
      }).catch(err =>{
        iterator.throw(err);
      });
    }
  }
 
  //重启生成器的执行
  try{
    handle(iterator.next());
  } catch (e) {
    iterator.throw(e);
  }
}

async函数内,我们声明了一个处理函数用于处理从生成器中返回的值——迭代器的一次“迭代”。如果生成器的结果是一个被成功兑现的承若,我们就是用迭代器的next方法把承诺的值返回给生成器并恢复执行。如果出现错误,承若被违背,我们就使用迭代器的throw方法抛出一个异常。直到生成器完成前,一直重复这几个操作。

注意:

这只是一个粗略的草稿,一个最小化的代码应该把生成器和promise结合在一起。不推荐生产环境下出现这些代码。

现在仔细看看这个生成器,在第一次调用迭代器的next方法后,生成器执行第一次getJSON(‘data/ninjas.json’)调用。此次调用创建了一个promise,该promise会包含需要的信息。但是这个值是异步获取的,所以我们完全不知道浏览器会花多少时间来获取它。但我们明白一件事,我们不希望在等待中阻塞应用的执行。所以对于这个原因,在执行的这一刻,生成器让渡了控制权,生成器暂停,并把控制流还给了回调函数的执行。由于让渡的值是一个promise对象getJSON,在这个回调函数中,通过使用promise的then和catch方法,我们注册了一个success和一个error回调函数,从而继续了函数的执行。当浏览器接收到了响应(可能是成功的响应,也可能是失败的响应),promise的两个回调函数之一则被调用了。如果promise被成功解决,则会执行success回调函数,随之而来的是迭代器next方法的调用,用于向生成器请求新的值,从而向生成器请求新值,从而生成器从挂起状态恢复,并把得到的值回传给回调函数。这意味着,程序又重新进入到生成器函数体内,当第一次执行yield表达式后,得到的值变成从服务器端获取的信息。

下一行代码的生成器函数中,我们使用获取到的数据ninjas[0].missionsUrl来发起新的geJSON请求,从而创建一个新的promise对象,最后返回最新的数据。我们依然不知道这个异步任务会执行多久,所以我们再一次让渡了这次执行,并重复这个过程。只要生成器中有异步任务,这个过程就会重新执行一次。

总结:

这个例子有一点不同,但它结合了许多知识点:

  • 函数是第一类对象——我们向async函数了一个参数,该参数也是函数。
  • 生成器函数——用它的特性来挂起和恢复执行。
  • pomise——帮我们处理异步代码。
  • 回调函数——在promise对象上注册成功和失败的回调函数。
  • 箭头函数——箭头函数的简洁适合用在回调函数上。
  • 闭包——在我们控制生成器的过程中,迭代器在async函数内被创建,随之我们在promise的回调函数内通过闭包来获取该迭代器。
getJSON("data/ninjas.json", (err, ninjas) =>{
  if (err) {
    console.log('Error fetchig ninjas', err);
    return;
  }

  getJSON(ninjas[0].missingsUrl, (err, missions) =>{
    if (err) {
      console.log("Error locating ninja missions", err);
      return;
    }
    console.log(missions);
  })
});

不同于把错误处理和流程中控制混合在一起,我们使用类似以下写法结束了代码的混乱:

async(function* () {
  try{
    //对每个任务均执行yield
    const ninjas = yield getJSON('data/ninjas.json');
    const missions = yield getJSON(ninjas[0].missionsUrl);
    //Study the mission description
  } catch (e) {
    //依旧可以使用标准的语言结构,诸如try-catch语句或循环语句
    //Oh no,we were not able to get the mission details
  }
});

最终结果结合了同步代码和异步代码的优点。有了同步代码,我们能更容易地理解、使用标准控制流以及异常处理机制、try-catch语句能力。而对于异步代码来说,我们有着天生的阻塞:当等待长时间运行的异步任务时,应用的执行不会被阻塞。

set

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符...』和『for…of…』进行遍历

集合的属性和方法:

  1. size 返回集合的元素个数
  2. add 增加一个新元素,返回当前集合
  3. delete 删除元素,返回 boolean 值
  4. has 检测集合中是否包含某个元素,返回 boolean 值
  5. clear 清空集合,返回 undefined
let s = new Set()
console.log(s,typeof s);            // Set(0) {} object

let s2 = new Set([1,2,3,4,5,3,1])
console.log(s2,typeof s2);          // Set(5) { 1, 2, 3, 4, 5 } object  //自动去重

for(let v of s){
   console.log(v);  //1 2 3 4 5
}
s2.add(666);
console.log(s2); //Set(6) { 1, 2, 3, 4, 5, 666 }
s2.delete(3);
console.log(s2);  //Set(5) { 1, 2, 4, 5, 666 }
console.log(s2.has(9)) //false
console.log(s2.has(5)) //true
console.log(s2,s2.size); //Set(5) { 1, 2, 4, 5, 666 }  5
s2.clear();
console.log(s2,s2.size); //Set(0) { }  0
  • 数组去重
//1. 数组去重
let arr = [1,2,3,4,5,4,3,2,1];
let result = [...new Set(arr)];
console.log(result);  // 1 2 3 4 5 

let arr = [1,2,'3',4,'abc',{a:123},{a:123},{a:'123'},1];
let result = [...new Set(arr)];
console.log(result); //没有达到去重效果
  • 求交
//2. 交集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let result = [...new Set(arr)].filter(item => {
    let s2 = new Set(arr2);// 4 5 6
    if(s2.has(item)){
        return true;
    }else{
        return false;
    }
});
//简化写法
let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));
console.log(result);  //4 5
  • 求并
//3. 并集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let union = [...new Set([...arr, ...arr2])];
console.log(union); //1 2 3 4 5 6
  • 求差
//4. 差集
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff);  //1 2 3

Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。

Map 的属性和方法:

  1. size 返回 Map 的元素个数
  2. set 增加一个新元素,返回当前 Map
  3. get 返回键名对象的键值
  4. has 检测 Map 中是否包含某个元素,返回 boolean 值
  5. clear 清空集合,返回 undefined
//声明 Map
let m = new Map();

//添加元素
m.set('name','尚硅谷');
m.set('change', function(){
    console.log("我们可以改变你!!");
});
let key = {
    school : 'ATGUIGU'
};
m.set(key, ['北京','上海','深圳']);

//size
console.log(m.size);

//删除
m.delete('name');

//获取
console.log(m.get('change'));
console.log(m.get(key));

//清空
m.clear();

//遍历
for(let v of m){
    console.log(v);
}

console.log(m);

class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

基本知识

  1. class 声明类
  2. constructor 定义构造函数初始化
  3. extends 继承父类
  4. super 调用父级构造方法
  5. static 定义静态方法和属性
  6. 父类方法可以重写
//es5写法
    function Phone(brand, price){
        this.brand = brand;
        this.price = price;
    }
    
    //添加方法
    Phone.prototype.call = function(){
        console.log("我可以打电话!!");
    }
    
    //实例化对象
    let Huawei = new Phone('华为', 5999);
    Huawei.call();  //我可以打电话!!
    console.log(Huawei);

//class
    class Phone{
        //构造方法 名字不能修改
        constructor(brand, price){
            this.brand = brand;
            this.price = price;
        }
    
        //添加方法必须使用该语法, 不能使用 ES5 的对象完整形式call:function(){}
        //该方法也是添加在原型上
        call(){
            console.log("我可以打电话!!");
        }
    }
    
    let onePlus = new Phone("1+", 1999);
    
    console.log(onePlus);

类的静态成员

//es5写法
    function Phone(){
    
    }
    
    //以下方法添加属性,生成的是静态成员,属于函数对象,不能被实例对象访问
    Phone.name = '手机';
    Phone.change = function(){
        console.log("我可以改变世界");
    }
    Phone.prototype.size = '5.5inch';
    
    let nokia = new Phone();
    
    console.log(nokia.name); //undefined
    nokia.change();   //报错 change不是函数
    console.log(nokia.size); //5.5inch


//类写法
    class Phone111{
        //静态属性属于类而不属于实例
        static name = '手机';
        static change(){
            console.log("我可以改变世界");
        }
    }
    
    let nokia111 = new Phone();
    console.log(nokia111.name); //undefined
    console.log(Phone111.name); //手机

对象继承

//es5
    //手机
    function Phone(brand, price){
        this.brand = brand;
        this.price = price;
    }

    Phone.prototype.call = function(){
        console.log("我可以打电话");
    }

    //智能手机
    function SmartPhone(brand, price, color, size){
        Phone.call(this, brand, price);
        this.color = color;
        this.size = size;
    }

    //设置子级构造函数的原型
    SmartPhone.prototype = new Phone;
    SmartPhone.prototype.constructor = SmartPhone;

    //声明子类的方法
    SmartPhone.prototype.photo = function(){
        console.log("我可以拍照")
    }

    SmartPhone.prototype.playGame = function(){
        console.log("我可以玩游戏");
    }

    const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

    console.log(chuizi);
//类
    class Phone{
        //构造方法
        constructor(brand, price){
            this.brand = brand;
            this.price = price;
        }
        //父类的成员属性
        call(){
            console.log("我可以打电话!!");
        }
    }

    class SmartPhone extends Phone {
        //构造方法
        constructor(brand, price, color, size){
            super(brand, price);// Phone.call(this, brand, price)
            this.color = color;
            this.size = size;
        }

        photo(){
            console.log("拍照");
        }

        playGame(){
            console.log("玩游戏");
        }
        //重写父类方法,可以重写,但是不能直接去调用父类方法
        call(){
            console.log('我可以进行视频通话');
        }
    }

    const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
    // console.log(xiaomi);
    xiaomi.call();  
    xiaomi.photo();
    xiaomi.playGame();

get和set

可以对属性进行方法绑定,当要获取属性的时候执行对应get方法,当要设置属性值时执行对应的set方法, 这些属性不能在构造函数的时候被构造,看起来像一个虚拟变量

// get 和 set  
      class Phone{
          get price(){
              console.log("价格属性被读取了");
              return '9999';
          }
          //必须要传参
          set price(newVal){
              console.log('价格属性被修改了');
          }
      }

      //实例化对象
      let s = new Phone();

      console.log(s.price);//9999
      s.price = 'free';

数值扩展

  1. Number.EPSILON 是 JavaScript 表示的最小精度

    EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16

    两个数差值小于这个数则认为这两个数相等

    function equal(a, b){
        if(Math.abs(a-b) < Number.EPSILON){
            return true;
        }else{
            return false;
        }
    }
    console.log(0.1 + 0.2 === 0.3); //false
    console.log(equal(0.1 + 0.2, 0.3))  //true
    
  2. 二进制和八进制

    ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示。

  3. Number.isFinite() 用来检查一个数值是否为有限的

    console.log(Number.isFinite(100)); //true
    console.log(Number.isFinite(100/0));//false
    console.log(Number.isFinite(Infinity));//false
    
  4. Number.isNaN() 用来检查一个值是否为 NaN

    console.log(Number.isNaN(123)); //false
    
  5. Number.parseInt() 与 Number.parseFloat()

    ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变。

    console.log(Number.parseInt('5211314love'));//5211314
    console.log(Number.parseFloat('3.1415926神奇'));//3.1415926
    
  6. Number.isInteger 用来判断一个数值是否为整数

     console.log(Number.isInteger(5));
            console.log(Number.isInteger(2.5));
    
  7. Math.trunc 用于去除一个数的小数部分,返回整数部分。

    console.log(Math.trunc(3.5)); //3
    
  8. Math.sign 判断一个数到底为正数 负数 还是零

    console.log(Math.sign(100)); //1
    console.log(Math.sign(0)); //0
    console.log(Math.sign(-20000)); //-1
    

对象扩展

ES6 新增了一些 Object 对象的方法

  1. Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)

    console.log(Object.is(120, 120));// true
    console.log(Object.is(NaN, NaN));// true
    console.log(Object.is(+0,-0))// false
    console.log(Object.is([1],[1]))           // False
    console.log(Object.is({A:"1"},{A:"1"}))   // False
    console.log(Object.is({A:"1"},{A:1}))     // False
    
    console.log(NaN === NaN);// false
    console.log(+0=== -0);// true
    
  2. Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象

    const config1 = {
        host: 'localhost',
        port: 3306,
        name: 'root',
        pass: 'root',
        test: 'test'
    };
    const config2 = {
        host: 'http://atguigu.com',
        port: 33060,
        name: 'atguigu.com',
        pass: 'iloveyou',
        test2: 'test2'
    }
    console.log(Object.assign(config1, config2)); //如果重名,后面会覆盖前面
    /*
        host: 'http://atguigu.com',
        port: 33060,
        name: 'atguigu.com',
        pass: 'iloveyou',
        test: 'test'
        test2: 'test2' 
    */
    
  3. Object.setPrototypeOf 可以直接设置对象的原型

    const school = {
        name: '尚硅谷'
    }
    const cities = {
        xiaoqu: ['北京','上海','深圳']
    }
    Object.setPrototypeOf(school, cities);
    console.log(Object.getPrototypeOf(school)); //xiaoqu: (3) ['北京', '上海', '深圳']
    console.log(school);//name: "尚硅谷"[[Prototype]]: Objectxiaoqu: (3) ['北京', '上海', '深圳']
    

es6模块化

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

基本知识

  1. 模块化的好处

    模块化的优势有以下几点:

    1. 防止命名冲突
    2. 代码复用
    3. 高维护性
  2. 模块化规范产品

    ES6 之前的模块化规范有:

    1. CommonJS => NodeJS、Browserify
    2. AM => requireJS
    3. CMD => seaJS19
  3. ES6 模块化语法

    模块功能主要由两个命令构成:export 和 import。

    ⚫ export 命令用于规定模块的对外接口

    ⚫ import 命令用于输入其他模块提供的功能

mod.js文件

export let name = "Hi"
export let nowis=()=> Date.now()

index.html

<script type="module">              // 要写type
  import * as m1 from "../mod.js"   // 导入为m1
  console.log(m1.nowis());          // 调用方法
</script>

暴露数据的方法

  • 分别暴露
export let name = "Hi"
export let nowis=()=> Date.now()
  • 统一暴露
let name = "Hi"
let nowis=()=> Date.now()
export {name,nowis}
  • 默认暴露
export default{
  name : "Hi",
  nowis: function(){return Date.now()}
}

默认暴露需要修改html

<script type="module">
  import * as m1 from "../mod.js"
  console.log(m1.default.nowis())    // 多个default
</script>

浏览器引用模块的方法

  • 通用引用方法
<script type="module">
  import * as m1 from "../mod.js"
</script>
  • 解构的方式引用
<script type="module">
  import {name,nowis} from "../mod.js"
</script>

如果两个模块有同名函数,解构后会出现变量名重复的问题,可以使用as进行变量名的替换,不影响解构

<script type="module">
  import {name,nowis} from "../mod1.js" // 有name nowis
  import {name as name2,lstis} from "../mod2.js" // 有name lstis
</script>

对于默认暴露

<script type="module">
  import {default as m1} from "../mod.js"
</script>
  • 针对默认暴露的简便模式
<script type="module">
  import m1 from "../mod1.js" // 因为默认暴露只有一个参数,可以这么做
</script>

文件统一引用

可以把所有模块引用都放在一起,然后直接引用这个文件

app.js

import * as m1 from "../mod1.js"
import * as m2 from "../mod2.js"
import * as m3 from "../mod3.js"

index.html

<script type="module" src="./app.js">

将ES6代码转化为ES5代码

在项目中考虑到项目兼容性,需要将项目的JS进行转化,需要的工具有

Babel: 将ES6代码转化为ES5代码,但是是CommonJS规范 browserify: 打包工具,把CommonJS规范的JS转化为浏览器可读的,这里是简易打包,项目可能需要webpack

npm install babel-cli babel-preset-env browserify -D  // 软件分别是 命令行 转化工具 打包工具 -D是开发依赖
npx babel ./src -d ./dist/js --preset=babel-preset-env  // -d前是原js目录 -d后是输出目录 最后是使用预设  npx是因为局部安装Babel全局安装则不需要
npx browserify dist/js/mod.js -o ./bundle.js          // 打包

浏览器最后引用./bundle.js

将npm包引入浏览器,jQuery为例

bash

npm install jquery

/src/js/app.js

import $ from "jquery";
$("body").css("background","pink");

bash

//每次修改都需要重新打包
npx babel ./src/js -d ./dist/js --preset=babel-preset-env
npx browserify ./dist/js/app.js -o ./dist/bundle.js

第3章 ES7新特性

Array.prototype.includes

Includes 方法用来检测数组中是否包含某个元素,返回布尔类型值

indexof方法返回的是数组下标,不存在则返回-1

const mingzhu = ['西游记','红楼梦','三国演义','水浒传'];

//判断
console.log(mingzhu.includes('西游记')); //true
console.log(mingzhu.includes('金瓶梅')); //false

指数操作符

在 ES7 中引入指数运算符「**」,用来实现幂运算,功能与 Math.pow 结果相同

// **
console.log(2 ** 10);// 1024
console.log(Math.pow(2, 10)); //1024

第 4章 ES8新特性

async 和 await

async 和 await 两种语法结合可以让异步代码像同步代码一样

async 函数

  1. async 函数的返回值为 promise 对象,
  2. promise 对象的结果由 async 函数执行的返回值决定
  3. 不能在全局直接声明async函数,实在不行可以写成(async()=>{})()的立即执行函数形式

await 表达式

  1. await 必须写在 async 函数中
  2. await 右侧的表达式一般为 promise 对象
  3. await 返回的是 promise 成功的值
  4. await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

对象方法的扩展

Object.keys()

获取对象所有的键名,返回值是数组

let lower = {
  "A":"a",
  "B":"b",
  "C":"c",
  "D":"d",
}

Object.keys(lower).forEach(d=>console.log(d))  // A B C D

Object.values

返回一个给定对象的所有可枚举属性值的数组

let lower = {
  "A":"a",
  "B":"b",
  "C":"c",
  "D":"d",
}

console.log(Object.values(lower))  // [ 'a', 'b', 'c', 'd' ]

Object.entries

返回一个给定对象自身可遍历属性 [key,value] 的数组

let lower = {
  "A":"a",
  "B":"b",
  "C":"c",
  "D":"d",
}
console.log(Object.entries(lower))
// [ [ 'A', 'a' ], [ 'B', 'b' ], [ 'C', 'c' ], [ 'D', 'd' ] ]
let m = new Map(Object.entries(lower))
console.log(m)
// Map(4) { 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd' }

Object.getOwnPropertyDescriptors

获取对象属性的描述对象,这个对象的每一个属性都对应描述中的一个对象,包括值,可写,可删除,可枚举,方便我们进行深层次的对象克隆

let lower = {
    "name": "a",
    "age": 12,
    "sex": "F",
    "note": "good",
}

console.log(Object.getOwnPropertyDescriptors(lower))
/**
{
    name: { value: 'a', writable: true, enumerable: true, configurable: true },
    age: { value: 12, writable: true, enumerable: true, configurable: true },
    sex: { value: 'F', writable: true, enumerable: true, configurable: true },
    note: {
        value: 'good',
        writable: true,
        enumerable: true,
        configurable: true
    }
}
*/

第5 章ES9新特性

REST参数与Spread扩展

在ES6中只有对数组的REST/Spread, 在ES9中支持对对象进行REST/Spread

REST

function cnnt({host,port,...args}){
    console.log(host); //127.0.0.1
    console.log(port); //80
    console.log(args); // {pwd: 123,  type: "A" }    
}

cnnt({
    host: "127.0.0.1",
    port: 80,
    pwd: 123,
    type: "A"
})

Spread

let Obj1={
    "name":"Liu"
}

let Obj2={
    "Sex":"M"
}

let Obj3={
    "Age":20
}

let res = {...Obj1,...Obj2,...Obj3}
console.log(res)
//对象的合并 { name: 'Liu', Sex: 'M', Age: 20 }

正则扩展

命名捕获分组

我们可以对政策匹配到的分组$1,$2...赋名,方面我们的使用

在之前

let str = '<iframe class="notranslate">Inner</iframe>';
// 想要获取class和标签内容要写两个()用来分组
let reg = /<iframe class="(.*)".*>(.*)</iframe>/;
let res = reg.exec(str);
console.log(res);
/*
  [
    '<iframe class="notranslate">Inner</iframe>',
    'notranslate',
    'Inner',
    index: 8,
    input: '<iframe class="notranslate">Inner</iframe>',
    groups: undefined       // 这里是undefined
    ]
*/
console.log(res[1]); //notranslate
console.log(res[2]); //Inner

也就是res[0]是匹配结构,res[1]是第一个分组,res[2]是第二个分组

es9:

将需要赋值的分组括号由(条件)改为(?<变量名>条件),使用捕获分组会存储着groups中

let str = '<iframe class="notranslate">Inner</iframe>';
// 想要获取class和标签内容要写两个()用来分组
let reg = /class="(?<cls>.*)".*>(?<Inn>.*)</iframe/;
let res = reg.exec(str);
console.log(res);
/*
  [
    'class="notranslate">Inner</iframe',      // [0-2]都没有变
    'notranslate',
    'Inner',
    index: 8,
    input: '<iframe class="notranslate">Inner</iframe>',
    groups: [Object: null prototype] { cls: 'notranslate', Inn: 'Inner' } // groups变了
  ]
*/
//更好的获取数据,即使正则结构改变了
console.log(res.groups.cls); //notranslate
console.log(res.groups.Inn); //Inner  

反向断言

正向断言是匹配某个串要求不仅要满足串的条件,原串后面的内容也要满足指定条件

例如: 对于字符串’aaa111bbb222’我想要最后一个连续的字母,那么应该匹配的是[a-zA-Z]+判断后一个内容是否满足要求(在正则条件后加上(?=后面的内容)来实现判断)

//如何取出bbb?
let str = 'aaa111bbb222';
let reg = /[a-zA-Z]+(?=2)/
let res = reg.exec(str);
console.log(res)
// [ 'bbb', index: 6, input: 'aaa111bbb222', groups: undefined ]

反向断言是要匹配一个串,满足串前面的内容是指定条件.实现方法是**(?<=前面的内容)条件**,

例如我想匹配第一个出现的数字串

let str = 'aaacc111bbb222';
let reg = /(?<=c)[0-9]+/
let res = reg.exec(str);
console.log(res)
// [ '111', index: 5, input: 'aaacc111bbb222', groups: undefined ]

dotAll模式

在正则中.代表任意除\n外的任意内容,在提取有\n的内容的时候就显得不方便,只需要设置/条件/s即可,就是在最后加入s属性

.*用来匹配任意字符串的时候经常出现贪婪匹配,可以设置.*?禁止贪婪(不加?会直接匹配到阿甘正传后面的)

//dot  .  元字符  除换行符以外的任意单个字符
let str = `
<ul>
    <li>
        <a>肖生克的救赎</a>
        <p>上映日期: 1994-09-10</p>
    </li>
    <li>
        <a>阿甘正传</a>
        <p>上映日期: 1994-07-06</p>
    </li>
</ul>`;
//声明正则
//原来的需要\s+匹配/n
const reg = /<li>\s+<a>(.*?)</a>\s+<p>(.*?)</p>/;
//现在只需要.*?一下子匹配多个/n
const reg = /<li>.*?<a>(.*?)</a>.*?<p>(.*?)</p>/gs; //此处加入s
//执行匹配
// const result = reg.exec(str);
let result;
let data = [];
while(result = reg.exec(str)){
    data.push({title: result[1], time: result[2]});
}
//输出结果
console.log(data);
/*
[
{title:肖生克的救赎 ,time:上映日期: 1994-09-10},
{title:阿甘正传 ,time:上映日期: 1994-07-06}
]
*/

第 6 章 ES 10 新特性

Object.fromEntries

  • Object.entries()可以获取对象所有的键值对,返回值是数组,元素是一个数组,包含键和值, 可以用来构造Map(对象—>数组)
  • Object.fromEntries()可以将一个Map/二维数组转化为对象的形式(数组—>对象)
//二维数组
const result = Object.fromEntries([
    ['name','嘿嘿嘿'],
    ['xueke', 'Java,大数据,前端,云计算']
]);
console.log(result ); //{name:'嘿嘿嘿'  xueke: 'Java,大数据,前端,云计算'}

//Map
const m = new Map();
m.set('name','Doing');
const result = Object.fromEntries(m);
console.log(result ); //{name:'Doing'}

//Object.entries ES8
const arr = Object.entries({
    name: "哈哈哈"
})
console.log(arr); //[name","哈哈哈"]

trimStart 和 trimEnd

在ES5中字符串有trim方法用来清除字符串两边的空白, 现在有start/end指定清除哪一边了空白

let str = '   iloveyou   ';
console.log(str);  //   iloveyou   
console.log(str.trimStart()); //iloveyou   
console.log(str.trimEnd());  //   iloveyou   

Array.prototype.flat 与 flatMap

flat译为平面,也就是可以将数组内部数组的元素维度降低,例如

//flat 平
//将多维数组转化为低位数组
const arr = [1,2,3,4,[5,6]];
console.log(arr.flat()); //[1,2,3,4,5,6]

const arr = [1,2,3,4,[5,6,[7,8,9]]];
console.log(arr.flat()); //[1,2,3,4,5,6,Array(3)]
//参数为深度 是一个数字
console.log(arr.flat(2)); //[1,2,3,4,5,6,7,8,9]

//flatMap
const arr = [1,2,3,4];
const result = arr.map(item => item * 10);
console.log(result); //[10,20,30,40]

const result = arr.map(item => [item * 10]);
console.log(result); //[[10],[20],[30],[40]]

const result = arr.flatMap(item => item * 10);
console.log(result); //[10,20,30,40]

const result = arr.flatMap(item => [item * 10]);
console.log(result); //[10,20,30,40]

Symbol.prototype.description

可以用Symbol.description方法获取Symbol的注释

//创建 Symbol
let s = Symbol('doing');
console.log(s.description);//doing

第 7 章 ES 11 新特性

类的私有属性

在传统OOP语言中的对象可以是私有的,ES11引入了这个特性, 定义私有属性只要在前面加入#就可以了

 class Person{
    //公有属性
    name;
    //私有属性
    #age;
    #weight;
    //构造方法
    constructor(name, age, weight){
        this.name = name;
        this.#age = age;
        this.#weight = weight;
    }

    intro(){
        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.intro(); //'晓红', 18, '45kg'

Promise.allSettled

  • Promise.allSettled(), 不论他包含的Promise的结果是什么,状态都为resolved,并保存每个Promise的结果
  • Promise.all(),所有Promise均成功其才resolved,成功则保存每个Promise的结果,失败则保存失败的Promise结果
  • 他们都用来做批量异步任务, all一般是用来做前一个成功后一个运行,allSettled一般是要全部运行, 之间没有关联, 要求保存结果
//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve('商品数据 - 1');
    },1000)
});

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

//调用 allsettled 方法
// const result = Promise.allSettled([p1, p2]);

// const res = Promise.all([p1, p2]);

console.log(res);

String.prototype.matchAll

  • .matchAll获取批量匹配的所有结果。必须加/g否则报错
  • ES11之前如果正则表达式有/g标志,需要多次调用.exec()才可以获取批量匹配的所有结果。如果没有匹配的结果,.exec()就会返回null。如果正则表达式没有/g标志,.exec()总是返回第一次匹配的结果(我的电脑不加/g直接网络崩溃了)。
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);

  //.matchAll()为所有匹配的匹配对象返回一个迭代器。所以可以使用for of遍历
  // for(let v of result){
  //     console.log(v);
  // }

  const arr = [...result];

  console.log(arr);

可选链操作符

// ?.
function main(config){
    //原本写法,需多次判断参数是否传递,否则当参数未传递时会报错
    // const dbHost = config && config.db && config.db.host; 
    //const dbHost = config.db.host; //如果config没传或者db没传会直接报错
    const dbHost = config?.db?.host; //如果没传会返回undefined

    console.log(dbHost);
}

main({
    db: {
        host:'192.168.1.100',
        username: 'root'
    },
    cache: {
        host: '192.168.1.200',
        username:'admin'
    }
})

动态 import 导入

不在文件开头直接导入相关文件,而是在需要时直接使用import()函数实现按需导入

import()的调用返回的是一个promise对象,当正确加载就resolve, resolve的值是模块暴露的对象

  • 静态引入
// 文件头
import * as md1 from "./demo.js"
  • 动态引入
if(something){
  import("./demo.js").then((d)=>{
    d.xx();     // xx就是暴露的对象
  })
}

BigInt

比int类范围大,用于大数运算

  • 字面值写法: 123n
  • Int转BigInt:BigInt(123)
  • BigInt不能和int进行运算,必须把int转为BigInt
//大整形
let n = 521n;
console.log(n, typeof(n)); //521n "bigint"

//函数
let n = 123;
console.log(BigInt(n)); //123n
console.log(BigInt(1.2));//报错

//大数值运算
let max = Number.MAX_SAFE_INTEGER;
console.log(max); //9007199254740991
console.log(max + 1);//9007199254740992
console.log(max + 2);//9007199254740992
console.log(max + 3);//9007199254740992

console.log(BigInt(max)) //9007199254740991
console.log(BigInt(max)+1) //报错
console.log(BigInt(max) + BigInt(1)) //9007199254740992
console.log(BigInt(max) + BigInt(2)) //9007199254740993

globalThis 对象

浏览器的全局对象是window, 但是NodeJS等没有window, 在新的NodeJS/浏览器中都可以使用golbalThis指向全局对象, 使得在浏览器/NodeJS中编程得到了一个统一

console.log(golbalThis) //window;

第 8 章ES12新特性

转载自: 前端虾米公社

replaceAll

看到replaceAll这个词,相比很容易联想到replace。在JavaScript中,replace方法只能是替换字符串中匹配到的第一个实例字符,而不能进行全局多项匹配替换,唯一的办法是通过正则表达式进行相关规则匹配替换而replaceAll则是返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。

let string = 'I like 前端,I like Doing'
//使用replace
let replaceStr = string.replace('like','love')
console.log(replaceStr)  // 'I love 前端,I like Doing'
//replace使用正则匹配所有
console.log(string.replace(/like/g,'love')) // 'I love 前端,I love Doing'
//使用replaceAll
let replaceAllStr = string.replaceAll('like','love')
console.log(replaceAllStr) // 'I love 前端,I love Doing'

需要注意的是,replaceAll在使用正则表达式的时候,如果非全局匹配(/g),则replaceAll()会抛出一个异常

let string = 'I like 前端,I like Doing'
console.log(string.replaceAll(/like/,'love')) //TypeError

Promise.any

  • Promise.any返回第一个resolve的结果状态,如果所有的promise均reject,则抛出异常表示所有请求失败
  • Promise.race 返回最先执行完毕的promise结果,并不在乎其成功或者失败
Promise.any([
  new Promise((resolve, reject) => setTimeout(reject, 500, '哎呀,我被拒绝了')),
  new Promise((resolve, reject) => setTimeout(resolve, 1000, '哎呀,她接受我了')),
  new Promise((resolve, reject) => setTimeout(resolve, 2000, '哎呀,她也接受我了')),
])
.then(value => console.log(`输出结果: ${value}`))
.catch (err => console.log(err))

//输出
//输出结果:哎呀,她接受我了

再来看下另一种情况

Promise.any([
  Promise.reject('Error 1'),
  Promise.reject('Error 2'),
  Promise.reject('Error 3')
])
.then(value => console.log(`请求结果: ${value}`))
.catch (err => console.log(err))

//输出
AggregateError: All promises were rejected

WeakRefs

使用WeakRefs的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)

当我们通过(const、let、var)创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在可访问。WeakRef对象包含对对象的弱引用。对对象的弱引用是不会阻止垃圾收集器GC恢复该对象的引用,则GC可以在任何时候删除它。

WeakRefs在很多情况下都很有用,比如使用Map对象来实现具有很多需要大量内存的键值缓存,在这种情况下最方便的就是尽快释放键值对占用的内存。

目前,可以通过WeakMap()或者WeakSet()来使用WeakRefs

举个栗子

我想要跟踪特定的对象调用某一特定方法的次数,超过1000条则做对应提示

let map = new Map()
function doSomething(obj){
  ...
}
function useObject(obj){
  doSomething(obj)
  
  let called = map.get(obj) || 0
  called ++ 
  
  if(called>1000){
     console.log('当前调用次数已经超过1000次了,over')
  }
  
  map.set(obj, called)
}

如上虽然可以实现我们的功能,但是会发生内存溢出,因为传递给doSomething函数的每个对象都永久保存在map中,并且不会被GC回收,因此我们可以使用WeakMap

let wmap = new WeakMap()
function doSomething(obj){
  ...
}
function useObject(obj){
  doSomething(obj)
  
  let called = wmap.get(obj) || 0
  
  called ++
  
  if(called>1000){
     console.log('当前调用次数已经超过1000次了,over')
  }
  
  wmap.set(obj, called)
}

因为是弱引用,所以WeakMap、WeakSet的键值对是不可枚举的

WeakSet和WeakMap相似,但是每个对象在WeakSet中的每个对象只可能出现一次,WeakSet中所有对象都是唯一的

let ws = new WeakSet()
let foo = {}
let bar = {}

ws.add(foo)
ws.add(bar)

ws.has(foo) //true
ws.has(bar) //true

ws.delete(foo) //删除foo对象
ws.has(foo) //false 已删除
ws.has(bar) //仍存在

WeakSet与Set相比有以下两个区别

WeakSet只能是对象集合,而不能是任何类型的任意值 WeakSet弱引用,集合中对象引用为弱引用,如果没有其他对WeakSet对象的引用,则会被GC回收 最后,WeakRef实例有一个方法deref,返回引用的原始对象,如果原始对象被回收,则返回undefined

const cache = new Map();

const setValue =  (key, obj) => {
  cache.set(key, new WeakRef(obj));
};

const getValue = (key) => {
  const ref = cache.get(key);
  if (ref) {
    return ref.deref();
  }
};

const fibonacciCached = (number) => {
  const cached = getValue(number);
  if (cached) return cached;
  const sum = calculateFibonacci(number);
  setValue(number, sum);
  return sum;
};

对于缓存远程数据来说,这可能不是一个好主意,因为远程数据可能会不可预测地从内存中删除。在这种情况下,最好使用LRU之类的缓存。

逻辑运算符和赋值表达式

逻辑运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)和赋值表达式而JavaScript已存在的 复合赋值运算符有:

  • 操作运算符:+= -= *= /= %= **=
  • 位操作运算符:&= ^= |=
  • 按位运算符:<<= >>= >>>=

现有的的运算符,其工作方式都可以如此来理解

表达式:a op= b 等同于:a = a op b

逻辑运算符和其他的复合赋值运算符工作方式不同

表达式:a op= b 等同于:a = a op (a = b)

  • a ||= b 等价于 a = a || (a = b)
  • a &&= b 等价于 a = a && (a = b)
  • a ??= b 等价于 a = a ?? (a = b)

为什么不再是跟以前的运算公式a = a op b一样呢,而是采用a = a op (a = b)。因为后者当且仅当a的值为false的时候才计算赋值,只有在必要的时候才执行分配,而前者的表达式总是执行赋值操作

??=可用来补充/初始化缺失的属性

const pages = [
  {
    title:'主会场',
    path:'/'
  },
  {
    path:'/other'
  },
  ...
]
  
for (const page of pages){
  page.title ??= '默认标题'
}
console.table(pages)
//(index)  title           path
//0        "主会场"       "/"
//1        "默认标题"     "/other"

小结:

  • &&=: 当LHS值存在时,将RHS变量赋值给LHS
  • ||=: 当LHS值不存在时,将RHS变量赋值给LHS
  • ??=: 当LHS值为null或者undefined时,将RHS变量赋值给LHS

数字分隔符

数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性

const money = 1_000_000_000
//等价于
const money = 1000000000

const totalFee = 1000.12_34
//等价于
const totalFee = 1000.1234

该新特性同样支持在八进制数中使用

const number = 0o123_456
//等价于
const number = 0o123456

参考:blog.csdn.net/Liukairui/a…