目录

1. let 关键字

2. const 关键字

3. 变量的解构赋值

4. 模板字符串

5. 简化对象写法

6. 箭头函数

7. 函数参数默认值设定

8. rest 参数

9. spread 扩展运算符

10. Symbol

10.1 Symbol 基本介绍与使用

10.2 对象添加 Symbol 类型的属性

10.3 Symbol 内置值

11. 迭代器

11.1 定义

11.2 工作原理

11.3 自定义遍历数据

12. Generator 生成器函数

12.1 生成器函数的声明和调用

12.2 生成器函数的参数传递

12.3 生成器函数案例

13. Promise

13.1 Promise 的定义和使用

13.2 Promise 封装读取文件

13.3 Promise 封装 Ajax 请求

13.4 Promise.prototype.then 方法

13.4 链式调用

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

13.6 Promise.prototype.catch

14. Set

14.1 Set 的定义和使用

14.2 集合案例

15. Map

16. class 类

16.1 复习 ES5 function 构造函数 的继承

16.2 extends 类继承和方法的重写

16.3 getter 和 setter

17. 数值扩展

18. 对象方法扩展

18.1 Object.is()

18.2 Object.assign

18.3 Object.setPrototypeOf 和 Object.getPrototypeof

19.ES6 模块化

19.1 模块化的好处

19.2 模块化规范产品

19.3 ES6 模块化语法

19.3.1 模块导出数据语法

19.3.2 模块导入数据语法

19.3.3 ES6 使用模块化方式二

19.4 使用 babel 对模块化代码转换

19.5 ES6 模块化引入 npm 安装的包



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

  • 不允许重复声明
  • 块级作用域
  • 不存在变量提升
  • 不影响作用域链

应用场景:以后声明变量使用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,这将报错。
    */
    
  • 正确实例:将以上代码中的 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);
    */
    
  • 正确示例:

    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;}
    
  • 解决方法2:

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

2. const 关键字

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

  • 声明必须赋初始值
  • 标识符一般为大写
  • 不允许重复声明
  • 值不允许修改
  • 块级作用域

注意:对象属性修改和数组元素变化不会出发 const 错误
应用场景:声明对象类型使用 const,非对象类型声明选择 let

案例:

const arr = [1, 2, 3, 4];
arr.push(5, 6);
console.log(arr);
// 不报错
const obj = {
    uname: 'rick',
    age: 30
}
obj.age = 40;
// 只要不改变地址,就不报错

3. 变量的解构赋值

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

数组的解构赋值:

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

对象的解构赋值:

const obj = {
    uname: 'rick',
    age: 30,
    sayHi: function () {
        console.log('hello');
    },
    sayBye() {
        console.log('Bye~');
    }
}
let {name, age, sayHi} = obj;
let {sayBye} = obj;

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

4. 模板字符串

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

  • 字符串中可以出现换行符
  • 可以使用 ${xxx} 形式输出变量
let name = 'jack';
console.log(`hello, ${name}`);
let ul = `<ul>
    <li>apple</li>
    <li>banana</li>
    <li>peach</li>
    </ul>`

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

5. 简化对象写法

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

let uname = 'rick';
let age = 30;
let sayHi = function () {
    console.log('Hi');
}
// 组建对象
const obj = {
    uname,
    age,
    sayHi() {
        console.log('Hi');
    }
}Copy to clipboardErrorCopied

应用场景:对象简写形式简化了代码,所以以后用简写就对了。

6. 箭头函数

ES6 允许使用「箭头」=> 定义函数。

  • function 写法:
    function fn(param1, param2,, paramN) { 
        // 函数体
        return expression; 
    }Copy to clipboardErrorCopied
  • => 写法:
    let fn = (param1, param2,, paramN) => {
        // 函数体
        return expression;
    }
    

箭头函数的 注意点:

  • 如果形参只有一个,则小括号可以省略
  • 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
  • 箭头函数 this 始终指向声明时所在作用域下 this 的值
  • 箭头函数不能作为构造函数实例化
  • 不能使用 arguments
// 省略小括号
let fn1 = n => {
    return n * n;
}

// 省略花括号
let fn2 = (a + b) => a + b;

// 箭头函数 this 始终指向声明时所在作用域下 this 的值
const obj = {
    a: 10,
    getA () {
        let fn3 = () => {
            console.log(this); // obj {...}
            console.log(this.a); // 10
        }
        fn3();
    }

}

案例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 {...}

案例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]

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

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

    div.addEventListener('click', function () {
        let _this = this;
        setTimeout(function () {
            console.log(_this); // <div>...<div>
            _this.style.backgroundColor = 'pink';
        }, 2000)
    });
    
  • 方案2:使用 => 箭头函数

    div.addEventListener('click', function () {
        setTimeout(() => {
            console.log(thid); // <div>...</div>
            this.style.backgroundColor = 'pink';
        }, 2000);
    });
    

7. 函数参数默认值设定

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

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

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

可与解构赋值结合:

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
})

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]
*/

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

let add = (...args) => {
    let sum = args.reduce((pre, cur) => pre + cur, 0);
    return sum;
}
console.log(add(1, 2, 3, 4, 5)); // 15

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

9. spread 扩展运算符

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

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

展开数组:

function fn(a, b, c) {
    console.log(arguments);
    console.log(a + b + c);
}
let arr = ['red', 'green', 'blue']; 
fn(...arr)
// [Arguments] { '0': 'red', '1': 'green', '2': 'blue' }
// redgreenblue

案例1:数组合并

let A = [1, 2, 3];
let B = [4, 5, 6];
let C = [...A, ...B];
console.log(C); // [1, 2, 3, 4, 5, 6]

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

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

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

const divs = document.querySelectorAll('div');
let divArr = [...divs];
console.log(divArr);

案例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 }

10. Symbol

10.1 Symbol 基本介绍与使用

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

JavaScript 的七种基本数据类型:

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

Symbol 的特点:

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

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); // 测试
    

10.2 对象添加 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]();

10.3 Symbol 内置值

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

案例1:Symbol.hasInstance 方法判断是否属于这个对象时被调用。

class A {
    static [Symbol.hasInstance]() {
        console.log('判断是否属于这个对象时被调用');
    }
}
let obj = {};
console.log(obj instanceof A
// 判断是否属于这个对象时被调用
// false

案例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 ]

11. 迭代器

11.1 定义

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

  • ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
  • 原生具备 iterator 接口的数据(可用for of 遍历)
    • Array
    • Arguments
    • Set
    • Map
    • String
    • TypedArray
    • NodeList

案例:使用 next() 方法遍历原生自带 iterator 接口的数据:

// 遍历 Map
const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);
let iter1 = mp[Symbol.iterator]();
// next() 方法每执行一次,指针自增
console.log(iter1.next()); // { value: [ 'a', 1 ], done: false }
console.log(iter1.next()); // { value: [ 'b', 2 ], done: false }
console.log(iter1.next()); // { value: [ 'c', 3 ], done: false }
console.log(iter1.next()); // { value: undefined, done: true }
// 遍历数组
let xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
let iter2 = xiyou[Symbol.iterator]();
console.log(iter2.next()); // { value: '唐僧', done: false }
console.log(iter2.next()); // { value: '孙悟空', done: false }
console.log(iter2.next()); // { value: '猪八戒', done: false }
console.log(iter2.next()); // { value: '沙僧', done: false }

上面的案例只是为了证明他们自带 iterator 接口,实际上直接使用 for...of 方法遍历即可(iterator 接口为 for...of)服务。例如,可以使用 for [k, v] of map 来遍历 Map 数据结构中的键和值。

const mp = new Map();
mp.set('a', 1);
mp.set('b', 2);
mp.set('c', 3);
for (let [k, v] of mp) {
    console.log(k, v);
}
/*
a 1
b 2
c 3
*/

11.2 工作原理

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

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

11.3 自定义遍历数据

我们可以通过给数据结构添加自定义 [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);
}

12. Generator 生成器函数

12.1 生成器函数的声明和调用

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

  • * 的位置没有限制
  • 使用 function * gen() 和 yield 可以声明一个生成器函数。生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值。
  • 每一个 yield 相当于函数的暂停标记,也可以认为是一个分隔符,每调用一次 next(),生成器函数就往下执行一段。
  • 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 }
*/

12.2 生成器函数的参数传递

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 }
*/

12.3 生成器函数案例

案例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);
            iter.next();
        }, 1000);
    }
    
    function two() {
        setTimeout(() => {
            console.log(222);
            iter.next();
        }, 2000);
    }
    
    function three() {
        setTimeout(() => {
            console.log(333);
        }, 3000);
    }
    
    function* generator() {
        yield one();
        yield two();
        yield three();
    }
    
    let iter = generator();
    iter.next();
    

案例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();

13. Promise

13.1 Promise 的定义和使用

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

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

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

Promise 的使用:

  • Promise 构造函数:new Promise((resolve, reject)=>{})
  • Promise.prototype.then 方法
  • 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);
})

13.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('读取失败!!');
})

13.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);
})

13.4 Promise.prototype.then 方法

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

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

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

具体情况如下:

  • 如果回调函数中返回的结果是 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);
    
  • 如果回调函数中返回的结果是 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);
    

13.4 链式调用

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

const p = new Promise(resolve=>{}, reject=>{});
p.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
.then(value=>{}, reason=>{})
...

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

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'));
});

13.6 Promise.prototype.catch

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

即:

obj.catch(onRejected);

等同于:

obj.then(undefined, onRejected);

语法:

p.catch(onRejected);

p.catch(function(reason) {
   // 拒绝
});

举例:

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 捕获异常,这句将不会输出');
});

输出结果:

Success
oh, no!
有 catch 捕获异常,所以这句输出

14. Set

14.1 Set 的定义和使用

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

定义一个 Set 集合:

let st1 = new Set();
let st2 = new Set([可迭代对象]);

集合(这里假设有一个集合 st)的属性和方法:

  • st.size:返回集合个数
  • st.add(item):往集合中添加一个新元素 item,返回当前集合
  • st.delete(item):删除集合中的元素,返回 boolean 值
  • st.has(item):检测集合中是否包含某个元素,返回 boolean 值
  • st.clear():清空集合
  • 集合转为数组:[...st]
  • 合并两个集合:[...st1, ...st2]

14.2 集合案例

案例1: 数组去重

let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
let res1 = [...new Set(arr1)];
console.log(res1); // [ 1, 2, 3, 4 ]

案例2:数组求交集

let arr2_1 = [1, 2, 2, 3, 4, 5];
let arr2_2 = [3, 6, 6, 7, 1, 4];
let res2 = arr2_1.filter(v => new Set(arr2_2).has(v))
console.log(res2); // [ 1, 3, 4 ]

案例3:数组求并集

let arr3_1 = [1, 2, 2, 3, 4, 5];
let arr3_2 = [3, 6, 6, 7, 1, 4];
let res3 = [...new Set([...arr3_1, ...arr3_2])];
console.log(res3); // [ 1, 2, 3, 4, 5, 6, 7 ]Copy to clipboardErrorCopied

案例4:数组求差集

let arr4_1 = [1, 2, 2, 3, 4, 5];
let arr4_2 = [3, 6, 6, 7, 1, 4];
let res4 = [...new Set(arr4_1)].filter(v => !(new Set(arr4_2).has(v)))
console.log(res4); // [ 2, 5 ]

15. Map

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

定义一个 Map:

let mp1 = new Map();
mp1.set('aaa', 111);
mp1.set('bbb', 222);
mp1.set('ccc', 333);

let mp2 = new Map([
    ['aaa', 111],
    ['bbb', 222],
    ['ccc', 333]
]);

console.log(mp1['aaa']); // 111
console.log(mp2.get('bbb')); // 222

Map 的属性和方法:(k 为键,v为值)

  • size:返回 Map 的元素(键值对)个数
  • set(k, v):增加一个键值对,返回当前 Map
  • get(k):返回键值对的键值
  • has():检测 Map 中是否包含某个元素
  • clear():清空集合,返回 undefined

16. class 类

这部分在 JS 高级也涉及,故可以前往 JS 高阶 class 学习。,那部分的笔记更加详细,有原理。所以这一节后面部分只给出例子。

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

16.1 复习 ES5 function 构造函数 的继承

//手机
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;
// 矫正 constructor 指向
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);

16.2 extends 类继承和方法的重写

ES6 中直接使用 extends 语法糖(更简洁高级的实现方式)来实现继承,同时可以重写父类的方法,直接在子类中重新写一次要重写的方法即可覆盖父类方法。

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("拍照");
    }

    // 方法的重写
    call(){
        console.log('我可以进行视频通话');
    }
}

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

16.3 getter 和 setter

实际上,getter 和 setter 是 ES5(ES2009)提出的特性,这里不做详细说明,只是配合 class 使用举个例子。
当属性拥有 get/set 特性时,属性就是访问器属性。代表着在访问属性或者写入属性值时,对返回值做附加的操作。而这个操作就是 getter/setter 函数。

使用场景: getter 是一种语法,这种 get 将对象属性绑定到 查询该属性时将被调用的函数。适用于某个需要动态计算的成员属性值的获取。setter 则是在修改某一属性时所给出的相关提示。

class Test {
    constructor(log) {
        this.log = log;
    }
    get latest() {
        console.log('latest 被调用了');
        return this.log;
    }
    set latest(e) {
        console.log('latest 被修改了');
        this.log.push(e);
    }
}

let test = new Test(['a', 'b', 'c']);
// 每次 log 被修改都会给出提示
test.latest = 'd';
// 每次获取 log 的最后一个元素 latest,都能得到最新数据。
console.log(test.latest);

以上输出:

latest 被修改了
latest 被调用了
[ 'a', 'b', 'c', 'd' ]

17. 数值扩展

  1. Number.EPSILON 是 JavaScript 表示的最小精度,一般用来处理浮点数运算。例如可以用于两个浮点数的比较。

    let equal = (x, y) => Math.abs(x - y) < Number.EPSILON;
    console.log(0.1 + 0.2 === 0.3); // false
    console.log(equal(0.1 + 0.2, 0.3)); // true
    
  2. 二进制和八进制:二进制以 0b 开头,八进制以 0o 开头。

    let b = 0b1010;
    let o = 0o777;
    let d = 100;
    let x = 0xff;
    console.log(x);
    
  3. Number.isFinite 检测一个数值是否为有限数。

    console.log(Number.isFinite(100)); // false
    console.log(Number.isFinite(100 / 0)); // true
    console.log(Number.isFinite(Infinity)); // false
    
  4. Number.parseInt 和 Number.parseFloat
    ES6 给 Number 添加了 parseInt 方法,Number.parseInt 完全等同于 parseInt。将字符串转为整数,或者进行进制转换。Number.parseFloat 则等同于 parseFloat()

    Number.parseInt === parseInt; // true
    Number.parseFloat === parseFloat; // true
    
    Number.parseInt(s, base);
    
    • s:待转换的字符串
    • base:进位制的基数
      console.log(Number.parseInt('5211314love')); // 5211314
      console.log(Number.parseFloat('3.1415926神奇')); // 3.1415926
      
  5. Number.isInteger() 判断一个数是否为整数。

    console.log(Number.isInteger(5)); // true
    console.log(Number.isInteger(2.5)); // false
    
  6. Math.trunc() 将数字的小数部分抹掉。

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

18. 对象方法扩展

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

  • Object.is 比较两个值是否严格相等,与『===』行为 基本一致
  • Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
  • __proto__setPrototypeOfsetPrototypeOf 可以直接设置对象的原型

18.1 Object.is()

Object.is() 方法判断两个值是否完全相同。Object.is 比较两个值是否严格相等,与 === 行为 基本一致。返回一个 Boolean 类型。

Object.is(value1, value2);

Object.is() 方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:

  • 都是 undefined
  • 都是 null
  • 都是 true 或 false
  • 都是相同长度的字符串且相同字符按相同顺序排列
  • 都是相同对象(意味着每个对象有同一个引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零而且非 NaN 且为同一个值

与 == 运算不同。 == 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换 (这种行为的结果会将 "" == false 判断为 true),而 Object.is 不会强制转换两边的值。

与 ===算也不相同。 === 运算符 (也包括 == 运算符) 将数字 -0 和 +0 视为相等,而将 Number.NaN 与 NaN 视为不相等。

18.2 Object.assign

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));

18.3 Object.setPrototypeOf 和 Object.getPrototypeof

Object.setPrototypeOf 用于设置对象的原型对象,Object.getPrototypeof 用于获取对象的原型对象,相当于 __proto__

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

19.ES6 模块化

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

19.1 模块化的好处

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

  • 防止命名冲突
  • 代码复用
  • 高维护性

19.2 模块化规范产品

ES6 之前的模块化规范有:

  • CommonJS => NodeJS、Browserify
  • AMD => requireJS
  • CMD => seaJS

19.3 ES6 模块化语法

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

  • export 命令用于规定模块的对外接口
  • import 命令用于输入其他模块提供的功能
19.3.1 模块导出数据语法
  1. 单个导出
    // 单个导出
    export let uname = 'Rick';
    export let sayHello = function () {
        console.log('Hi, bro!');
    }
    
  2. 合并导出
    let uname = 'Rick';
    let sayHello = function () {
        console.log('Hi, bro!');
    }
    // 合并导出
    export { uname, sayHello };
    
  3. 默认导出
    // 默认导出
    export default {
        uname: 'Rick',
        sayHello: function () {
            console.log('Hi, bro!');
        }
    }
    
19.3.2 模块导入数据语法
  1. 通用导入方式

    import * as m1 from './js/m1.js';
    import * as m2 from './js/m2.js';
    import * as m3 from './js/m3.js';
    
  2. 解构赋值导入

    import { uname, sayHello } from './js/m1.js';
    // 有重复名可以设置别名
    import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
    console.log(uname);
    // 配合默认导出
    import {default as m3} from "./src/js/m3.js";Copy to clipboardErrorCopied
  3. 简便方式导入,针对默认暴露

    import m3 from "./src/js/m3.js";
    
19.3.3 ES6 使用模块化方式二

将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下:

import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';

console.log(m1);
console.log(m2);
console.log(m3);

在 index.html 中引入 app.js 文件内容:

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

19.4 使用 babel 对模块化代码转换

有的浏览器不支持 ES6 语法,这时候就需要使用 babel 来将其转换成 ES5 等价语法。

  1. 安装工具
    npm i babel-cli babel-preset-env browserify(webpack) -D
    
  2. 编译
    npx babel src/js -d dist/js --presets=babel-preset-env
    
  3. 打包
    npx browserify dist/js/app.js -o dist/bundle.js
    

npm install jquery

再通过 import 导入即可。 

 

10-25 03:36