Es6+和Promise,迭代器,生成器,异常处理

文章目录

一、ES6+

let关键字

基本用法

  • 变量未声明不能使用,否则报的错误就是变量未定义
  • 在ES6中默认是启动了严格模式的,严格模式的特征就是:变量未声明不能使用,否则报的错误就是变量未定义。
  • 在ES5中怎样开启严格模式,在代码的最开始加上:“use strict”
  • 通过let声明的变量仅在块级作用域内有效
let命令注意事项
  1. 不存在变量提升(变量不会挂到windows上
  2. 暂时性死区
    • 如果在区域中存在let命令,那么在这个区域中通过let命令所声明的变量从一开始就生成了一个封闭的作用域,只要在声明变量前使用,就会出错。
    • 所谓的“暂时性死区”指的就是,在代码块内,使用let命令声明变量之前,该变量都是不可用的。
  3. 不允许重复声明
  4. 形成块级作用域

块级作用域

有一段代码是用大括号包裹起来的,那么大括号里面就是一个块级作用域

  • 为什么需要块级作用域?
    • 第一:内层变量可能会覆盖外层变量
    • 第二: 用来计数的循环变量成为了全局变量

const命令

通过const命令声明的常量,其值是不允许被修改的。

const命令注意事项

  1. 不存在常量提升
  2. 只在声明的块级作用域内有效
  3. 暂时性死区
  4. 不允许重复声明
  5. 常量声明必须赋值

解构赋值

数组解构赋值

let arr = [1, 2, 3];
let [num1, num2, num3] = arr;
console.log(num1, num2, num3);
注意事项
  • 如果解析不成功,对应的值会为undefined.
  • 不完全解构的情况

对象解构赋值

先找到同名属性,然后再赋值给对应的变量。

let obj = {
    userName: 'ls',
    userAge: 21
};
let {
    userName: name,
    userAge: age
} = obj;
console.log(name, age)
对象解构赋值注意事项
  • 默认解构
  • 嵌套结构对象的解构

字符串的解构赋值

字符串也可以进行解构赋值,这是因为字符串被转换成了一个类似于数组的对象。

let [a, b, c, d, e, f] = 'wangcai';
console.log(a, b, c, d, e, f);
let {
    length: len
} = 'wangcai';
console.log('len=', len);

函数参数的解构赋值

function test([x, y]) {
    return x + y;
}
console.log(test([3, 6]));

解构好处

  • 交换变量的值
let num1 = 3;
let num2 = 6;
[num1, num2] = [num2, num1];
console.log(num1, num2);
  • 提取JSON对象中的数据
let userData = {
    id: 12,
    userName: 'wangcai',
    userAge: 20
}
let {
    id,
    userName,
    userAge
} = userData;
console.log(id, userName, userAge);
und都在哪些地方出现过?
   1)声明的变量,没有赋值
   2)访问一个对象中不存在的属性
   3)函数没有返回值,默认也是返回und
   4)形参没有赋值,也是und
   5)访问数组中不存在的索引,对应的元素也是und
   6)解析不成功的,得到的也是und

扩展运算符与 rest 运算符

扩展运算符的表现形式是三个点(…), 可以将一个数组转换为用逗号分隔的序列。

展开运算符

应用
  • 求数组中的最大值
let arr = [12, 23, 11, 56];
console.log(Math.max.apply(null, arr));
let arr = [12, 23, 11, 56];
console.log(Math.max(...arr));
  • 用于函数调用
function test(num1, num2) {
    return num1 + num2;
}
let array = [23, 56];
console.log(test(...array));

rest 运算符

  • 函数剩余参数
function add(...values) {
    console.log(values);
}
add(2, 3);
  • 解构剩余参数
let arr = [1, 2, 3, 4, 5, 6];
let [arr1, ...arr2] = arr; //进行解构处理
console.log(arr1); // 1
console.log(arr2); // [2,3,4,5,6]
  • rest参数之后不能再有其他的参数

展开运算符和rest区别

  • 第一:当3个点(…)出现在函数的形参上或者出现在赋值号的左侧,则表示的就是 rest 运算符
  • 第二:当3个点(…)出现在函数的实参上或者出现在赋值号的右侧,则表示它为扩展运算符。

箭头函数

箭头函数注意事项

  • 箭头函数直接返回一个对象,用括号
  • 箭头函数中this,找出定义箭头函数的上下文(即包含箭头函数最近的函数或者是对象),那么上下文所处的父上下文即为this.

箭头函数不适合的场景

  • 不能作为构造函数,不能使用 new 操作符
  • 没有 prototype 属性
  • 不适合将原型函数定义成箭头函数
  • 绑定事件也不建议使用,无法获取this

对象的扩展

简洁表示方式

  • 对象中的属性名和变量名一样
let userName = 'wangcai';
let userAge = 18;
let person = {
    userName,
    userAge
}
console.log(person);
  • 方法简写
let userName = 'wangcai';
let userAge = 18;
let person = {
    userName,
    userAge,
    sayHello() {
        console.log('Hello');
    }
}
person.sayHello();

Object.assign( )方法

Object.assign( ) 方法用来源对象的所有可枚举的属性复制到目标对象。该方法至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出异常。

let target = {
    a: 1,
    b: 2
};
let source = {
    c: 3,
    d: 4
};
Object.assign(target, source);
console.log(target);

深浅拷贝

通过 Object.assign( ) 方法,实现的拷贝只拷贝了属性的值,属于浅拷贝

简单的模拟深拷贝
function clone(source) {
    let newObj = {};
    for (let key in source) {
        // 由于address属性为对象,所以执行递归。
        if (typeof source[key] === 'object') {
            newObj[key] = clone(source[key]);
        } else {
            // 如果是name属性直接赋值
            newObj[key] = source[key];
        }
    }
    return newObj;
}

自己写的深拷贝

 function deepCopy(obj, o) {
      if (obj == null) return o;
      o = o ? o : new obj.constructor
      // o = new o.constructor;
      for (let k in obj) {
        if (obj[k] instanceof Function) {//判断函数类型,在判断对象之前判断,因为一切皆对象
          o[k] = obj[k]
        } else if (obj[k] instanceof Array) {//判断数组类型,在判断对象之前判断
          o[k] = deepCopy(obj[k], [])
        } else if (obj[k] instanceof Object) {//判断对象类型
          o[k] = deepCopy(obj[k], {})
        } else {
          o[k] = obj[k]
        }
      }
      return o
    }
    let obj = {
      name: 'zs',
      info: { num: 5, age: 18 },
      other: [{ q: 5 }, [1, 5]],
      fn: function () {
        console.log(111);
      }
    }
    let newobj = deepCopy(obj)//使用方法1 返回新数组
    // let newobj = {}
    // deepCopy(obj, newobj)//使用方法2 参数一拷贝到参数二
    console.log(obj);
    console.log(newobj);
    let arr = [[[1, 5, 8], 5, [1, 5], [{ w: 8 }], { a: 5 }]]
    let newarr = []
    deepCopy(arr, newarr)
    console.log(arr);
    console.log(newarr);
    function deepCopy(obj) {
      if (obj == null) return o;
      let o = new obj.constructor
      // o = new o.constructor;
      for (let k in obj) {
        if (obj[k] instanceof Object) {//判断对象类型
          o[k] = deepCopy(obj[k])
        } else {
          o[k] = obj[k]
        }
      }
      return o
    }
       let obj = {
      name: 'zs',
      info: { num: 5, age: 18 },
      other: [{ q: 5 }, [1, 5]],
      fn: function () {
        console.log(111);
      }
    }
    let newobj = deepCopy(obj)//使用方法 
    console.log(obj);
    console.log(newobj);
注意事项
  1. 如果目标对象与源对象有同名属性,那么后面的属性会覆盖前面的属性。
let target = {
    a: 1,
    b: 2
};
let source = {
    b: 3,
    d: 4
};
Object.assign(target, source);
console.log(target);
  1. 不可枚举的属性不会被复制。
let obj = {};
Object.defineProperty(obj, 'b', {
    enumerable: false,//不可枚举
    value: 'world'
})
let obj1 = {
    a: 'hello'
}
Object.assign(obj1, obj);
console.log('obj1=', obj1);

Symbol

  • Symbol是一种数据类型
  • Symbol类型的值是通过Symbol函数生成的。它的值是独一无二的,也就是唯一的,可以保证对象中属性名称的唯一。
let s = Symbol();
console.log(typeof s);//symbol

标记

let s = Symbol('s');
let s1 = Symbol('s1');
console.log(s);
console.log(s1);
let s = Symbol('s');
let s1 = Symbol('s');
console.log(s === s1);//false.

应用

作为属性名的Symbol

第一种添加属性的方式:

let mySymbol = Symbol();
let obj = {}
// 第一种添加属性的方式
obj[mySymbol] = 'hello';
console.log(obj[mySymbol]);

第二种添加属性的方式:

let mySymbol = Symbol();
let obj = {
    [mySymbol]: 'world' // 注意mySymbol必须加上方括号,否则为字符串而不是Symbol类型。
}
console.log(obj[mySymbol]);

第三种添加属性的方式

let mySymbol = Symbol();
let obj = {};
Object.defineProperty(obj, mySymbol, {
    value: '你好'
})
console.log(obj[mySymbol]);
防止属性名称冲突
let obj = {
    name: 'zs',
    age: 18
}
let mySymbol = Symbol('lib1');

function test1(obj) {
    obj[mySymbol] = 42;

}
let mySymbol2 = Symbol('lib2');

function test2(obj) {
    obj[mySymbol2] = 369;
}
test1(obj);
test2(obj);
console.log(obj);

Proxy

**Objext.keys(obj)**得到obj对象的key,是个数组

<script>
    let obj = {
        name: "wc",
        age: 18,
        adress: "bj",
    }
    Object.keys(obj).forEach(key => {
        let value = obj[key];
        Object.defineProperty(obj, key, {
            get: function() {
                console.log(`监听到了obj对象的${key}属性被访问了`);
                return value;
            },
            set: function() {
                console.log(`监听到了obj对象的${key}属性被设置了`);
            }
        })
    })
    console.log(obj.name);
    obj.name = "wc666";
    console.log(obj.adress);
</script>
  • 上面监听属性的变化不足
    • Object.defineProperty刚开始设计初衷,并不是用来监听对象中的属性
    • 如果对象非常复杂,需要递归去监听,一旦递归,性能非常差
    • 有些操作监听不了,如添加属性,删除属性…

使用Proxy监听对对象中属性的操作

<script>
    let obj = {
        name: "wc",
        age: 18
    }

    // Proxy是ES6中的一个类
    // objProxy 是上面obj的代理对象
    // obj 叫原始对象
    // {} handler 处理对象
    let objProxy = new Proxy(obj, {
        // key 表示你访问的属性名
        // target 表示原始对象
        get: function(target, key) {
            console.log(`监听到了obj对象的${key}属性被访问了`, target);
            // .....
            return target[key]
        },
        set: function(target, key, newValue) {
            console.log(`监听到了obj对象的${key}属性被设置了`, target);
            target[key] = newValue;
        }
    })
    console.log(objProxy.name);
    objProxy.name = "wc666"
    console.log(objProxy.name);
</script>

当new Proxy时,第1个参数是原始对象,第2个参数是处理对象,处理对象中放捕获器,上面的的get和set其实就是捕获器,proxy中有13的捕获器:

<script>
    let obj = {
        name: "wc",
        age: 18
    }
    // 代理对象就可以监听到你对对象中属性的操作
    let objProxy = new Proxy(obj, {
        // 获取值时的捕获器
        get: function(target, key) {
            console.log(`监听到了obj对象的${key}属性被访问了`, target);
            return target[key]
        },
        // 设置值时的捕获器
        set: function(target, key, newValue) {
            console.log(`监听到了obj对象的${key}属性被设置了`, target);
            target[key] = newValue;
        },
        // 监听in的捕获器
        has: function(target, key) {
            console.log(`监听到了obj对象的${key}属性in操作`, target);
            return key in target;
        },
        // 监听delete的捕获器
        deleteProperty: function(target, key) {
            console.log(`监听到了obj对象的${key}属性delete操作`, target);
            delete target[key]
        }
    })
    // 判断name是否是objProxy的属性
    console.log("name" in objProxy);
    delete objProxy.name;
    console.log(objProxy.name);
</script>

注意:要使Proxy起作用,必须针对Proxy对象进行操作,不是针对目标对象进行操作(上面的是student对象)。

Set和Map结构

  • 在ES6之前,我们存储数据的结构主要有两种:数组、对象。在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。

Set

  • Set结构与数组类似,但是成员的值都是唯一的,没有重复值。创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式), 我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重

常用的操作方法

  • add(value) : 添加某个值,返回Set结构本身。
  • delete(value) : 删除某个值,返回一个布尔值,表示删除是否成功
  • has(value) : 返回一个布尔值,表示参数是否为Set的成员.
  • clear() : 清除所有成员,没有返回值
  • 遍历 : 另外Set是支持for of的遍历的
    • size属性,返回的是Set结构中的成员总数
    • Set结构中的成员是不允许出现重复值的(数组去重)
// 清除数组中的重复数据.
// Set函数可以接受一个数组或者是类似数组的对象,作为参数。
let array = [1, 2, 3, 3, 5, 6];
let s = new Set(array);
console.log(Array.from(s));

WeakSet使用

和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。

和Set区别
  • 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
  • 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
  • 区别三:WeakSet不能遍历,因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁,所以存储到WeakSet中的对象是没办法获取的;
WeakSet常见的方法:
  • add(value):添加某个元素,返回WeakSet对象本身
  • delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型
  • has(value):判断WeakSet中是否存在某个元素,返回boolean类型
 // 1.Weak Reference(弱引用)和Strong Reference(强引用)
 let obj1 = {
     name: "wc"
 }
 let obj2 = {
     name: "xq"
 }
 let obj3 = {
     name: "z3"
 }

 // let arr = [obj1, obj2, obj3]
 // obj1 = null
 // obj2 = null
 // obj3 = null

 // const set = new Set(arr)
 // arr = null

 // 2.WeakSet的用法
 // 2.1.和Set的区别一: 只能存放对象类型
 const weakSet = new WeakSet()
 weakSet.add(obj1)
 weakSet.add(obj2)
 weakSet.add(obj3)

 // 2.2.和Set的区别二: 对对象的引用都是弱引用

 // 3.WeakSet的应用
 const pWeakSet = new WeakSet()
 class Person {
     constructor() {
         pWeakSet.add(this)
     }

     running() {
         if (!pWeakSet.has(this)) {
             console.log("Type error: 调用的方式不对")
             return
         }
         console.log("running~")
     }
 }

 let p = new Person()
 // p = null
 p.running()
 const runFn = p.running
 runFn()
 const obj = {
     run: runFn
 }
 obj.run()

Map

用于存储映射关系

和对象区别

  • 对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
  • 某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key,那么我们就可以使用Map

Map的常用属性和方法

  • 常见的属性之size:返回Map中元素的个数;
  • 常见的方法之set(key, value):在Map中添加key、value,并且返回整个Map对象
  • 常见的方法之get(key):根据key获取Map中的value;
  • 常见的方法之has(key):判断是否包括某一个key,返回Boolean类型;
  • 常见的方法之delete(key):根据key删除一个键值对,返回Boolean类型
  • 常见的方法之clear():清空所有的元素;
  • 常见的方法之forEach(callback, [, thisArg]):通过forEach遍历Map;
  • Map也可以通过for of进行遍历。
 const info = {
     name: "wc"
 }
 const info2 = {
     age: 18
 }

 // 1.对象类型的局限性: 不可以使用复杂类型作为key
 // const obj = {
 //   address: "bj",
 //   [info]: "haha",
 //   [info2]: "hehe"
 // }
 // console.log(obj)

 // 2.Map映射类型
 const map = new Map()
 map.set(info, "wc")
 map.set(info2, "xq")
 console.log(map)

 // 3.Map的常见属性和方法
 // console.log(map.size)
 // 3.1. set方法, 设置内容
 map.set(info, "z3")
 console.log(map)
 // 3.2. get方法, 获取内容
 // console.log(map.get(info))
 // 3.3. delete方法, 删除内容
 // map.delete(info)
 // console.log(map)
 // 3.4. has方法, 判断内容
 // console.log(map.has(info2))
 // 3.5. clear方法, 清空内容
 // map.clear()
 // console.log(map)
 // 3.6. forEach方法
 // map.forEach(item => console.log(item))

 // 4.for...of遍历
 for (const item of map) {
     const [key, value] = item
     console.log(key, value)
 }

WeakMap的使用

和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。

和Map有什么区别:
  • 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
  • 区别二:WeakMap的key对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象
  • 区别三:WeakMap也是不能遍历的,没有forEach方法,也不支持通过for of的方式进行遍历;
WeakMap常见的方法有四个:
  • set(key, value):在Map中添加key、value,并且返回整个Map对象;
  • get(key):根据key获取Map中的value;
  • has(key):判断是否包括某一个key,返回Boolean类型;
  • delete(key):根据key删除一个键值对,返回Boolean类型;
 let obj1 = {
     name: "wc"
 }
 let obj2 = {
     name: "xq"
 }

 // 1.WeakMap的基本使用
 const weakMap = new WeakMap()
 // weakMap.set(123, "aaa")
 weakMap.set(obj1, "aaa")
 weakMap.set(obj2, "bbb")

 obj1 = null
 obj2 = null

用weakMap解决循环引用问题

     循环引用,a引用b,b引用a,
    深copy的循环引用,obj里面的一个属性不断复制obj自身,会造成栈满爆栈
function deepCopy(obj, weakmap = new WeakMap()) {
      if (obj == null) return o;
      let o = new obj.constructor
      if (weakmap.get(obj)) {//当已存在obj就不再复制
        return weakmap.get(obj)
      }
      weakmap.set(obj, o)//用WeakMap的key保存原对象的引用记录, value是对应的深拷贝对象的引用
      // o = new o.constructor;
      for (let k in obj) {
        if (obj[k] instanceof Object) {//判断对象类型
          o[k] = deepCopy(obj[k], weakmap)
        } else {
          o[k] = obj[k]
        }
      }
      return o
    }
    let obj = {
      name: 'zs',
      info: { num: 5, age: 18 },
      other: [{ q: 5 }, [1, 5]],
      fn: function () {
        console.log(111);
      }
    }
    obj.qwe = obj
    let newobj = deepCopy(obj)
    console.log(obj);
    console.log(newobj);

class类

定义一个类

<script>
    // Person是类名
    class Person {

    }
    let p = new Person();
    console.log(p); // 对象
</script>
<script>
    // Person是类名
    // 类表达式
    let Person = class {

    }
    let p = new Person();
    console.log(p); // 对象
</script>

通过类创建出来的对象

<script>
    function Student() {}
    console.log(typeof Student); // function

    class Person {

    }
    let p = new Person();
    // 通过class创建的类,也有prototype
    console.log(Person.prototype);
    console.log(p.__proto__);
    console.log(Person.prototype == p.__proto__);
    console.log(typeof Person);
</script>

通过class创建的对象,赋值私有属性和公有属性

<script>
    class Person {
        // 一个类只有一个constructor
        // 1)内部创建一个对象  nm = {}
        // 2)将Person的原型prototype赋值给创建出来的对象  nm.__proto__ = Person.prototype
        // 3)将对象赋值给this  将this指向对象   new绑定  this = mn;
        // 4)执行constructor中的代码
        // 5)返回创建出来的对象  return nm
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    // p的私有属性:name  age
    let p = new Person("wc", 18);
    console.log(p.hasOwnProperty("name"));
    console.log(p.hasOwnProperty("age"));
</script>
私有属性(ES13#)
    class Person{
        // 实例属性
        height = 188;
        // 之前:不希望外面访问   潜规则
        _address = "bj"
        // ES13对象属性,可以以#打头,表示私有
        #money = "1个亿"
        // 静态属性,也叫类属性
        static total = "100件"
        // 静态属性,也叫类属性, 私有
        static #sum = "1000万"
        getMoney(){
            return this.#money;
        }
        getSum(){
            return Person.#sum;
        }
    }
    let p = new Person();
    console.log(p.height);
    console.log(p._address);
    // console.log(p.#money);
    console.log(p.getMoney());
    console.log(Person.total);
    // console.log(Person.#sum);
    console.log(p.getSum());

对象中的方法或公有属性

<script>
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        // running是公有属性还是私有属性
        running() {
            console.log(this.name + " running...");
        }
    }
    let p = new Person("wc", 18);
    p.running();
    console.dir(p)
</script>

类中的设置器和访问器

<script>
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
            // _address 表示不建议在类的外面访问
            // 当时就可以使用get 和 set
            this._address = "bj"
        }
        get address() {
            console.log("getter调用了~");
            // getter返回什么,address属性就是什么
            return this._address;
        }
        set address(value) {
            this._address = value;
        }
    }
    let p = new Person("wc", 18);
    console.log(p.address); // 自动调用上面的get address(){}
    p.address = "gz" // 自动调用上面的set address(){}
    console.log(p.address);
</script>

静态属性

<script>
    // 一切都是对象
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        running() {
            console.log(this.name + " running...");
        }
        // 静态属性 只能通过类名来访问
        static eating() {
            console.log("eating...");
        }
    }
    Person.eating();
    let p = new Person("wc", 10);
    p.eating();
</script>

继承

<script>
    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        running() {
            console.log(this.name + " running...");
        }
        static eating() {
            console.log("eating...");
        }
    }
    // extends表示继承  Student类继承了Person类
    class Student extends Person {
        constructor(name, age, sno) {
            // 由于Student继承了Person,当new Student时,还需要走Person的constructor
            // super(); // super表示调用Persion的constructor
            // this.name = name;  // name表示Student的私有属性
            // this.age = age;  // age表示Student的私有属性
            // this.sno = sno;   // sno表示Student的私有属性

            super(name, age);
            this.sno = sno;
        }
    }
    let stu = new Student("wc", 18, 110);
    console.log(stu.name);
    console.log(stu.age);
    stu.running();
    Student.eating(); // 静态属性也可以继承到
</script>
父类有的公有属性,子类是可以重写
继承,JS中内置的类
<script>
    // 自己实现的类,去继承JS中的内置的类
    class MyArray extends Array {
        // 你的push,覆盖了Array中的push
        // 重写
        push() {
            console.log("....");
        }
    }
    let marr = new MyArray();
    marr.push(1)
    marr.push(2)
    console.log(marr);
</script>

静态代码块

    class Person {
        // 静态代码块,在加载这个类的时候,就执行了
        static {
            console.log(this);
            console.log("hello es6+");
            console.log("hello es6+");
        }
    }

es6+

ES7

ES7-Array Includes

判断一个数组中是否包含一个指定的元素

let names = ["wc", "xq", "z3"];
if (names.includes("wc")) {
    console.log("包含wc")
}

console.log(names.indexOf(NaN)); // -1 
console.log(names.includes(NaN)); // true
ES7-指数exponentiation运算符
const res = Math.pow(2, 2);
const res2 = 3 ** 3;
console.log(res, res2)

ES8

ES8-Object values 和 Object entries
const obj = {
    name: "wc",
    age: 18,
    height: 1.88,
    address: "bj"
}

// 1.获取所有的key
const keys = Object.keys(obj)
console.log(keys)

// 2.ES8 Object.values
const values = Object.values(obj)
console.log(values)

// 3.ES8 Object.entries 键值对象
// 3.1. 对对象操作
const entries = Object.entries(obj)
console.log(entries)
for (const entry of entries) {
    const [key, value] = entry
    console.log(key, value)
}

// 3.2. 对数组/字符串操作
console.log(Object.entries(["wc", "xq"]))
console.log(Object.entries("Hello"))
ES8-String Padding

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

// padStart和padEnd
// 1.应用场景一: 对时间进行格式化
// const minute = "15".padStart(2, "0")
// const second = "6".padStart(2, "0")

// console.log(`${minute}:${second}`)

// 2.应用场景二: 对一些敏感数据格式化
let cardNumber = "410883199898764665"
const sliceNumber = cardNumber.slice(0, -4)
cardNumber = sliceNumber.padStart(cardNumber.length, "*")
console.log(cardNumber)
ES8-Trailing Commas

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

 function foo(num1, num2, ) {
     console.log(num1, num2)
 }

 foo(10, 20, )
ES8-Object Descriptors
ES8-Async Function

ES9

  • ES9-iterator迭代器
  • ES9-Object spread operators
  • ES9-Promise finally

ES10

ES10-flat flatMap
  • flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
  • flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
    • 注意一:flatMap是先进行map操作,再做flat的操作;
    • 注意二:flatMap中的flat相当于深度为1;
// flat的使用: 将一个数组, 按照制定的深度遍历, 将遍历到的元素和子数组中的元素组成一个新的数组, 进行返回
const nums = [10, 20,
    [111, 222],
    [333, 444],
    [
        [123, 321],
        [231, 312]
    ]
]
const newNums1 = nums.flat(1)
console.log(newNums1)
const newNums2 = nums.flat(2)
console.log(newNums2)

// 2.flatMap的使用:对数组中每一个元素应用一次传入的map对应的函数
const messages = [
    "wc",
    "xq",
    "z3"
]

// 1.for循环的方式:
// const newInfos = []
// for (const item of messages) {
//   const infos = item.split(" ")
//   for (const info of infos) {
//     newInfos.push(info)
//   }
// }
// console.log(newInfos)

// 2.先进行map, 再进行flat操作
// const newMessages = messages.map(item => item.split(" "))
// const finalMessages = newMessages.flat(1)
// console.log(finalMessages)

// 3.flatMap
const finalMessages = messages.flatMap(item => item.split(" "))
console.log(finalMessages)
ES10-Object fromEntries

entries转换成对象

// 1.对象
 const obj = {
     name: "wc",
     age: 18,
     height: 1.88
 }

 const entries = Object.entries(obj)
 const info = Object.fromEntries(entries)
 console.log(info)

应用

	//查询字符串
    let queryString = "?name=wc&age=18&height=1.88";
    let params = new URLSearchParams(queryString);
    console.log(params.get("name"));
    console.log(params.get("age"));
    console.log(params.get("height"));

    // console.log(params.entries());

    for(let item of params.entries()){
        console.log(item);
    }

    let obj = Object.fromEntries(params.entries());
    console.log(obj);
ES10-trimStart trimEnd

去除一个字符串首尾的空格,我们可以通过trim方法
ES10中给我们提供了trimStart和trimEnd

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

ES11

ES11-BigInt

MAX_SAFE_INTEGER的数值,表示的可能是不正确

let maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt);
console.log(maxInt + 1);
console.log(maxInt + 2);

那么ES11中,引入了新的数据类型BigInt,用于表示大的整数, BitInt的表示方法是在数值的后面加上n

let bigInt = 9007199254740992n;
console.log(bigInt + 1n);
console.log(bigInt + 2n);
ES11-Nullish Coalescing Operator

ES11,Nullish Coalescing Operator增加了空值合并操作符

let info = undefined
// info = info || "默认值"//info ||= "默认值"
// console.log(info)

// ??: 空值合并运算符
info = info ?? "默认值"
console.log(info)
ES11-Optional Chaining

可选链也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁:

const obj = {
    name: "wc",
    friend: {
        name: "xq",
        // running: function() {
        //   console.log("running~")
        // }
    }
}

// 1.直接调用: 非常危险
// obj.friend.running()

// 2.if判断: 麻烦/不够简洁
// if (obj.friend && obj.friend.running) {
//   obj.friend.running()
// }

// 3.可选链的用法: ?.
obj?.friend?.running?.()
ES11-Global This

之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的

  • 在浏览器中可以通过this、window来获取;
  • 在Node中我们需要通过global来获取;
    在ES11中对获取全局对象进行了统一的规范:globalThis
console.log(globalThis)
ES11-for…in标准化

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

let obj = {
    name: "wc",
    age: 18,
    height: 1.88
}
for (let key in obj) {
    console.log(key)
}

ES12

ES12-logical assignment operators
// 赋值运算符
 // const foo = "xq"
 let counter = 100
 counter = counter + 100
 counter += 50

 // 逻辑赋值运算符
 function foo(message) {
     // 1.||逻辑赋值运算符
     // message = message || "默认值"
     // message ||= "默认值"

     // 2.??逻辑赋值运算符
     // message = message ?? "默认值"
     message ?? = "默认值"

     console.log(message)
 }

 foo("wc")
 foo()

 // 3.&&逻辑赋值运算符
 let obj = {
     name: "wc",
     running: function() {
         console.log("running~")
     }
 }

 // 3.1.&&一般的应用场景
 // obj && obj.running && obj.running()
 // obj = obj && obj.name
 obj && = obj.name
 console.log(obj)

ES13

ES13-method .at()

Array.prototype.at()
at() 方法接收一个整数值并返回该索引的项目,允许正数和负数。负整数从数组中的最后一个项目开始倒数。

ES13-Object.hasOwn(obj, propKey)
  • Object中新增了一个静态方法(类方法): hasOwn(obj, propKey), 方法用于判断一个对象中是否有某个自己的属性;
  • 和之前Object.prototype.hasOwnProperty区别
    • 区别一:防止对象内部有重写hasOwnProperty
    • 区别二:对于隐式原型指向null的对象, hasOwnProperty无法进行判断
 const obj = {
     name: "wc",
     age: 18,
     // 防止对象中也有一个自己的hasOwnProperty方法
     hasOwnProperty: function() {
         return "ok"
     },
     __proto__: {
         address: "bj"
     }
 }

 console.log(obj.name, obj.age)
 console.log(obj.address)

 console.log(obj.hasOwnProperty("name"))
 console.log(obj.hasOwnProperty("address"))

 console.log(Object.hasOwn(obj, "name"))
 console.log(Object.hasOwn(obj, "address"))

 // 和hasOwnProperty的区别二:
 const info = Object.create(null)
 info.name = "wc"
 // console.log(info.hasOwnProperty("name"))
 console.log(Object.hasOwn(info, "name"))
ES13-New members of classes

在ES13中,新增了定义class类中成员字段(field)的其他方式:

  • Instance public fields
  • Static public fields
  • Instance private fields
  • static private fields
  • static block
 class Person {
     // 实例属性
     // 对象属性: public 公共 -> public instance fields
     height = 1.88

     // 对象属性: private 私有: 潜规则
     // _intro = "name is wc"

     // ES13对象属性: private 私有: 潜规则
     #intro = "name is wc"

     // 2.类属性(static)
     // 类属性: public
     static totalCount = "1000万"

     // 类属性: private
     static #maleTotalCount = "1000万"

     constructor(name, age) {
         // 对象中的属性: 在constructor通过this设置
         this.name = name
         this.age = age
         this.address = "bj"
     }

     // 3.静态代码块
     static {
         console.log("Hello World")
         console.log("Hello Person")
     }
 }

 const p = new Person("wc", 18)
 console.log(p)
 console.log(p.name, p.age, p.height, p.address, p.#intro)
 console.log(Person.#maleTotalCount)

二、Promise与异步方案

手写Promise
b站视频
知乎链接
添加链接描述
添加链接描述
添加链接描述

异步

  • 同步代码
    书写顺序和代码的执行顺序是一样的
  • 异步代码
    异步代码的书写顺序和代码的执行顺序不一样

解决异步问题

<script>
    // 最早解决异步问题:靠回调函数
    // 1.设计这样的一个函数
    function execCode(counter, successCallback, failureCallback) {
        // 异步任务
        setTimeout(() => {
            if (counter > 0) { // counter可以计算的情况 
                let total = 0
                for (let i = 0; i < counter; i++) {
                    total += i
                }
                // 在某一个时刻只需要回调传入的函数
                successCallback(total)
            } else { // 失败情况, counter有问题
                failureCallback(`${counter}值有问题`)
            }
        }, 3000)
    }
    // 2.ES5之前,处理异步的代码都是这样封装
    execCode(100, (value) => {
        console.log("本次执行成功了:", value)
    }, (err) => {
        console.log("本次执行失败了:", err)
    });
</script>

Promise

  • Promise是一个类,可以翻译成 承诺、许诺 、期约;
  • 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
  • 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
    • 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
    • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
    • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
<script>
    // p叫promise对象
    // 手写promise
    // 当new Promise时,执行器会立即执行
    // Promise有三个状态  当new出来时,是处于等状态
    // 调用resolve可以把等待状态的promise变成成功态
    // 调用reject可以把等待状态的promise变成失败态
    // 一个promise只能从等待到成功或从等待到失败
    let p = new Promise((resolve, reject) => {
        console.log("我是执行器,我立即执行了...");
        // 在执行器中通常写异步代码
        // 我们说的异步指的是定时器中的回调函数
        setTimeout(() => {
            // console.log("我是定时器");
            // 在异步代码中,可以调用resovle或reject
            // resolve,reject是一个函数
            // resolve中的值,就是成功的值,也就是终值  value
            // resolve("包包"); // 就是把等待的promise变成成功的promise

            // reject中的值,就是失败的值,也就是失败的原因  reason
            reject("没钱"); // 就是把等待的promise变成失败的promise
        }, 3000)
    });
</script>

Promise有三种状态:

  • 等待状态:pending 默认你创建出来的promise是处于等待状态
  • 成功状态:fulfulled 当调用resolve时,就可以把promise从等待变成成功
  • 失败状态:rejected 当调用reject时,就可以把promise从等待变成失败
<script>
    // Promise解决异步问题
    function execCode(counter) {
        let promise = new Promise((resolve, reject) => {
            // 异步任务
            setTimeout(() => {
                if (counter > 0) { // counter可以计算的情况 
                    let total = 0
                    for (let i = 0; i < counter; i++) {
                        total += i
                    }
                    // 成功的回调
                    resolve(total)
                } else { // 失败情况, counter有问题
                    // 失败的回调
                    reject(`${counter}有问题`)
                }
            }, 3000)
        })
        return promise;
    }
    let promise = execCode(100)
    promise.then(result => {
        console.log(result);
    }, err => {
        console.log(err);
    })
</script>

resolve

  • resolve不同值的区别
    • 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
    • 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
    • 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态:
<script>
    // resolve的实参问题
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p的resolve")
            reject("没钱")
        }, 2000)
    })
    const promise = new Promise((resolve, reject) => {
        // 1)参数是普通的数据
        // resolve(["a","b","c"])

        // 2)参数是promise 
        // 如果resolve的参数是promise,最终结果由p决定
        // resolve(p)

        // 3)参数是thenable(就是一个对象中有一个then函数)
        resolve({
            then: function(resolve, reject) {
                // resolve("包包")
                reject("没钱")
            }
        })
    });

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
    })
</script>

then函数

  • then方法是Promise对象上的一个方法(实例方法):
    • 它其实是放在Promise的原型上的 Promise.prototype.then
  • then方法接受两个参数:
    • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
    • reject的回调函数:当状态变成reject时会回调的函数;
  • 一个Promise的then方法是可以被多次调用的:
    • 每次调用我们都可以传入对应的fulfilled回调;
    • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
<script>
    // then函数
    const promise = new Promise((resolve, reject) => {
        resolve("success")
        // reject("error")
    });

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
    })

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
    })

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
    })
</script>
then函数的返回值
  • then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用:
    • 但是then方法返回的Promise到底处于什么样的状态呢?
  • Promise有三种状态,那么这个Promise处于什么状态呢?
    • 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
    • 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
      • 情况一:返回一个普通的值;
      • 情况二:返回一个Promise;
      • 情况三:返回一个thenable值;
    • 当then方法抛出一个异常时,那么它处于reject状态;

thenable

<script>
    // then函数的返回值问题
    const promise = new Promise((resolve, reject) => {
        resolve("success")
    });

    promise.then(res => {
        console.log(res);
        // 如果返回thenable,整体的promise取决于thenable的状态
        return {
            then: function(resolve, reject) {
                // resolve("包包")
                reject("没钱")
            }
        };
    }, err => {
        console.log(err);
    }).then(res => {
        console.log("res:", res);
    }, err => {
        console.log("err:", err);
    })
</script>
then的顺延
<script>
    // then的顺延
    const promise = new Promise((resolve, reject) => {
        reject("bad")
    });

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
        // 这里返回了und  就意味着新的promise是成功的
        // new Error("我错了")  就意味着新的promise是失败的
        throw new Error("我错了")
    }).then(res => {
        console.log("res:", res);
    }, null).then(null, err => {
        console.log("err:", err);
    })
</script>

catch方法

  • catch方法也是Promise对象上的一个方法(实例方法):
    • 它也是放在Promise的原型上的 Promise.prototype.catch
  • 一个Promise的catch方法是可以被多次调用的:
    • 每次调用我们都可以传入对应的reject回调;
    • 当Promise的状态变成reject的时候,这些回调函数都会被执行;
catch方法 – 返回值
  • 事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:
    • 下面的代码,后续是catch中的err2打印,还是then中的res打印呢?
    • 答案是res打印,这是因为catch传入的回调在执行完后,默认状态依然会是fulfilled的;
  • 如果我们希望后续继续执行catch,那么需要抛出一个异常:
    在这里插入图片描述

finally

  • 在ES9中,新增了finally方法,无论promise是成功的,还是失败的,最终都会执行finally
  • finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行。
<script>
    // finally
    const promise = new Promise((resolve, reject) => {
        reject("bad")
    });

    promise.then(res => {
        console.log(res);
    }, err => {
        console.log(err);
        throw new Error("我错了")
    }).then(res => {
        console.log("res:", res);
    }).catch(err => {
        console.log("err:", err);
    }).finally(() => {
        console.log("哈哈哈哈")
        console.log("呵呵呵呵")
    })
</script>

Promise系统掌握之类方法(静态方法)

  • then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上
resolve
  • 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
    • Promise.resolve的用法相当于new Promise,并且执行resolve操作:
  • resolve参数的形态:
    • 情况一:参数是一个普通的值或者对象
    • 情况二:参数本身是Promise
    • 情况三:参数是一个thenable
<script>
    // 类方法(静态方法)
    const promise = Promise.resolve("hello")

    promise.then(res => {
        console.log("then结果:", res)
    })
    // 相当于
    // new Promise((resolve) => {
    //   resolve("hello")
    // })
</script>
reject
  • reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
  • Promise.reject的用法相当于new Promise,只是会调用reject:
  • Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。
<script>
    // 类方法(静态方法)
    const promise = Promise.reject("rejected error")
    promise.catch(err => {
        console.log("err:", err)
    })

    // 相当于
    // new Promise((_, reject) => {
    //   reject("rejected error")
    // })
</script>
all方法
  • 它的作用是将多个Promise包裹在一起形成一个新的Promise;
  • 新的Promise状态由包裹的所有Promise共同决定:
    • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
    • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
<script>
    // 类方法(静态方法) all

    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p1 resolve")
            reject("p1 reject error")
        }, 3000)
    })

    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p2 resolve")
            reject("p2 reject error")
        }, 2000)
    })

    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("p3 resolve")
        }, 5000)
    })

    // 类方法(静态方法) all
    // all的作用:所有promise都成功后,得到所有成功后的promise结果
    //    如果有一个先失败了,直接得到最先失败promise的结果
    Promise.all([p1, p2, p3]).then(res => {
        // ['p1 resolve', 'p2 resolve', 'p3 resolve']
        console.log(res);
    }).catch(err => {
        console.log(err);
    })
</script>
allSettled
  • all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
    • 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
  • 在ES11(ES2020)中,添加了新的API Promise.allSettled:
    • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态;
    • 并且这个Promise的结果一定是fulfilled的;
      在这里插入图片描述
  • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
  • 这个对象中包含status状态,以及对应的value值;
<script>
    // 类方法(静态方法) allSettled

    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p1 resolve")
            reject("p1 reject error")
        }, 3000)
    })

    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p2 resolve")
            reject("p2 reject error")
        }, 2000)
    })

    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("p3 resolve")
        }, 5000)
    })

    // 类方法: allSettled
    // [
    //     {
    //         "status": "fulfilled",
    //         "value": "p1 resolve"
    //     },
    //     {
    //         "status": "fulfilled",
    //         "value": "p2 resolve"
    //     },
    //     {
    //         "status": "fulfilled",
    //         "value": "p3 resolve"
    //     }
    // ]   
    // allSettled  获取所有的promise的结果,不管成功还是失败
    Promise.allSettled([p1, p2, p3]).then(res => {
        console.log("all settled:", res)
    })
</script>
race方法
  • race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
<script>
    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p1 resolve")
            reject("p1 reject error")
        }, 3000)
    })

    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p2 resolve")
            reject("p2 reject error")
        }, 2000)
    })

    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("p3 resolve")
        }, 5000)
    })
    // 类方法: race方法    race是比赛的意思
    // 特点: 会等到第一个Promise有结果(无论这个结果是fulfilled还是rejected)
    Promise.race([p1, p2, p3]).then(res => {
        console.log("race promise:", res)
    }).catch(err => {
        console.log("race promise err:", err)
    })
</script>
any方法
  • any方法是ES12中新增的方法,和race方法是类似的:
    • any方法会等到一个fulfilled状态,才会决定新Promise的状态;
    • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
  • 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
<script>
    // 类方法: any方法

    // 创建三个Promise
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p1 resolve")
            reject("p1 reject error")
        }, 3000)
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("p2 resolve")
            // reject("p2 reject error")
        }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve("p3 resolve")
            reject("p3 reject error")
        }, 5000)
    })
    // 类方法: any方法
    //  any 返回第1个成功的  或者  返回所有都失败了
    Promise.any([p1, p2, p3]).then(res => {
        console.log("any promise res:", res)
    }).catch(err => {
        console.log("any promise err:", err)
    })
</script>

async函数

  • async是一个关键字,用于声明一个异步函数,async是asynchronous简写,是异步的意思。
  • sync是synchronous简写,是同步的意思。
<script>
    // 普通函数
    // function foo() { }
    // const bar = function () { }
    // const baz = () => { }

    // 生成器函数
    // function* foo() { }

    // 异步函数
    async function foo() {
        console.log("foo function1")
        console.log("foo function2")
        console.log("foo function3")
    }
    // async返回promise
    let res = foo();
    console.log(res);

    let gn = async function() {};
    let kn = async () => {};
    class Person {
        async running() {}
    }
</script>

async函数的返回值

异步函数的结果永远都是promise
异步函数内部代码的执行过程和普通函数是一样的,默认也是同步执行。异步函数和普通函数的区别,如下:

  • 异步函数可以有返回值,但是不管返回什么普通值,都会包裹在Pormise.resolve中
  • 如果异步函数自己返回了promise,得到的Promies状态由这个promise决定
  • 如果我们异步函数返回值是一个对象并且实现thenable,得到的Promies状态由then方法中做了什么才能决定
  • 如果在async函数中抛出一个错误,得到的promise是一个失败的promsie
<script>
    async function foo() {
        console.log("foo function1")
        console.log("foo function2")
        console.log("foo function3")
        // 1)返回普通值,promis是成功的promsie
        // return 123;

        // 2)返回promise   res这个promise是成功还是失败,取决于你返回的promise是成功还是失败
        // return new Promise((resolve, reject)=>{
        //     setTimeout(()=>{
        //         resolve("hello")
        //     },2000)
        // })

        // 3)返回thenable  res这个promise是成功还是失败,取决于你返回的thenable是成功还是失败
        return {
            then: function(resolve, reject) {
                reject("没钱~")
            }
        }
    }

    let res = foo();
    res.then(res => {
        console.log("res:", res);
    }).catch(err => {
        console.log("err:", err);
    })
</script>

async函数有异常

<script>
    // 在async函数中,如果抛出一个错误,res这个promise是失败的promise
    async function foo() {
        console.log("foo function1")
        console.log("foo function2")
        console.log("foo function3")
        throw new Error("我是异常")
    }

    let res = foo();
    res.then(res => {
        console.log("res:", res);
    }).catch(err => {
        console.log("err:", err);
    })
</script>

awati的使用

async关键字可以单独使用,在异步函数内部可以使用await关键字,但是在普通函数中不能使用await关键字

  • 作用
    • await后面跟一个表达式,这个表达式通常是一个promise
    • 这个await可以等待它后面的promise成功后,拿到成功的结果,得到之后,才会执行后面的代码
  • await后面跟不同的数据:
    • 如果await后面跟一个普通值,那么会直接返回这个值。
    • 如果await后面跟一个thenable对象,那么要看你这个thenable中的then做了什么。
    • 如果await后面的promise是失败的,需要通过try catch来获取失败的结果。
<script>
    // await后面跟一个普通值
    // function fn(){
    //     await 123
    // }
    // SyntaxError: await is only valid in async functions and the top level bodies of modules
    // fn();

    // awati 1)必须写在async函数中  2)await后面通常是跟一个promsie
    //       3)await前面就可以获取promise成功的结果
    async function gn() {
        let rs = await 123
        console.log("rs:", rs); // 123
        // 返回一个und 
    }
    gn().then(res => {
        console.log("res:", res);
    })
</script>

await后面通常跟promise

<script>
    function bar() {
        console.log("bar function")
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(123)
            }, 2000)
        })
    }
    async function foo() {
        // await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码
        // await下面的代码相当于一个.then  
        const res1 = await bar()
        console.log("await后面的代码:", res1)
        const res2 = await bar()
        console.log("await后面的代码:", res2)
    }

    foo()
</script>
<!-- <script>
    function bar() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                // resolve("包包")
                reject("没钱")
            }, 2000)
        })
    }

    async function foo() {
        // bar().then(res=>{
        //     console.log("res:",res);
        // }).catch(err=>{
        //     console.log("err:",err);
        // })

        try {
            let res = await bar();
            console.log("res:", res);
        } catch (err) {//用try catch返回失败结果
            console.log("err:", err);
        }
    }

    foo();
</script> -->

使用async+await处理异常问题

<script>
    function requestData(url) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(url)
                // reject("error message")
            }, 2000);
        })
    }
    // await后面跟promise
    // await前面得到成功的promise的结果
    // await下面就可以使用成功的结果  相当于then 
    // await等待的意思  让我们处理异步像同步代码一样
    async function getData() {
        const res1 = await requestData("001")
        console.log("res1:", res1)
        const res2 = await requestData(res1)
        console.log("res2:", res2)
        const res3 = await requestData(res2)
        console.log("res3:", res3)
        return res3
    }
    getData().then(res => {
        console.log(res);
    })
</script>

浏览器事件环

进程:

计算机已经运行直来的程序,是操作系统操作程序的一种方式。当一个软件运行起来后,就是一个进程,电脑上可以运行很多软件,在OS上,有很多的进程,进程是OS分配资源(CPU和内存)的基本单位。OS可以当在一个工厂,一个个的车间就是进程。

线程:

操作系统能够运行运算调度的最小单位,一个进程中,至少要包含一个线程,用来执行程序中的代码,这个线程叫主线程,线程才是真正干活的,类似于工厂中的工人。一个车间如果只有一个工人,就是单线程,如果一个车间中有N个工人,就是多线程。

操作系统的工作方式
  • 操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
    • 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
    • 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
    • 对于用户来说是感受不到这种快速的切换的;

浏览器是多进程的:

浏览器是一个多进程的软件,一个选项卡,就是一个进程,进程之间一般是独立的。在每一个进程中,包含了很多的线程,其中就包括JS代码执行线程。执行JS代码的线程就一个,也就是说,同一个时刻,只能做一件事,那么我们就说JS是单线程的。如果遇到了一个非常耗时的任务,线程就阻塞,此时,JS的主线程不会等待,浏览器会开一些其它线程去执行耗时任务,小线程执行的结果,就通过回调函数告诉主线程,我们说的JS是单线程的,是指主线程是单线程的,浏览器内部还可以开一些其它线程,如定时器线程,如ajax数据请求线程。

异步代码分两类:

  • 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
  • 微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()等

JS代码的执行顺序:

  1. 从代码段开始执行
  2. 如果遇到一个宏任务,会把这个任务放到一个宏任务队列,如果遇到一个微任务,就把这个微任务放到微任务任务中。
  3. 当同步代码执行完毕后,先去清空微任务队列。
  4. 当微任务队列清空完毕后,从宏任务队列中取出一个宏任务,去执行,在执行过程中,你的宏任务中可能还有同步代码或宏任务或微任务,重复上面的步骤,执行完一个宏任务,肯定要清空微任务队列。

例题

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

第一次: 在发现 Promise.resolve(4) 的时候,创建 NewPromiseResolveThenableJob,并将其送入微任务队列
第二次: 在处理 Promise.resolve(4) 的时候,调用 then 方法时,内部创建了微任务来处理回调函数
解析
https://juejin.cn/post/7151707736562991112
https://juejin.cn/post/6953452438300917790

三、Iterator-Generator

迭代器

  • 迭代器(iterator),使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
    • 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
    • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;
  • 从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
  • 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
    • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
    • 在JavaScript中这个标准就是一个特定的next方法;
  • next方法有如下的要求:
    • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
    • done(boolean)
      • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
      • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
    • value
      • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
        在这里插入图片描述
        在这里插入图片描述

可迭代对象

  • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
  • 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;
  • 好处
    • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作;
    • 比如 for…of 操作时,其实就会调用它的 @@iterator 方法;
      在这里插入图片描述
原生迭代器对象
  • String、Array、Map、Set、arguments对象、NodeList集合;
    在这里插入图片描述
可迭代对象的应用
  • JavaScript中语法:for …of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment);
  • 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  • 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

在这里插入图片描述
在这里插入图片描述

自定义类的迭代实现

在这里插入图片描述
在这里插入图片描述

迭代器的中断

  • 迭代器在某些情况下会在没有完全迭代的情况下中断:
    • 比如遍历的过程中通过break、return、throw中断了循环操作;
    • 比如在解构的时候,没有解构所有的值;
  • 那么这个时候我们想要监听中断的话,可以添加return方法:
    在这里插入图片描述
    在这里插入图片描述

生成器

  • 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
  • 生成器函数也是一个函数,但是和普通的函数有一些区别:
    • 首先,生成器函数需要在function的后面加一个符号:*
    • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
    • 最后,生成器函数的返回值是一个Generator(生成器):
      • 生成器事实上是一种特殊的迭代器
      • MDN:Instead, they return a special type of iterator, called a Generator.

生成器函数执行

  • 调用next
  • 通过yield来返回结果
    在这里插入图片描述

生成器传递参数 – next函数

  • 给每个分段来传递参数
    • 在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
    • 注意:也就是说我们是为本次的函数代码块执行提供了一个值;
      在这里插入图片描述

生成器提前结束 – return函数

  • return传值后这个生成器函数就会结束,之后调用next不会继续生成值
    在这里插入图片描述

生成器抛出异常 – throw函数

  • 抛出异常后我们可以在生成器函数中捕获异常;
  • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行
    在这里插入图片描述

生成器替代迭代器

  • 生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
    在这里插入图片描述
  • 还可以使用yield*来生产一个可迭代对象
    • 相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值
      在这里插入图片描述

自定义类迭代 – 生成器实现

  • 在之前的自定义类迭代中,我们也可以换成生成器:
    在这里插入图片描述

对生成器的操作

在这里插入图片描述

异步处理方案

  • 案例需求:
    • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
    • 第二次的请求url依赖于第一次的结果;
    • 第三次的请求url依赖于第二次的结果;
    • 依次类推;
      在这里插入图片描述
      在这里插入图片描述

Generator方案

优化
在这里插入图片描述

  • 自动执行generator函数
  • 目前我们的写法有两个问题:
    • 第一,我们不能确定到底需要调用几层的Promise关系;
    • 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?
  • 所以,我们可以封装一个工具函数execGenerator自动执行生成器函数:
    自动执行generator函数
    在这里插入图片描述

异步函数 async function

  • async关键字用于声明一个异步函数:
    • async是asynchronous单词的缩写,异步、非同步;
    • sync是synchronous单词的缩写,同步、同时;
  • 写法
    在这里插入图片描述

异步函数的执行流程

  • 异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
  • 异步函数有返回值时,和普通函数会有区别:
    • 情况一:异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到Promise.resolve中;
    • 情况二:如果我们的异步函数的返回值是Promise,状态由会由Promise决定;
    • 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
  • 如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;

await关键字

  • async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
  • await关键字有什么特点呢?
    • 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
    • 那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
  • 如果await后面是一个普通的值,那么会直接返回这个值;
  • 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;
  • 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值
    // 1.普通函数
    // function foo1() {
    //   await 123
    // }
    // foo1()


    // 2.await关键字
    // await条件: 必须在异步函数中使用
    function bar() {
      console.log("bar function")
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(123)
        }, 100000)
      })
    }

    async function foo() {
      console.log("-------")
      // await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码
      const res1 = await bar()
      console.log("await后面的代码:", res1)
      const res2 = await bar()
      console.log("await后面的代码:", res2)

      console.log("+++++++")
    }

    foo()

await和async结合

    // 1.定义一些其他的异步函数
    function requestData(url) {
      console.log("request data")
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(url)
        }, 3000)
      })
    }

    async function test() {
      console.log("test function")
      return "test"
    }

    async function bar() {
      console.log("bar function")

      return new Promise((resolve) => {
        setTimeout(() => {
          resolve("bar")
        }, 2000);
      })
    }

    async function demo() {
      console.log("demo function")
      return {
        then: function(resolve) {
          resolve("demo")
        }
      }
    }


    // 2.调用的入口async函数
    async function foo() {
      console.log("foo function")

      const res1 = await requestData("why")
      console.log("res1:", res1)

      const res2 = await test()
      console.log("res2:", res2)

      const res3 = await bar()
      console.log("res3:", res3)

      const res4 = await demo()
      console.log("res4:", res4)
    }

    foo()

四、throw

错误处理方案

throw语句:

  • throw语句用于抛出一个用户自定义的异常;
  • 当遇到throw语句时,当前的函数执行会被停止(throw后面的语句不会执行);
  • 如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。
  • throw表达式就是在throw后面可以跟上一个表达式来表示具体的异常信息
  • throw关键字可以跟上哪些类型呢?
    • 基本数据类型:比如number、string、Boolean
    • 对象类型:对象类型可以包含更多的信息
  • 但是每次写这么长的对象又有点麻烦,所以我们可以创建一个类:
    在这里插入图片描述
Error类型
  • JavaScript已经给我们提供了一个Error类,我们可以直接创建这个类的对象
    在这里插入图片描述
  • Error包含三个属性
    • messsage:创建Error对象时传入的message;
    • name:Error的名称,通常和类的名称一致;
    • stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack;
  • Error有一些自己的子类:
    • RangeError:下标值越界时使用的错误类型;
    • SyntaxError:解析语法错误时使用的错误类型;
    • TypeError:出现类型错误时,使用的错误类型;

异常的捕获

异常传递过程
  • foo函数在被执行时会抛出异常,也就是我们的bar函数会拿到这个异常;
  • 但是bar函数并没有对这个异常进行处理,那么这个异常就会被继续传递到- 调用bar函数的函数,也就是test函数;
  • 但是test函数依然没有处理,就会继续传递到我们的全局代码逻辑中;
  • 依然没有被处理,这个时候程序会终止执行,后续代码都不会再执行了;
    在这里插入图片描述
    在这里插入图片描述
try catch

在这里插入图片描述
在这里插入图片描述

  • 在ES10(ES2019)中,catch后面绑定的error可以省略。
  • 当然,如果有一些必须要执行的代码,我们可以使用finally来执行:
    • finally表示最终一定会被执行的代码结构;
    • 注意:如果try和finally中都有返回值,那么会使用finally当中的返回值
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值