• 尚硅谷ES6复习总结上(64th)


    1. let 关键字

    let 关键字用来声明变量,使用 let 声明的变量有几个特点:

    1、不允许重复声明
    2、块级作用域

     if (true) {
        let a = 10;
    }
    console.log(a) // a is not defined
    
    • 1
    • 2
    • 3
    • 4

    3、不存在变量提升

     console.log(a); //报错
     let a = 20;
    
    • 1
    • 2

    4、不影响作用域链

    以后声明变量使用let 就对了

    案例1:给多个 div 循环注册点击事件

    // 错误示例,divs.length === 3
    document.addEventListener('DOMContentLoaded', function () {
        let divs = document.querySelectorAll('.box div');
        for (var i = 0; i < divs.length; i++) {
            divs[i].addEventListener('click', function () {
                divs[i].style.backgroundColor = 'pink';
            });
        }
        console.log(i); // 3
    });
    /*
    i 为当前作用域下的共享变量。
    当每次点击 div 的时候,各个点击事件共享 i 的值。
    此时 i = 3,这将报错。
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    正确实例:将以上代码中的 var 改为 let。

    案例2:1s 后循环输出所有数字
    错误示例:

    for (var i = 1; i <= 5; i++) {
        setTimeout(() => {
            console.log(i);
        }, 1000);
    }
    /*
    输出:6 6 6 6 6
    循环从1-5的时间很短暂,远不及 1s。
    此时五个异步事件瞬间加入到异步事件队列中,等待 1s后依次执行。
    而此时i为6,故瞬间输出 5 个 6。
    异步事件队头
    (1) console.log(i);
    (2) console.log(i);
    (3) console.log(i);
    (4) console.log(i);
    (5) console.log(i);
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    正确示例:

    for (let j = 1; j <= 5; j++) {
        setTimeout(() => {
            console.log(j);
        }, 1000);
    }
    // 输出:1 2 3 4 5
    // let 有块级作用域,每个 j 都会形成一个自己的块级作用域,与相应的异步事件共享:
    // {j = 1;} {j = 2;} {j = 3;} {j = 4;} {j = 5;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    解决方法2:

    // 给每一个 i 设置一个立即执行函数,会形成自己的块级作用域,不影响外部变量。
    for (var i = 1; i <= 5; i++) {
        (function (i) {
            setTimeout(() => {
                console.log(i);
            }, 1000);
        })(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2. const 关键字

    const 关键字用来声明常量,const 声明有以下特点:

    1、声明必须赋初始值

    const test; // Missing initializer in const declaration
    
    • 1

    2、标识符一般为大写(潜规则,小写也行)
    3、不允许重复声明
    4、值不允许修改,但对数组和对象的元素修改,不算做对常量的修改,不会报错,原因是地址没有发生改变
    5、块级作用域

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

    3. 解构赋值

    ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

    1、数组的解构赋值

    const arr = ['red', 'green', 'blue'];
    let [r, g, b] = arr;
    
    • 1
    • 2

    2、对象的解构赋值

    const obj = {
        uname: 'rick',
        age: 30,
        sayHi: function () {
            console.log('hello');
        },
        sayBye() {
            console.log('Bye~');
        }
    }
    let {name, age, sayHi} = obj;
    let {sayBye} = obj;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    4. 模板字符串

    模板字符串(template string)是增强版的字符串,用反引号 ` 标识,特点:

    1、字符串中可以出现换行符

    let ul = `
    • apple
    • banana
    • peach
    `
    • 1
    • 2
    • 3
    • 4
    • 5

    2、可以使用 ${xxx} 形式输出变量(这个${}是固定写法)

    let name = 'jack';
    console.log(`hello, ${name}`);
    
    • 1
    • 2

    应用场景:当遇到字符串与变量拼接的情况使用模板字符串。

    5. 简化对象写法

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

    let name = 'tianyang'; 
    let slogon = '一点一滴都是进步'; 
    let improve = function () { console.log('学习可以提高你的技能'); }
    //属性和方法简写
    let xuexi = { 
        name,//name:name的简化
        slogon,
        improve(){console.log('学习可以提高你的技能');}//improve:function () { console.log('学习可以提高你的技能'); }的简化
       
    };
    console.log(xuexi)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6. 箭头函数

    ES6 允许使用() => {} 定义函数。

    function 写法

    function fn(param1, param2,, paramN) { 
        // 函数体
        return expression; 
    }
    
    • 1
    • 2
    • 3
    • 4

    箭头函数写法

    let fn = (param1, param2,, paramN) => {
        // 函数体
        return expression;
    }
    
    • 1
    • 2
    • 3
    • 4

    箭头函数的 注意点:

    1、如果形参只有一个,则小括号可以省略
    2、函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
    3、箭头函数 this 始终指向声明时所在作用域下 this 的值
    4、箭头函数不能作为构造函数实例化
    5、不能使用 arguments

    // 省略小括号
    let fn1 = n => {
        return n * n;
    }
    
    // 省略花括号
    let fn2 = (a + b) => a + b;    //return也不能写
    
    // 箭头函数 this 始终指向声明时所在作用域下 this 的值
    const obj = {
        a: 10,
        getA () {
            let fn3 = () => {
                console.log(this); // obj {...}
                console.log(this.a); // 10
            }
            fn3();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    案例1:箭头函数 this 始终指向声明时所在作用域下 this 的值,call 等方法无法改变其指向。

    let obj = {
        uname: 'rick',
        age: 30
    };
    let foo = () => {
        console.log(this);
    }
    let bar = function () {
        console.log(this);
    }
    // call 对箭头函数无效
    foo.call(obj); // window
    bar.call(obj); // obj {...}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    案例2:筛选偶数

    let arr = [2, 4, 5, 10, 12, 13, 20];
    let res = arr.filter(v => v % 2 === 0);
    console.log(res); // [2, 4 , 10, 12, 20]
    
    • 1
    • 2
    • 3

    案例3:点击 div两秒后变成粉色

    方案1:使用 _this 保存 div 下的 this,从而设置 div 的 style 属性。

    div.addEventListener('click', function () {
        let _this = this;  //不这样做this就指向window,现在指向div
        setTimeout(function () {
            console.log(_this); // 
    ...
    _this.style.backgroundColor = 'pink'; }, 2000) });
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    方案2:使用 => 箭头函数

    div.addEventListener('click', function () {
        setTimeout(() => {
            console.log(this); // 
    ...
    this.style.backgroundColor = 'pink'; }, 2000); });
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    7. 函数参数默认值设定

    ES6 允许给函数参数设置默认值,当调用函数时不给实参,则使用参数默认值。

    具有默认值的形参,一般要靠后。

    let add = (x, y, z=3) => x + y + z;
    console.log(add(1, 2)); // 6
    
    • 1
    • 2

    可与解构赋值结合:

    function connect({ host = '127.0.0.1', uesername, password, port }) {
        console.log(host); // 127.0.0.1
        console.log(uesername);
        console.log(password);
        console.log(port);
    }
    connect({
        // host: 'docs.mphy.top',
        uesername: 'root',
        password: 'root',
        port: 3306
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    8. rest 参数

    ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments,作用与 arguments 类似。将接收的参数序列转换为一个数组对象。

    用在函数形参中,语法格式:fn(a, b, ...args),写在参数列表最后面。

    let fn = (a, b, ...args) => {
        console.log(a);
        console.log(b);
        console.log(args);
    };
    fn(1,2,3,4,5);
    /*
    1
    2
    [3, 4, 5]   返回的是一个数组,而arguments返回的是对象
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    案例1:求不定个数数字的和

    let add = (...args) => {
    //reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数
    //接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组
        let sum = args.reduce((pre, cur) => pre + cur, 0);
        return sum;
    }
    console.log(add(1, 2, 3, 4, 5)); // 15
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    应用场景:rest 参数非常适合不定个数参数函数的场景

    9. spread 扩展运算符

    扩展运算符(spread)也是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。可用在调用函数时,传递的实参,将一个数组转换为参数序列。

    扩展运算符也可以将对象解包。

     let arr = [1, 2, 3];
     /*...arr*/  // 1, 2, 3
     console.log(...arr);    // 1 2 3
     //等同于
     console.log(1, 2, 3)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    案例1:数组合并

    // 方法一 
    let A = [1, 2, 3];
    let B = [4, 5, 6];
    let C = [...A, ...B];
    console.log(C); // [1, 2, 3, 4, 5, 6]
    // 方法二 
    A.push(...B);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    案例2:数组克隆
    这种数组克隆属于浅拷贝。

    let arr1 = ['a', 'b', 'c'];
    let arr2 = [...arr1];
    console.log(arr2); // ['a', 'b', 'c']
    
    • 1
    • 2
    • 3

    案例3:将伪数组转换为真实数组

    const divs = document.querySelectorAll('div');   //是一个对象
    let divArr = [...divs];  //变成了数组
    console.log(divArr);
    
    • 1
    • 2
    • 3

    案例4:对象合并

    // 合并对象
    let obj1 = {
        a: 123
    };
    let obj2 = {
        b: 456
    };
    let obj3 = {
        c: 789
    };
    let obj = { ...obj1, ...obj2, ...obj3 };
    console.log(obj);
    // { a: 123, b: 456, c: 789 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10. Symbol数据类型

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

    JavaScript 的七种基本数据类型:

    1、值类型(基本类型):string、number、boolean、undefined、null、symbol
    2、引用数据类型:object(包括了array、function)

    Symbol 的特点:

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

    1、Symbol的创建方式:

    //创建一个 Symbol
    let s1 = Symbol();
    console.log(s1, typeof s1);    // Symbol() symbol
    
    //添加具有标识的 Symbol 
    let s2 = Symbol('1');
    let s2_1 = Symbol('1');
    console.log(s2 === s2_1);    // false  Symbol 都是独一无二的
    
    //使用 Symbol.for() 方法创建,名字相同的 Symbol 具有相同的实体。
    let s3 = Symbol.for('apple');
    let s3_1 = Symbol.for('apple');
    console.log(s3 === s3_1); // true
    
    //输出 Symbol 变量的描述,使用 description 属性
    let s4 = Symbol('测试');
    console.log(s4.description); // 测试
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    11. 对象添加 Symbol 类型的属性

    案例:安全的向对象中添加属性和方法。
    分析:如果直接向对象中添加属性或方法,则原来对象中可能已经存在了同名属性或方法,会覆盖掉原来的。所以使用 Symbol 生成唯一的属性或方法名,可以更加安全的添加。

    代码实现:

    // 这是一个 game 对象,假设我们不知道里面有什么属性和方法
    const game = {
        uname: '俄罗斯方块',
        up: function () { },
        down: function () { }
    }
    
    // 通过 Symbol 生成唯一的属性名,然后给 game 添加方法
    let [up, down] = [Symbol('up'), Symbol('down')];
    game[up] = function () {
        console.log('up');
    }
    game[down] = function () {
        console.log('down');
    }
    
    // 调用刚刚创建的方法
    game[up]();
    game[down]();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    12. Symbol 内置值

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

    在这里插入图片描述
    案例1:Symbol.hasInstance 方法判断是否属于这个对象时被调用。

    class A {
        static [Symbol.hasInstance]() {
            console.log('判断是否属于这个对象时被调用');
        }
    }
    let obj = {};
    console.log(obj instanceof A
    // 判断是否属于这个对象时被调用
    // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    案例2:数组使用 concat 方法时,是否可以展开。

    let arr1 = [1, 2, 3];
    let arr2 = [4, 5, 6];
    let arr3 = [4, 5, 6];
    arr2[Symbol.isConcatSpreadable] = false;
    console.log(arr1.concat(arr2));
    // [ 1, 2, 3, [ 4, 5, 6, [Symbol(Symbol.isConcatSpreadable)]: false ] ]
    console.log(arr1.concat(arr3));
    // [ 1, 2, 3, 4, 5, 6 ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    13. 迭代器

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

    1、ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
    2、原生具备 iterator 接口的数据(可用for of 遍历)

    1.Array
    2.Arguments
    3.Set
    4.Map
    5.String
    6.TypedArray
    7.NodeList
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

    自定义遍历数据
    我们可以通过给数据结构添加自定义 [Symbol.iterator]() 方法来使该数据结构能够直接被遍历,从而使 for...of 能够直接遍历指定数据,达到为 for...of 服务的功能。

    // 需求:遍历对象中的数组
    const xiaomi = {
        uname: '小明',
        course: [ '高数', '大物', '英语', '数据库' ],
        // 通过自定义 [Symbol.iterator]() 方法
        [Symbol.iterator]() {
            // 初始指针对象指向数组第一个
            let index = 0;
            // 保存 xiaomi 的 this 值
            let _this = this;
            return {
                next: function () {
                    // 不断调用 next 方法,直到指向最后一个成员
                    if (index < _this.course.length) {
                        return { value: _this.course[index++], done: false };
                    } else {
                        // 每调用next 方法返回一个包含value 和done 属性的对象
                        return { value: undefined, done: true };
                    }
                }
            }
        }
    }
    // for...of直接遍历达到目的
    for (let v of xiaomi) {
        console.log(v);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    14. Generator 生成器函数

    生成器函数是 ES6 提供的一种 异步编程解决方案,语法行为与传统函数完全不同。

    1、* 的位置没有限制
    2、使用 function * gen()yield 可以声明一个生成器函数。生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值。
    3、每一个 yield 相当于函数的暂停标记,也可以认为是一个分隔符,每调用一次 next(),生成器函数就往下执行一段。
    4、next 方法可以传递实参,作为 yield 语句的返回值

    例如以下生成器函数中,3 个 yield 语句将函数内部分成了 4 段。

    function* generator() {
        console.log('before 111'); // 生成器第 1 段
        yield 111;
        console.log('before 222'); // 生成器第 1 段
        yield 222;
        console.log('before 333'); // 生成器第 1 段
        yield 333;
        console.log('after 333'); // 生成器第 1 段
    }
    let iter = generator();
    console.log(iter.next());
    console.log(iter.next());
    console.log(iter.next());
    console.log(iter.next());
    /*
    before 111
    { value: 111, done: false }
    before 222
    { value: 222, done: false }
    before 333
    { value: 333, done: false }
    after 333
    { value: undefined, done: true }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    生成器函数的参数传递

    function* generator(arg) {
        console.log(arg); // 生成器第 1 段
        let one = yield 111;
        console.log(one); // 生成器第 2 段
        let two = yield 222;
        console.log(two); // 生成器第 3 段
        let three = yield 333; 
        console.log(three); // 生成器第 4 段
    }
    
    let iter = generator('aaa'); // 传给生成器第 1 段
    console.log(iter.next());
    console.log(iter.next('bbb')); // 传给生成器第 2 段,作为这一段开始的 yield 语句返回值
    console.log(iter.next('ccc')); // 传给生成器第 3 段,作为这一段开始的 yield 语句返回值
    console.log(iter.next('ddd')); // 传给生成器第 4 段,作为这一段开始的 yield 语句返回值
    /*
    aaa
    { value: 111, done: false }
    bbb
    { value: 222, done: false }
    ccc
    { value: 333, done: false }
    ddd
    { value: undefined, done: true }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    15. 生成器函数案例

    案例1:1s后输出111,2s后输出222,3s后输出333

    传统方式:嵌套太多,代码复杂,产生回调地狱。

    setTimeout(() => {
        console.log(111);
        setTimeout(() => {
            console.log(222);
            setTimeout(() => {
                console.log(333);
            }, 3000);
        }, 2000);
    }, 1000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    生成器实现:结构简洁明了

    function one() {
        setTimeout(() => {
            console.log(111);
            iter.next();
        }, 1000);
    }
    
    function two() {
        setTimeout(() => {
            console.log(222);
            iter.next();
        }, 2000);
    }
    
    function three() {
        setTimeout(() => {
            console.log(333);
            iter.next();
        }, 3000);
    }
    
    function* generator() {
        yield one();
        yield two();
        yield three();
    }
    
    let iter = generator();
    iter.next();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    案例2:生成器函数模拟每隔1s获取商品数据

    function getUsers() {
        setTimeout(() => {
            let data = '用户数据';
            iter.next(data); // 传参给生成器函数的第 2 段,后面类似
        }, 1000);
    }
    
    function getOrders() {
        setTimeout(() => {
            let data = '订单数据';
            iter.next(data);
        }, 1000);
    }
    
    function getGoods() {
        setTimeout(() => {
            let data = '商品数据';
            iter.next(data);
        }, 1000);
    }
    
    function* generator() {
        let users = yield getUsers();
        console.log(users);
        let orders = yield getOrders();
        console.log(orders);
        let goods = yield getGoods();
        console.log(goods);
    }
    
    let iter = generator();
    iter.next();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    16. Promise

    1、Promise 的定义和使用

    Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

    一个 Promise 必然处于以下几种状态之一:

    1、待定(pending):初始状态,既没有被兑现,也没有被拒绝。
    2、已兑现(fulfilled):意味着操作成功完成。
    3、已拒绝(rejected):意味着操作失败。

    Promise 的使用:

    1、Promise 构造函数:new Promise((resolve, reject)=>{})
    2、Promise.prototype.then 方法
    3、Promise.prototype.catch 方法

    一个简单案例:

    let p = new Promise(function (resolve, reject) {
        // 使用 setTimeout 模拟请求数据库数据操作
        setTimeout(function () {
            // 这个异步请求数据库数据操作是否正确返回数据
            let isRight = true;
            if (isRight) {
                let data = '数据库中的数据';
                // 设置 Promise 对象的状态为操作成功
                resolve(data);
            } else {
                let err = '数据读取失败!'
                // 设置 Promise 对象的状态为操作失败
                reject(err);
            }
        }, 1000);
    });
    p.then(function (value) {
        console.log(value);
    }, function (reason) {
        console.error(reason);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2、Promise 封装读取文件

    // 使用 nodejs 的 fs 读取文件模块
    const fs = require('fs');
    
    const p = new Promise(function (resolve, reject) {
        fs.readFile('./resources/为学.txt', (err, data) => {
            // err 是一个异常对象
            if (err) reject(err);
            // 如果成功
            resolve(data);
        })
    })
    
    p.then(function (value) {
        // 转为字符串输出
        console.log(value.toString());
    }, function (reason) {
        console.log('读取失败!!');
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、Promise 封装 Ajax 请求

    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('get', 'https://api.apiopen.top/getJoke');
        xhr.send();
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 成功
                    resolve(xhr.response);
                } else {
                    // 失败
                    reject(xhr.status);
                }
            }
        }
    });
    
    // 指定回调
    p.then(function (value) {
        console.log(value);
    }, function (reason) {
        console.error(reason);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4、Promise.prototype.then 方法

    先复习一下一个 Promise 的三种状态:

    1、待定(pending):初始状态,既没有被兑现,也没有被拒绝。
    2、已兑现(fulfilled):意味着操作成功完成。
    3、已拒绝(rejected):意味着操作失败。

    Promise.prototype.then 方法返回的结果依然是 Promise 对象,对象状态由回调函数的执行结果决定。

    具体情况如下:
    1、若 then 方法没有返回值,则 then 方法返回的对象的状态值为成功 fulfilled,返回结果值为 undefined

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve('用户数据')
            reject('出错了');
        }, 1000);
    })
    // 未设定返回值
    const res = p.then((value) => {
        console.log(value);
    }, (reason) => {
        console.warn(reason);
    })
    // 打印 then 方法的返回值
    console.log(res);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、如果回调函数中返回的结果是非 Promise 类型的属性,则 then 方法返回的对象,其状态为成功(fulfilled),返回结果值取决于 then 方法所执行的是哪个函数(resolve 或 reject)。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve('用户数据')
            reject('出错了');
        }, 1000);
    })
     // 返回的非 Promise 对象
    const res = p.then((value) => {
        console.log(value);
        return '成功了!!';
    }, (reason) => {
        console.warn(reason);
        return '出错啦!!'
    })
    // 打印 then 方法的返回值
    console.log(res);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、如果回调函数中返回的结果是 Promise 类型(return new Promise()),则 then 方法返回的 Promise 对象状态与该返回结果的状态相同,返回值也相同。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('用户数据')
            // reject('出错了');
        }, 1000);
    })
    const res = p.then((value) => {
        console.log(value);
        // 返回 Promise 对象
        return new Promise((resolve, reject) => {
            resolve('(1)成功了!!!');
            // reject('(1)出错了!!!')
        })
    }, (reason) => {
        console.warn(reason);
        return new Promise((resolve, reject) => {
            // resolve('(2)成功了!!!');
            reject('(2)出错了!!!')
        })
    })
    // 打印 then 方法的返回值
    console.log(res);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4、如果回调函数中返回的结果是 throw 语句抛出异常,则 then 方法的对象的状态值为 rejected,返回结果值为 throw 抛出的字面量或者 Error 对象。

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('用户数据');
        }, 1000);
    });
    const res = p.then((value) => {
        console.log(value);
        return new Promise((resolve, reject) => {
            throw new Error('错误了!!');
        })
    });
    // 打印结果
    console.log(res);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5、链式调用

    Promise.prototype.then 方法返回的结果还是 Promise 对象,这意味着我们可以继续在该结果上使用 then 方法,也就是链式调用。

    const p = new Promise(resolve=>{}, reject=>{});
    p.then(value=>{}, reason=>{})
    .then(value=>{}, reason=>{})
    .then(value=>{}, reason=>{})
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6、链式调用练习-多个文件读取

    const fs = require('fs');
    
    let p = new Promise((resolve, reject) => {
        fs.readFile('./resources/users.md', (err, data) => {
            // 传给下一轮文件读取操作
            resolve(data);
        })
    });
    
    p.then(value => {
        return new Promise((resolve, reject) => {
            // value 为第一次读取的文件数据,data 为第二次(当前)读取的数据
            fs.readFile('./resources/orders.md', (err, data) => {
                // 将上轮读取结果和本轮合并传到下一轮轮读取操作
                resolve([value, data]);
            });
        });
    }).then(value => {
        return new Promise((resolve, reject) => {
            fs.readFile('./resources/goods.md', (err, data) => {
                // value 为上一轮传递过来的文件数据数组
                value.push(data);
                // 传给下一轮操作
                resolve(value);
            });
        });
    }).then(value => {
        // 合并数组元素,输出
        console.log(value.join('\n'));
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    7、Promise.prototype.catch

    catch() 方法返回一个 Promise,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。
    即:

    obj.catch(onRejected);
    
    • 1

    等同于:

    obj.then(undefined, onRejected);
    
    • 1

    语法:

    p.catch(onRejected);
    
    p.catch(function(reason) {
       // 拒绝
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    举例:

    var p1 = new Promise(function (resolve, reject) {
        resolve('Success');
    });
    
    p1.then(function (value) {
        console.log(value); // "Success!"
        throw 'oh, no!';
    }).catch(function (e) {
        console.log(e); // "oh, no!"
    }).then(function () {
        console.log('有 catch 捕获异常,所以这句输出');
    }, function () {
        console.log('没有 catch 捕获异常,这句将不会输出');
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出结果:

    Success
    oh, no!
    有 catch 捕获异常,所以这句输出
    
    • 1
    • 2
    • 3
  • 相关阅读:
    基于LLVM13 Enzyme 安装
    采集EtherNET/IP转Profinet在西门子plc中的应用
    MyBatis开发的详细步骤
    postgresql简单sql
    使用drawio的图层构建更强大的图表
    前端表单滑块验证码开发
    LeetCode 热题 HOT 100 第五十八天 226. 翻转二叉树 简单题 用python3求解
    【VMware虚拟机使用记录】—— 虚拟机开启失败的问题分析及解决方法
    #LLM入门|Prompt#1.9_总结_Summary
    数据抓取使用爬虫ip常见问题解决方法
  • 原文地址:https://blog.csdn.net/weixin_55608297/article/details/128087829