1. 简介

  1. ECMAScript、JavaScript、NodeJs,它们的区别是什么?

    • ECMAScript:简称ES,是一个语言标准(循环、判断、变量、数组等数据类型)
    • JavaScript:运行在浏览器端的语言,该语言使用ES标准。 ES + web api = JavaScript
    • NodeJs:运行在服务器端的语言,该语言使用ES标准。 ES + node api = NodeJs
    • 无论JavaScript,还是NodeJs,它们都是ES的超集(super set)
  2. ECMAScript有哪些关键的版本?

    • ES3.0: 1999
    • ES5.0: 2009
    • ES6.0: 2015, 从该版本开始,不再使用数字作为编号,而使用年份
    • ES7.0: 2016
  3. 为什么ES6如此重要?

    • ES6解决JS无法开发大型应用的语言层面的问题

2. 块级绑定

2-1. 使用var声明变量的问题

  1. 允许重复的变量声明:导致数据被覆盖
  2. 变量提升:怪异的数据访问、闭包问题
  3. 全局变量挂载到全局对象:全局对象成员污染问题

2-2. 使用let声明变量

ES6不仅引入乐let关键字用于解决变量声明的问题,同时引入了块级作用域的概念
块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域

  1. let声明的变量不会挂载到全局对象
  2. let声明的变量,不允许当前作用域范围内重复声明
  3. 在块级作用域中用let定义的变量,在作用域外不能访问
  4. 使用let不会有变量提升,因此,不能在定义let变量之前使用它
  • 底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access ‘a’ before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
  • 在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
  • 在循环中使用let声明的循环变量,在循环结束后会销毁

2-3. 使用const声明变量

  1. constlet完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。

    实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:

    • 根据经验,开发中的很多变量,都是不会更改,也不应该更改的
    • 后续的很多框架或者是第三方JS库,都要求数据不可变,使用常量可以一定程度上保证这一点
  2. 注意的细节:

    • 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
    • 常量的命名
      • 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
      • 普通的常量:使用和之前一样的命名即可
    • 在for循环中,循环变量不可以使用常量

3. 字符串和正则表达式

3-1. 更好的Unicode支持

  • 早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字。我们将一个16位的二进制编码叫做一个码元(Code Unit)
  • 后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)
  • ES6为了解决这个困扰,为字符串提供了方法:codePointAt,根据字符串码元的位置得到其码点
  • 同时,ES6为正则表达式添加了一个flag: u,如果添加了该配置,则匹配时,使用码点匹配

3-2. 更多的字符串API

以下均为字符串的实例(原型)方法

  • includes 判断字符串中是否包含指定的子字符串
  • startsWith 判断字符串中是否以指定的字符串开始
  • endsWith 判断字符串中是否以指定的字符串结尾
  • repeat 将字符串重复指定的次数,然后返回一个新字符串。

3-3. [扩展]正则中的粘连标记

标记名:y
含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置必须在lastIndex位置。

3-4. 模板字符串

  • 在ES6中,提供了模板字符串的书写,可以非常方便的换行和拼接,要做的,仅仅是将字符串的开始或结尾改为 ` 符号
  • 如果要在字符串中拼接js表达式,只需要在模板字符串中使用${JS表达式}

3-5. [扩展]模板字符串标记

在模板字符串书写之前,可以加上标记;
作用:可以检测变量,防止变量中存在恶意代码;

1
标记名`模板字符串`

标记是一个函数,函数参数如下:

  1. 参数1:被插值分割的字符串数组
  2. 后续参数:所有的插值

4. 函数

4-1. 参数默认值

  • 在书写形参时,直接给形参赋值,附的值即为默认值
  • 这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是undefined),则会自动使用默认值,注意传入:null不会使用默认值
1
2
3
fuction sum(a, b = 1, c = 1) {
return a + b + c;
};

4-2. [扩展]对arguments的影响

  • 只要给函数加上参数默认值,该函数会自动变量严格模式下的规则
  • 严格模式下arguments和形参是脱离的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// "use strict"

function test(a, b = 1) {
console.log("arugments", arguments[0], arguments[1]);
console.log("a:", a, "b:", b);
a = 3;
console.log("arugments", arguments[0], arguments[1]);
console.log("a:", a, "b:", b);
};

text(1, 2);
// arugments 1 2
// a: 1 b: 2
// arugments 1 2
// a: 3 b: 2

4-3. [扩展]留意暂时性死区

形参和ES6中的letconst声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区

1
2
3
4
5
6
7
function test(a = b, b) {
console.log(a, b);
};

test(undefined, 2);

//会报错,而且也不能在函数体内再次定义与形参同名变量

4-4. 剩余参数

ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。

1
2
3
function (...形参名) {

};

细节:

  1. 一个函数,仅能出现一个剩余参数
  2. 一个函数,如果有剩余参数,剩余参数必须是最后一个参数

arguments的缺陷:

  1. 如果和形参配合使用,容易导致混乱。严格模式下arguments和形参是脱离的
  2. 从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图

4-5. 展开运算符

使用方式:...要展开的东西

  1. 对数组展开 ES6
  2. 对对象展开 ES7

应用:
curry 柯理化,用户固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接收剩余的参数

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
function cal(a, b, c, d){
return a + b * c - d;
}

function curry(func, ...args){
reutrn function(...subArgs){
const allArgs = [...args, ...subArgs];
if(allArgs.length >= func.length){
//参数够了
return func(...allArgs);
}else{
//参数不够,继续固定
return curry(func, ...allArgs);
}
}
}

const newCal = curry(cal, 1, 2)

console.log(newCal(3, 4)) // 1+2*3-4
console.log(newCal(4, 5)) // 1+2*4-5
console.log(newCal(5, 6)) // 1+2*5-6
console.log(newCal(6, 7)) // 1+2*6-7

const newCal2 = newCal(8)

console.log(newCal2(9)); // 1+2*8-9

4-6. 明确函数的双重用途

ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用

1
2
3
new.target 
//该表达式,得到的是:如果没有使用new来调用函数,则返回undefined
//如果使用new调用函数,则得到的是new关键字后面的函数本身

作用:检测方法是否通过 new 来调用
ES6以前:无法检测用过 call 重定向进行的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(firstName, lastName) {
//判断是否是使用new的方式来调用的函数
if (!(this instanceof Person)) {
throw new Error("该函数没有使用new来调用")
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
const p1 = new Person("彼", "方");
console.log(p1)

const p2 = Person("彼", "方"); //该函数没有使用new来调用
console.log(p2);

const p3 = Person.call(p1, "彼", "方")
console.log(p3);

ES6新增API: new.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(firstName, lastName) {
//判断是否是使用new的方式来调用的函数
if (new.target === undefined) {
throw new Error("该函数没有使用new来调用")
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
const p1 = new Person("彼", "方");
console.log(p1);

const p2 = Person("彼", "方"); //该函数没有使用new来调用
console.log(p2);

const p3 = Person.call(p1, "彼", "方") //该函数没有使用new来调用
console.log(p3);

4-7. 箭头函数

回顾:this指向

  1. 通过对象调用函数,this指向对象
  2. 直接调用函数,this指向全局对象
  3. 如果通过new调用函数,this指向新创建的对象
  4. 如果通过applycallbind调用函数,this指向指定的数据
  5. 如果是DOM事件函数,this指向事件源

语法说明:

箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数

完整语法:

1
2
3
(参数1, 参数2, ...)=>{
//函数体
}

如果参数只有一个,可以省略小括号

1
2
3
参数 => {

}

如果箭头函数只有一条返回语句,可以省略大括号,和return关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
参数 => 返回值

// 返回一条语句时直接书写
const isOdd = num => num % 2 !== 0;
console.log(isOdd(3))
console.log(isOdd(4))

// 注意:返回对象时用小括号括起来
const sum = (a, b) => ({
a: a,
b: b,
sum: a + b
});
console.log(sum(3, 5))

注意细节:

  • 箭头函数的自身没有this,箭头函数的this是在定义时确定的而不是调用者;
  • 箭头函数中,不存在this、arguments、new.target,如果使用了,则使用的是函数外层的对应的this、arguments、new.target
1
2
3
4
5
6
7
8
9
10
const obj = {
method: function(){
const func = () => {
console.log(this) //obj
console.log(arguments) //method[234]
}
func()
}
}
obj.method(234);
  • 箭头函数没有原型
  • 箭头函数不能作用构造函数使用

应用场景:

  1. 临时性使用的函数,并不会刻意调用它,比如:
    1. 事件处理函数
    2. 异步处理函数:例如在 setTimeout 中使用避免 this 指向window
    3. 其他临时性的函数
  2. 为了绑定外层this的函数
  3. 在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
1
2
3
4
5
6
const numbers = [3, 7, 78, 3, 5, 345];

// 过滤偶数 -> 扩大2倍 -> 求和
const result = numbers.filter(num => num % 2 !== 0).map(num => num * 2).reduce((a, b) => a + b);

console.log(result);

5. 对象

5-1. 新增的对象字面量语法

  1. 成员速写

    如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function createUser(loginId, loginPwd, nickName) {
    const sayHello = function () {
    console.log("loginId", this.loginId, "nickname", this.nickName)
    }
    return {
    loginId,
    loginPwd,
    nickName,
    sayHello,
    id: Math.random()
    }
    }
  2. 方法速写

    对象字面初始化时,方法可以省略冒号和function关键字

    1
    2
    3
    4
    5
    6
    7
    const user = {
    name: "彼方",
    age: 1000,
    sayHello(){
    console.log(this.name, this.age)
    }
    }
  3. 计算属性名

    有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在ES6,可以使用中括号来表示该属性名是通过计算得到的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const prop1 = "name";
    const prop2 = "age";
    const prop3 = "sayHello";

    const user = {
    [prop1]: "彼方",
    [prop2]: 1000,
    [prop3](){
    console.log(this[prop1], this[prop2])
    }
    }

5-2. Object的新增API

注意 Object 是一个构造函数

  1. Object.is

    用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点

    1. NaN和NaN相等
    2. 2+0和-0不相等
    1
    2
    3
    4
    5
    console.log(NaN === NaN);        // false
    console.log(+0 === -0); // true

    console.log(Object.is(NaN, NaN)) // true
    console.log(Object.is(+0, -0)) // false
  2. Object.assign

    用于混合对象

    1
    2
    3
    4
    //将obj2的数据,覆盖到obj1,并且会对obj1产生改动,然后返回obj1
    const obj = Object.assign(obj1, obj2);

    const obj = Object.assign({}, obj1, obj2);
  3. Object.getOwnPropertyNames 的枚举顺序

    Object.getOwnPropertyNames方法之前就存在,只不过,官方没有明确要求,对属性的顺序如何排序,如何排序,完全由浏览器厂商决定

    ES6规定了该方法返回的数组的排序方式如下:

    • 先排数字,并按照升序排序
    • 再排其他,按照书写顺序排序
  4. Object.setPrototypeOf

    该函数用于设置某个对象的隐式原型

    比如:Object.setPrototypeOf(obj1, obj2)
    相当于:obj1.__proto__ = obj2

5-3. 面向对象简介

面向对象:一种编程思想,跟具体的语言无关

对比面向过程:

  • 面向过程:思考的切入点是功能的步骤
  • 面向对象:思考的切入点是对象的划分

5-4. 类:构造函数的语法糖

传统的构造函数的问题:

  1. 属性和原型方法定义分离,降低了可读性
  2. 原型成员可以被枚举
  3. 默认情况下,构造函数仍然可以被当作普通函数使用
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
//面向对象中,将 下面对一个对象的所有成员的定义,统称为类

//构造函数 构造器
function Animal(type, name, age, sex) {
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}

//定义实例方法(原型方法)
Animal.prototype.print = function () {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}

const a = new Animal("狗", "旺财", 3, "男");
a.print();

//可以遍历到原型上的 print 方法
for (const prop in a) {
console.log(prop);
}

类的特点:

  1. 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
  2. 类中的所有代码均在严格模式下执行
  3. 类的所有方法都是不可枚举的
  4. 类的所有方法都无法被当作构造函数使用
  5. 类的构造器必须使用 new 来调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Animal {
//如果constructor为空可以省略不写
constructor(type, name, age, sex) {
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}

print() {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}
}

const a = new Animal("狗", "旺财", 3, "男");
a.print();

for (const prop in a) {
console.log(prop)
}

5-5. 类的其他书写方式

  1. 可计算的成员名

    使用方式: [变量]

  2. gettersetter

    ES5:通过 Object.defineProperty 定义某个对象成员属性的读取和设置

    使用getter和setter控制的属性,不在原型上

    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
    33
    34
    35
    36
    37
    38
    const printName = "print";

    class Animal {
    constructor(type, name, age, sex) {
    this.type = type;
    this.name = name;
    this.age = age;
    this.sex = sex;
    }

    //创建一个age属性,并给它加上getter,读取该属性时,会运行该函数
    get age() {
    return this._age + "岁";
    }

    //创建一个age属性,并给它加上setter,给该属性赋值时,会运行该函数
    set age(age) {
    if (typeof age !== "number") {
    throw new TypeError("age property must be a number");
    }
    if (age < 0) {
    age = 0;
    }
    else if (age > 1000) {
    age = 1000;
    }
    this._age = age;
    }

    [printName]() {
    console.log(`【种类】:${this.type}`);
    console.log(`【名字】:${this.name}`);
    console.log(`【年龄】:${this.age}`);
    console.log(`【性别】:${this.sex}`);
    }
    }

    var a = new Animal("狗", "旺财", 3, "男");
  3. 静态成员

    1. 构造函数本身的成员
    2. 使用static关键字定义的成员即静态成员
    3. 静态成员无法通过原型和实例获取;
    4. 静态成员通过构造函数直接获取
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Chess {
    constructor(name) {
    this.name = name;
    }

    static width = 50; //ES7字段初始化器写法
    static height = 50; //ES7字段初始化器写法

    static method() {

    }
    }

    console.log(Chess.width)
    console.log(Chess.height)
    Chess.method();
  4. 字段初始化器(ES7)

    1. 使用static的字段初始化器,添加的是静态成员
    2. 没有使用static的字段初始化器,添加的成员位于对象上
    3. 箭头函数在字段初始化器位置上,指向当前对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Test {
    //字段初始化器相当于把a、c写到constructor中
    static a = 1;
    b = 2;
    c = 3;

    constructor() {
    this.d = this.b + this.c;
    }
    }

    const t = new Test();
    console.log(t)
  5. 类表达式

    1
    2
    3
    4
    5
    6
    7
    const A = class { //匿名类,类表达式
    a = 1;
    b = 2;
    }

    const a = new A();
    console.log(a)
  6. [扩展]装饰器(ES7)(Decorator)

    • 横切关注点
    • 例如类当中有些方法已经过时不建议使用,但又不能删除时进行提示作用
    • 装饰器的本质是一个函数

    使用案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Test {

    @Obsolete
    print() {
    console.log("print方法")
    }
    }

    function Obsolete(target, methodName, descriptor) {
    // console.log(target, methodName, descriptor);
    // function Test
    // print
    // { value: function print(){}, ... }
    const oldFunc = descriptor.value
    descriptor.value = function (...args) {
    console.warn(`${methodName}方法已过时`);
    oldFunc.apply(this, args);
    }
    }

5-6. 类的继承

如果两个类A和B,如果可以描述为:B 是 A,则,A和B形成继承关系

如果B是A,则:

  1. B继承自A
  2. A派生B
  3. B是A的子类
  4. A是B的父类

如果A是B的父类,则B会自动拥有A中的所有实例成员

ES5实现:

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
function Animal(type, name, age, sex) {
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}
Animal.prototype.print = function () {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}

function Dog(name, age, sex) {
//借用父类的构造函数
Animal.call(this, "犬类", name, age, sex);
}

//将Dog的隐式原型Dog.prototype 指向Animal的隐式原型Animal.prototype
//这样才能继承print方法
Object.setPrototypeOf(Dog.prototype, Animal.prototype);

const d = new Dog("旺财", 3, "公");
d.print();
console.log(d);

继承的关键字:

  • extends:继承,用于类的定义
  • super
    • 直接当作函数调用,表示父类构造函数
    • 如 的原型

注意

  • ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数
  • 如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器

ES6实现:

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
33
34
35
36
37
38
39
40
41
42
43
class Animal {
constructor(type, name, age, sex) {
this.type = type;
this.name = name;
this.age = age;
this.sex = sex;
}

print() {
console.log(`【种类】:${this.type}`);
console.log(`【名字】:${this.name}`);
console.log(`【年龄】:${this.age}`);
console.log(`【性别】:${this.sex}`);
}

jiao(){
throw new Error("动物怎么叫的?");
}
}

class Dog extends Animal {
constructor(name, age, sex) {
super("犬类", name, age, sex);
// 子类特有的属性
this.loves = "吃骨头";
}

print(){
//调用父类的print
super.print();
//自己特有的代码
console.log(`【爱好】:${this.loves}`);
}

//同名方法,会覆盖父类
jiao(){
console.log("旺旺!");
}
}

const d = new Dog("旺财", 3, "公");
d.print();
d.jiao();

【冷知识】:

  • 用JS制作抽象类
    • 抽象类:一般是父类,不能通过该类创建对象
  • 正常情况下,this的指向,this始终指向具体的类的对象

6. 解构

6-1. 对象解构

  1. 什么是解构:

    使用ES6的一种语法规则,将一个对象或数组的某个属性提取到某个变量中

  2. 在解构中使用默认值

    {同名变量 = 默认值}

  3. 非同名属性解构

    {属性名:变量名}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const user = {
name: "彼方",
age: 1000,
sex: "男",
address: {
province: "**",
city: "xx"
}
}

// 先定义4个变量:name、age、gender、address
// 再从对象user中读取同名属性赋值(其中gender读取的是sex属性)
let { name, age, sex: gender = 123, address } = user;
console.log(name, age, gender, address)

//解构出user中的name、province
//定义两个变量name、province
//再解构
// 注意:这里不会对address进行赋值;
const { name, address: { province } } = user;

//报错: address is not defined
console.log(name, address, province)

6-2. 数组解构

普通数组解构:

1
2
3
4
5
const numbers = ['a', 'b', 'c', 'd'];

const [n1, , , n4, n5 = 123] = numbers;

console.log(n1, n4, n5);

嵌套数组解构:

1
2
3
4
const numbers = ["a", "b", "c", "d", [1, 2, 3, 4]];

// 得到numbers下标为4的数组中的下标为2的数据,放到变量n中
const [, , , ,{, , n}] = numbers;

嵌套对象解构:

1
2
3
4
5
6
7
8
9
const numbers = ["a", "b", "c", "d", {
a: 1,
b: 2
}];

//得到numbers下标为4的数组的属性a,赋值给变量A
const [, , , , { a: A }] = numbers; //方式一

const { a: A } = numbers[4]; //方式二

6-3. 剩余项解构

对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = {
name: "彼方",
age: 1000,
sex: "男",
address: {
province: "**",
city: "xx"
}
}

//解构出name,然后,剩余的所有属性,放到一个新的对象中,变量名为obj
// name: 彼方
// obj : {age:1000, sex:"男", address:{...}}

const { name, ...obj } = user;

数组:

1
2
3
4
5
6
7
8
9
const numbers = [324, 7, 23, 5, 3243];

// 得到数组前两项,分别放到变量a和b中,然后剩余的所有数据放到数组nums
const [a, b, ...nums] = numbers;

//实现变量交换
let a = 1, b = 2;
[b, a] = [a, b]
console.log(a, b)

6-4. 参数解构【常用】

参数解构时,如果参数为undefinednull 则会报错,通常使用参数默认值进行兼容处理

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function print({ name, age, sex, address: {
province,
city
} }) {
console.log(`姓名:${name}`)
console.log(`年龄:${age}`)
console.log(`性别:${sex}`)
console.log(`身份:${province}`)
console.log(`城市:${city}`)
}

const user = {
name: "彼方",
age: 1000,
sex: "男",
address: {
province: "**",
city: "xx"
}
}
print(user)

应用场景:

1
2
3
4
5
6
7
8
9
//使用参数默认值进行兼容
function ajax({
method = "get",
url = "/"
} = {}) {
console.log(method, url)
};

ajax();

7. 符号

7-1. 普通符号

符号是ES6新增的一个数据类型,它通过使用函数 Symbol(符号描述) 来创建
符号描述:仅方便开发者理解用途方便调试使用,无任何实际作用
符号设计的初衷,是为了给对象设置私有属性
私有属性:只能在对象内部使用,外面无法使用

符号具有以下特点:

  • 没有字面量
  • 使用 typeof 得到的类型是 symbol
  • 每次调用 Symbol 函数得到的符号永远不相等,无论符号名是否相同
  • 符号可以作为对象的属性名存在,这种属性称之为符号属性
    • 开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问
    • 符号属性是不能枚举的,因此在 for-in 循环中无法读取到符号属性,Object.keys 方法也无法读取到符号属性
    • Object.getOwnPropertyNames 尽管可以得到所有无法枚举的属性,但是仍然无法读取到符号属性
    • ES6 新增 Object.getOwnPropertySymbols 方法,可以读取符号,获取结果为symbol数组
  • 符号无法被隐式转换,因此不能被用于数学运算、字符串拼接或其他隐式转换的场景,但符号可以显式的转换为字符串,通过 String 构造函数进行转换即可,console.log 之所以可以输出符号,是它在内部进行了显式转换

使用示例:

1
2
3
4
5
6
7
8
//创建一个符号
const syb1 = Symbol("这是用于对象的一个属性");
const obj = {
a: 1,
b: 2,
[syb1]: 3 //符号属性
}
console.log(obj);

实现私有属性【对象】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const hero = (function () {
const getRandom = Symbol();

return {
attack: 30,
hp: 300,
defence: 10,
gongji() { //攻击
//伤害:攻击力*随机数(0.8~1.1)
const dmg = this.attack * this[getRandom](0.8, 1.1);
console.log(dmg);
},
[getRandom](min, max) { //根据最小值和最大值产生一个随机数
return Math.random() * (max - min) + min;
}
}
})()

console.log(hero);

实现私有属性【类】:

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

const Hero = (() => {
const getRandom = Symbol();

return class {
constructor(attack, hp, defence) {
this.attack = attack;
this.hp = hp;
this.defence = defence;
}

gongji() {
//伤害:攻击力*随机数(0.8~1.1)
const dmg = this.attack * this[getRandom](0.8, 1.1);
console.log(dmg);
}

[getRandom](min, max) { //根据最小值和最大值产生一个随机数
return Math.random() * (max - min) + min;
}
}
})();

const h = new Hero(3, 6, 3);
console.log(h);

7-2. 共享符号

根据某个符号名称(符号描述)能够得到同一个符号
获取共享符号: Symbol.for("符号名/符号描述")

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//示例一
const syb1 = Symbol.for("abc");
const syb2 = Symbol.for("abc");
console.log(syb1 === syb2); //true

//示例二
const syb3 = Symbol.for();
const syb4 = Symbol.for();
console.log(syb3 === syb4); //true

//示例三
const obj = {
a: 1,
b: 2,
[Symbol.for("c")]: 3
}
console.log(obj[Symbol.for("c")]);

实现原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
const SymbolFor = (() => {
const global = {};//用于记录有哪些共享符号
return function (name) {
if (!global[name]) {
global[name] = Symbol(name);
}
return global[name];
}
})();

const syb1 = SymbolFor("abc");
const syb2 = SymbolFor("abc");
console.log(syb1 === syb2);

7-3. 知名(公共、具名)符号

知名符号是一些具有特殊含义的共享符号,通过 Symbol 的静态属性得到
ES6 延续了 ES5 的思想:减少魔法,暴露内部实现!
因此,ES6 用知名符号暴露了某些场景的内部实现

  1. Symbol.hasInstance

    该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定

    1
    2
    3
    4
    obj instanceof A

    //等效于
    A[Symbol.hasInstance](obj) // Function.prototype[Symbol.hasInstance]

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function A() {};  //构造函数A
    const obj = new A(); //创建实例 obj
    console.log(obj instanceof A); //true
    console.log(A[Symbol.hasInstance](obj)); //true [等效于上方]

    //改写A的 Symbol.hasInstance 方法
    Object.defineProperty(A, Symbol.hasInstance, {
    value: function (obj) {
    return false;
    }
    });
    console.log(obj instanceof A); //false
    console.log(A[Symbol.hasInstance](obj)); //false
  2. [扩展] Symbol.isConcatSpreadable

    该知名符号会影响数组的 concat 方法

    拼接数组:

    1
    2
    3
    4
    5
    6
    7
    8
    const arr = [3];
    const arr2 = [5, 6, 7, 8];
    //如果某个参数存在数组索引和length,那么采用分割合并,即:
    const result = arr.concat(56,arr2); //[3,56,5,6,7,8]

    //修改arr2 数组合并时的合并方式;默认true:拆分合并,false:直接合并
    arr2[Symbol.isConcatSpreadable] = false;
    const result = arr.concat(56,arr2); //[3, 56, [5,6,7,8]]

    拼接对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const arr = [1];
    const obj1 = {
    0: 3,
    1: 4,
    length: 2,
    };
    const obj2 = {
    0: 3,
    1: 4,
    length: 2,
    [Symbol.isConcatSpreadable]: true
    };

    const result1 = arr.concat(2, obj1); //[1, 2, {0:3,1:4,length:2}]
    const result2 = arr.concat(2, obj2); //[1, 2, 3, 4]
  3. [扩展] Symbol.toPrimitive

    该知名符号会影响类型转换的结果

    示例一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const obj = {
    a:1,
    b:2
    };
    //需要计算,所以obj需要得到一个基本类型
    //首先调用obj.valueOf() 得到自身没变:{a:1,b:2}
    //在调用obj.toString() 得到基本类型: [object Object]
    console.log(obj + 123); //[object Object]123

    //改写 Symbol.toPrimitive 方法
    obj[Symbol.toPrimitive] = function() {
    return 2;
    }
    console.log(obj * 123); //246

    示例二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Temperature {
    constructor(degree) {
    this.degree = degree;
    }

    [Symbol.toPrimitive](type) {
    if (type === "default") {
    return this.degree + "摄氏度";
    }
    else if (type === "number") {
    return this.degree;
    }
    else if (type === "string") {
    return this.degree + "℃";
    }
    }
    }

    const t = new Temperature(30);

    console.log(t + "!"); //30摄氏度!
    console.log(t / 2); //15
    console.log(String(t)); //30℃
  4. [扩展] Symbol.toStringTag

    该知名符号会影响 Object.prototype.toString 的返回值

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Person1 {
    };

    class Person2 {
    [Symbol.toStringTag] = "Person"
    };

    const p1 = new Person1();
    const p2 = new Person2();
    const arr = [32424, 45654, 32];

    console.log(Object.prototype.toString.apply(p1)); //[object Object]
    console.log(Object.prototype.toString.apply(p2)); //[object Person]
    console.log(Object.prototype.toString.apply(arr)); //[object Person]
  5. 其他知名符号

8. 异步处理

8-1. Promise

Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一

8-1-1. Promise规范

这套规范最早诞生于前端社区,规范名称为Promise A+

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象,该对象称之为Promise对象,也叫做任务对象
    示例图片
  2. 每个任务对象,都应该有两个阶段、三个状态
    示例图片
    根据常理,它们之间存在以下逻辑:
    • 任务总是从未决阶段变到已决阶段,无法逆行
    • 任务总是从挂起状态变到完成或失败状态,无法逆行
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变
  3. 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。
    示例图片
  4. 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected
    示例图片

8-1-2. PromiseAPI

ES6提供了一套API,实现了Promise A+规范

基本使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
// 任务的具体执行流程,该函数会立即被执行
// 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
// 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});

pro.then(
(data) => {
// onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
},
(reason) => {
// onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
}
);

应用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 延迟一段指定的时间
* @param {Number} duration 等待的时间
* @returns {Promise} 返回一个任务,该任务在指定的时间后完成
*/
function delay(duration) {
//如果异步函数不存在失败则可以省略 reject 参数
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
};

// 利用delay函数,等待1秒钟,输出:finish
delay(1000).then(() => {
console.log('finish');
});

8-1-3. Promise 的链式调用

atch方法:

.catch(onRejected) = .then(null, onRejected)

链式调用:

  1. then方法必定会返回一个新的Promise
    可理解为后续处理也是一个任务
  2. 【重点】新任务的状态取决于后续处理:
    • 若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据
    • 若有后续处理但还未执行,新任务挂起。
    • 若后续处理执行了,则根据后续处理的情况确定新任务的状态
      • 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
      • 后续处理执行有错,新任务的状态为失败,数据为异常对象
      • 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致

注意:
.then().catch() 方法接收的是一个方法函数;如果不是一个方法那么就相当于传入 null,即没有注册回调函数,所以回调可以直接忽略,代码如下:

1
2
3
4
5
6
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);

//等价于如下代码
Promise.resolve(1).then(console.log);

//最终结果:1

8-1-4. Promise 的静态方法

方法名 含义
Promise.resolve(data) 直接返回一个完成状态的任务
Promise.reject(reason) 直接返回一个拒绝状态的任务
Promise.all(任务数组) 返回一个任务
任务数组全部成功则成功
任何一个失败则失败
Promise.any(任务数组) 返回一个任务
任务数组任一成功则成功
任务全部失败则失败
Promise.allSettled(任务数组) 返回一个任务
任务数组全部已决则成功
该任务不会失败
Promise.race(任务数组) 返回一个任务
任务数组任一已决则已决,状态和其一致

8-1-5. async 和 await

消除回调:
有了Promise,异步任务就有了一种统一的处理方式
有了统一的处理方式,ES官方就可以对其进一步优化
ES7推出了两个关键字asyncawait,用于更加优雅的表达Promise

async:

async关键字用于修饰函数,被它修饰的函数,一定返回Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function method1(){
return 1; // 该函数的返回值是Promise完成后的数据
}

method1(); // Promise { 1 }

async function method2(){
return Promise.resolve(1); // 若返回的是Promise,则method得到的Promise状态和其一致
}

method2(); // Promise { 1 }

async function method3(){
throw new Error(1); // 若执行过程报错,则任务是rejected
}

method3(); // Promise { <rejected> Error(1) }

await:

await关键字表示等待某个Promise完成,它必须用于async函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function method(){
const n = await Promise.resolve(1);
console.log(n); // 1
}

// 上面的函数等同于
function method(){
return new Promise((resolve, reject)=>{
Promise.resolve(1).then(n=>{
console.log(n);
resolve(1)
})
})
}

await也可以等待其他数据

1
2
3
async function method(){
const n = await 1; // 等同于 await Promise.resolve(1)
}

如果需要针对失败的任务进行处理,可以使用try-catch语法

1
2
3
4
5
6
7
8
9
10
11
async function method(){
try{
const n = await Promise.reject(123); // 这句代码将抛出异常
console.log('成功', n)
}
catch(err){
console.log('失败', err)
}
}

method(); // 输出: 失败 123

应用示例:

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
//需要异步处理的数据
const arr = [1,2,3,4,5,6,7,8,9];

//异步处理:1s处理一个
function asyncMethod(param) {
return new Promise((resolve, reject)=>{
setTimeout(() => {
if(Math.random() < 0.3){
resolve(`成功:${param}`);
}else{
reject(`失败:${param}`);
}
}, 1000);
});
};

//【重点】处理数据并输出结果
async function runArr(arrs) {
for (const value of arrs) {
try{
const result = await asyncMethod(value);
console.log(result);
}catch(err){
console.log(err);
};
};
};

runArr(arr); //每隔1s 输出一个结果

9. Fetch Api

9-1. Fetch Api 概述

XMLHttpRequest的问题:

  1. 所有的功能全部集中在同一个对象上,容易书写出混乱不易维护的代码
  2. 采用传统的事件驱动模式(回调,注册事件等),无法适配新的 Promise Api

Fetch Api 的特点:

  1. 并非取代 AJAX,而是对 AJAX 传统 API 的改进

    AJAX 是一个概念,不刷新浏览器下,异步请求数据;和实现方法无关。

  2. 精细的功能分割:头部信息、请求信息、响应信息等均分布到不同的对象,更利于处理各种复杂的 AJAX 场景
  3. 使用 Promise Api,更利于异步代码的书写
  4. Fetch Api 并非 ES6 的内容,属于 HTML5 新增的 Web Api
  5. 需要掌握网络通信的知识

9-2. 基本使用

请求测试地址:http://101.132.72.36:5100/api/local

使用 fetch 函数即可立即向服务器发送网络请求

9-2-1. 参数

  1. 必填,字符串,请求地址
  2. 选填,对象,请求配置

数据请求示例代码:

1
2
3
4
5
6
7
8
9
10
11
const url = 'http://101.132.72.36:5100/api/local';
const config = {
method: "POST",
headers: {
"Content-Type": "application/json",
"a":1
},
body: `{ "a":"1" }`
};

fetch(url, config);

请求配置对象:

  • method:字符串,请求方法,默认值GET
  • headers:对象,请求头信息
  • body: 请求体的内容,必须匹配请求头中的 Content-Type
  • mode:字符串,请求模式
    • cors:默认值,配置为该值,会在请求头中加入 origin 和 referer
    • no-cors:配置为该值,不会在请求头中加入 origin 和 referer,跨域的时候可能会出现问题
    • same-origin:指示请求必须在同一个域中发生,如果请求其他域,则会报错
  • credentials: 如何携带凭据(cookie)
    • omit:默认值,不携带cookie
    • same-origin:请求同源地址时携带cookie
    • include:请求任何地址都携带cookie
  • cache:配置缓存模式
    • default: 表示fetch请求之前将检查下http的缓存.
    • no-store: 表示fetch请求将完全忽略http缓存的存在. 这意味着请求之前将不再检查下http的缓存, 拿到响应后, 它也不会更新http缓存.
    • no-cache: 如果存在缓存, 那么fetch将发送一个条件查询request和一个正常的request, 拿到响应后, 它会更新http缓存.
    • reload: 表示fetch请求之前将忽略http缓存的存在, 但是请求拿到响应后, 它将主动更新http缓存.
    • force-cache: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 除非没有任何缓存, 那么它将发送一个正常的request.
    • only-if-cached: 表示fetch请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 如果没有缓存, 它将抛出网络错误(该设置只在mode为”same-origin”时有效).

9-2-2. 返回值

fetch 函数返回一个 Promise 对象

  • 当收到服务器的返回结果后(无论请求是否成功,无论状态码是多少),Promise 进入 resolved 状态,状态数据为 Response 对象
  • 当网络发生错误(或其他导致无法完成交互的错误)时,Promise 进入 rejected 状态,状态数据为错误信息。

数据返回使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function getProvinces() {

const url = 'http://101.132.72.36:5100/api/local';

// 方式一
fetch(url).then( resp => {
console.log(resp);
}).catch( err => {
console.log(err);
});

//方式二【推荐】
try{
const resp = await fetch(url);
console.log(resp);
}catch (err) {
console.log(err);
};
};

Response对象:

  • ok:boolean,当响应消息码在200~299之间时为true,其他为false
  • status:number,响应的状态码
  • text():用于处理文本格式的 Ajax 响应。它从响应中获取文本流,将其读完,然后返回一个被解决为 string 对象的 Promise。
  • blob():用于处理二进制文件格式(比如图片或者电子表格)的 Ajax 响应。它读取文件的原始数据,一旦读取完整个文件,就返回一个被解决为 blob 对象的 Promise。
  • json():用于处理 JSON 格式的 Ajax 的响应。它将 JSON 数据流转换为一个被解决为 JavaScript 对象的promise。
  • redirect():可以用于重定向到另一个 URL。它会创建一个新的 Promise,以解决来自重定向的 URL 的响应。

使用示例:

1
2
3
4
5
6
async function getProvinces() {
const url = "http://101.132.72.36:5100/api/local";
const resp = await fetch(url)
const result = await resp.json();
console.log(result)
};

9-3. Request 对象

除了使用基本的fetch方法,还可以通过创建一个Request对象来完成请求(实际上,fetch的内部会帮你创建一个Request对象)

1
new Request(url地址, 配置);

具体应用:

1
2
3
4
5
6
7
async function getProvinces() {
const url = "http://101.132.72.36:5100/api/local";
const req = new Request(url, {});
const resp = await fetch(req)
const result = await resp.json();
console.log(result)
};

注意点:
尽量保证每次请求都是一个新的Request对象。

如果获得了一个Request对象,但不清楚是否请求过,可以通过 Request 对象上的 clone 方法获得一个完全相同的新对象;

1
2
//req: Request 对象;
req.clone(); //克隆一个全新的request对象,配置一致

9-4. Response 对象

fetch() 成功收到服务器的返回的状态数据;详情说明

1
2
3
4
5
6
7
8
9
10
11
12
13
async function getProvinces() {

const resp = new Response(`[
{"id":1, "name":"北京"},
{"id":2, "name":"天津"}
]`,
{
ok: true,
status: 200
});
const result = await resp.json();
console.log(result)
}

9-5. Headers 对象

在Request和Response对象内部,会将传递的请求头对象,转换为Headers

Headers对象中的方法:

  • has(key):检查请求头中是否存在指定的key值
  • get(key): 得到请求头中对应的key值
  • set(key, value):修改对应的键值对,不存在则新增
  • append(key, value):添加对应的键值对,存在不会覆盖,获取值用逗号隔开
  • keys(): 得到所有的请求头键的集合
  • values(): 得到所有的请求头中的值的集合
  • entries(): 得到所有请求头中的键值对的集合

应用场景:

统一封装请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
function getCommonHeaders() {
return new Headers({
a: 1,
b: 2
});
};

const url = "http://101.132.72.36:5100/api/local";
const headers = getCommonHeaders();
headers.set("a", 3);
req = new Request(url, {
headers
});

9-6. 文件上传

流程:

  1. 客户端将文件数据发送给服务器
  2. 服务器保存上传的文件数据到服务器端
  3. 服务器响应给客户端一个文件访问地址

测试地址:http://101.132.72.36:5100/api/upload
键的名称(表单域名称,和后端沟通确定):imagefile

请求方法:POST
请求的表单格式:multipart/form-data
请求体中必须包含一个键值对,键的名称是服务器要求的名称,值是文件数据

HTML5中,JS仍然无法随意的获取文件数据,但是可以获取到input元素中,被用户选中的文件数据
可以利用HTML5提供的FormData构造函数来创建请求体

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* <input type="file" id="avatar" /> */

async function upload() {
const inp = document.getElementById("avatar");
if(inp.files.length === 0){
alert("请选择上传文件!");
return;
};
const formData = new FormData(); //构建一个空的请求体
formData.append("imagefile", inp.files[0]); //1、键值名称,2、二进制数据
const url = "http://101.132.72.36:5100/api/upload";
const resp = await fetch(url, {
method: "POST",
body: formData //会自动修改请求头(修改Content-Type类型)
});
const result = await resp.json();
console.log(result);
};

10. 迭代器和生成器

10-1. 迭代器

背景知识

  1. 什么是迭代?

    从一个数据集合中按照一定的顺序,不断取出数据的过程

  2. 迭代和遍历的区别?

    迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完

    遍历强调的是要把整个数据依次全部取出

  3. 迭代器

    对迭代过程的封装,在不同的语言中有不同的表现形式,通常为对象

  4. 迭代模式

    一种设计模式,用于统一迭代过程,并规范了迭代器规格

    • 迭代器应该具有得到下一个数据的能力
    • 迭代器应该具有判断是否还有后续数据的能力

JS中的迭代器

JS规定,如果一个对象具有 next 方法,并且该方法返回一个对象,该对象的格式如下:

1
{value: 值, done: 是否迭代完成}

则认为该对象是一个迭代器;

示例:

1
2
3
4
5
6
7
8
const obj = {
next() {
return {
value: xxx,
done: xx
}
};
};

含义:

  • next方法:用于得到下一个数据
  • 返回的对象
    • value:下一个数据的值
    • done:boolean,是否迭代完成

迭代器创建函数示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [6, 7, 8, 9];

function createIterator(arr) {
let i = 0;
next(){
var result = {
value: arr[i],
done: i >= arr.length
};
i++;
return result;
}
};

const iter1 = createIterator(arr1);
const iter2 = createIterator(arr2);

10-2. 可迭代协议 与 for-of 循环

可迭代协议

概念回顾:

  • 迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
  • 迭代器创建函数(iterator create):一个返回迭代器的函数

可迭代协议:

ES6规定,如果一个对象具有知名符号属性 Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
[Symbol.iterator]() {
return {
next() {
return {
value:1,
done:false,
}
}
}
}
}

for-of 循环

for-of 循环用于遍历可迭代对象,格式如下

1
2
3
4
5
//迭代完成后循环结束
for (const item of iterable){
//iterable:可迭代对象
//item:每次迭代得到的数据
}

展开运算符与可迭代对象

展开运算符可以作用于可迭代对象,这样,就可以轻松的将可迭代对象转换为数组。

10-3. 生成器 (Generator)

方便开发者自己书写迭代器

  1. 什么是生成器?
    生成器是一个通过构造函数Generator创建的对象(无法通过 new 关键字创建),生成器既是一个迭代器,同时又是一个可迭代对象

  2. 如何创建生成器?
    生成器的创建,必须使用生成器函数( Generator Function )

  3. 如何书写一个生成器函数呢?

    function 后边或者方法名前边添加 *

    1
    2
    3
    4
    5
    6
    7
    8
    //这是一个生成器函数,该函数一定返回一个生成器
    function *method(){};

    function* method(){};

    function*() {}

    { *method(){} }
  4. 生成器函数内部是如何执行的?

    • 生成器函数内部是为了给生成器的每次迭代提供的数据(运行生成器函数时不会运行内部函数
    • 每次调用生成器的 next 方法,将导致生成器函数运行到下一个 yield 关键字位置
    • yield 是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function* test() {
    console.log("第1次运行");
    yield 1;
    console.log("第2次运行");
    yield 2;
    console.log("第3次运行");
    }
    const generator = test(); //这里只作为生成器,不会执行test内部代码

    generator.next(); //第1次运行 {value: 1, done: false}
    generator.next(); //第2次运行 {value: 2, done: false}
    generator.next(); //第3次运行 {value: undefined, done: true}
  5. 有哪些需要注意的细节?

    1. 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中

    2. 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function* test() {
      let info = yield 1; //第一次执行next方法代码停在yield这里,info还没有接收到值,而且这里info接收到的值也不是1,如果next传参则yield的返回值为参数,否则为undefined;
      console.log(info);
      info = yield 2 + info;
      console.log(info);
      };

      const generator1 = test();

      generator1.next(); //{value: 1, done: false}
      generator1.next(2); //2 {value: 4, done: false}
    3. 第一次调用next方法时,传参没有任何意义

    4. 在生成器函数内部,可以调用其他生成器函数,但是要注意加上 *

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      function* t1() {
      yield "a";
      yield "b";
      };

      function* test() {
      yield* t1(); //相当于将上方t1中的代码拷贝到这里,及t1会参与迭代
      yield 1;
      yield 2;
      yield 3;
      };

      const generator = test();

      generator.next(); //{value: 'a', done: false}
      generator.next(); //{value: 'b', done: false}
      generator.next(); //{value: '1', done: false}
      generator.next(); //{value: '2', done: false}
  6. 生成器的其他API

    • return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束
    • throw方法:调用该方法,可以在生成器中产生一个错误

10-4. 生成器应用-异步任务控制

在react中会经常使用
在ES5时,解决没有async和await

使用场景:

在ES5时,实现async和await的效果

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//延时函数
function delay(time){
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(time);
},time);
});
};

//模拟async和await写法逻辑
function* task() {
const d = yield 1; //代表其他数据及同步函数
console.log(d);
// const resp = yield fetch("http://101.132.72.36:5100/api/local");
// const result = yield resp.json();
// console.log(result);
const a = yield delay(3000);
console.log(a);
const b = yield 'b';
console.log(b);
}

//具体实现
function run(generatorFunc){
const generator = generatorFunc(); //生成迭代器
let result = generator.next(); //启动任务(开始迭代),得到迭代数据
handleResult();
function handleResult() {
if(result.done){
return; //迭代完成
};
//迭代未完成,分为两种情况
//1、迭代的数据是一个Promise
//2、迭代的数据是其它数据
if(typeof result.value.then == "function"){
result.value.then(data => {
//1、迭代的数据是一个Promise
result = generator.next(data);
handleResult();
}, err => {
result = generator.throw(err);
handleResult();
})
}else{
//2、迭代的数据是其它数据
result = generator.next(result.value);
handleResult();
};
}
};

console.log('start');
run(task);
console.log('end');
//结果:'start' 1 'end' 3000 'b'

11. 更多的集合类型

11-1. set 集合

一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。

作用:set用于存放不重复的数据

  1. 如何创建set集合

    1
    2
    3
    new Set(); //创建一个没有任何内容的set集合

    new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
  2. 如何对set集合进行后续操作

    • add(数据):添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作;
      • set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等;
    • has(数据):判断set中是是否存在对应的数据;
    • delete(数据):删除匹配的数据(同add使用Object.is进行匹配),返回是否删除成功;
    • clear():清空整个set集合;
    • size: 获取set集合中的元素数量,只读属性,无法重新赋值;
  3. 如何与数组进行相互装换;

    1
    2
    3
    4
    //数组转set集合
    const s = new Set([x,x,x,x,x]);
    //set集合转数组
    const arr = [...s];
  4. 如何遍历

    • 使用for-of循环
    • 使用set中的示例方法forEach

注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项;

11-2. set应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 两个数组的并集、交集、差集 (不能出现重复项),得到的结果是一个新数组
const arr1 = [33, 22, 55, 33, 11, 33, 5];
const arr2 = [22, 55, 77, 88, 88, 99, 99];

//并集
console.log('并集:', [...new Set(arr1.concat(arr2))]);

//交集
const s3 = new Set(arr2);
var result = [...new Set(arr1)].filter(item => s3.has(item));
console.log('交集:',result);

//差集
console.log('差集:', [...new Set([...arr1,...arr2])].filter(item => result.indexOf(item) < 0));

11-3. [扩展]手写set

模拟实现,与js底层实现不同;js底层可直接操作内存,且数据存储使用的是链表

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class MySet {
constructor(iterable = []){
//验证是否是可迭代的对象
if(typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(`你提供的${iterable}不是一个可迭代的对象!`);
};
this._datas = [];
for (const item of iterable) {
this.add(item);
};
};

get size() {
return this._datas.length;
};

add(data){
if(!this.has(data)) {
this._datas.push(data);
};
};

has(data) {
for (const item of this._datas) {
if(this.isEqual(data, item)){
return true;
};
};
return false;
};

delete(data) {
for(let i = 0; i < this._datas.length; i++) {
const element = this._datas[i];
if(this.isEqual(element, data)){
this._datas.splice(i, 1);
return true;
};
};
return false;
};

clear() {
this._datas.length = 0;
};

*[Symbol.iterator]() {
for(const item of this._datas) {
yield item;
};
};

forEach(callback) {
for(const item of this._datas){
callback(item, item, this);
};
};

/**
* 判断两个数据是否相等
* @param {*} data1
* @param {*} data2
*/
isEqual(data1, data2) {
if (data1 === 0 && data2 === 0) {
return true;
};
return Object.is(data1, data2);
};
};

11-4. map集合

键值对(key value pair)数据集合的特点:键不可重复

map集合专门用于存储多个键值对数据。

在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。

使用对象存储有以下问题:

  1. 键名只能是字符串
  2. 获取数据的数量不方便
  3. 键名容易跟原型上的名称冲突

11-4-1. 如何创建map

1
2
3
4
5
6
7
8
//创建一个空的map
new Map();

//创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值;
new Map(iterable);

//注意:即第一次迭代的结果不一定为数组但必须是可迭代的;
new Map([["a", 3], ["b", 4], ["c", 5]]);

11-4-2. 如果进行后续操作

  • size: 只读属性,获取当前map中键的数量;
  • set(键, 值): 设置一个键值对,键和值可以是任何类型;
    • 如果键不存在,则添加一项;
    • 如果键已存在,则修改它的值;
    • 比较键的方式和set相同;
  • get(键):根据一个键得到对应的值;
  • has(键):判断某个键是否存在;
  • delete(键):删除指定的键;
  • clear():清空map;

11-4-3. 和数组互相转换

和set一样

11-4-4. 遍历

  • for-of,每次迭代得到的是一个长度为2的数组
  • forEach, 通过回调函数遍历
    • 参数1:每一项的值
    • 参数2:每一项的键
    • 参数3:map本身

11-5. [扩展]手写map

模拟实现,与js底层实现不同;js底层可直接操作内存,且数据存储使用的是链表

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class MyMap {
constructor(iterable = []) {
if(typeof iterable[Symbol.iterator] !== "function") {
throw new TypeError(`你提供的${iterable}不是一个可迭代的对象!`);
};
this._datas = [];
for(const item of iterable) {
//item 也得是一个可迭代对象
if(typeof item[Symbol.iterator] !== "function"){
throw new TypeError(`你提供的${item}不是一个可迭代的对象!`);
};
const iterator = item[Symbol.iterator]();
const key = iterator.next().value;
const value = iterator.next().value;
this.set(key, value);
};
};

set(key, value) {
const obj = this._getObje(key);
if(obj){
//存在则修改
obj.value = value;
}else {
this._datas.push({
key,
value
})
};
};

get(key) {
const item = this._getObje(key);
if(item) {
return item;
};
return undefined;
};

get size(){
return this._datas.length;
};

delete(key) {
for(let i = 0; i < this._datas.length; i++) {
const element = this._datas[i];
if(this.isEqual(element.key, key)){
this._datas.splice(i, 1);
return true;
};
};
return false;
};

clear() {
this._datas.length = 0;
};

/**
* 根据key值从内部数组中,找到对应的数组项
* @param {*} key
*/
_getObje(key) {
for(const item of this._datas){
if(this.isEqual(item.key, key)){
return item;
};
};
};

has(key) {
return this._getObje(key)!== undefined;
};

/**
* 判断两个数据是否相等
* @param {*} data1
* @param {*} data2
*/
isEqual(data1, data2) {
if (data1 === 0 && data2 === 0) {
return true;
};
return Object.is(data1, data2);
};

*[Symbol.iterator]() {
for (const item of this._datas) {
yield [item.key, item.value];
};
};

forEach(callback){
for (const item of this._datas) {
callback(item.value, item.key, this);
};
};
};

11-6. WeakSet 和 WeakMap

11-6-1. WeakSet

使用该集合,可以实现和set一样的功能,不同的是:

  1. 它内部存储的对象地址不会影响垃圾回收

    通常用来检测变量是否还存在引用;(如果WeakSet中还存在,那么外部还存在引用)

  2. 他只能添加对象
  3. 不能遍历(不是可迭代的对象)、没有size属性、没有forEace方法

11-6-2. WeakMap

类似于map的集合,不同的是:

  1. 它的键存储的地址不会影响垃圾回收
  2. 它的键只能是对象
  3. 不能遍历(不是可迭代的对象)、没有size属性、没有forEace方法

12. 代理与反射

12-1. [回顾]属性描述符

Property Descriptor 属性描述符:用于描述一个属性的相关信息

通过 Object.getOwnPropertyDescriptor(对象, 属性名) 可以得到一个对象的某个属性的属性描述符

  • value: 属性值
  • configurable: 该属性的描述符是否可以修改
  • enumerable: 该属性是否可以被枚举
  • writable: 该属性是否可以被重新赋值

Object.getOwnPropertyDescriptors(对象) 可以得到某个对象的所有属性描述符

如果需要为某个对象添加属性时 或 修改属性时,配置其属性描述符,可以使用下面的代码:

1
2
3
Object.defineProperty(对象, 属性名, 描述符);

Object.defineProperties(对象, 多个属性的描述符);

使用示例:

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
const obj = {
name: '测试',
age: 23
}

Object.defineProperty(obj, "name", {
value: '修改',
configurable: false,
enumerable:false,
writable:false
});

Object.defineProperties(obj,{
name:{
value: '修改',
configurable: false,
enumerable:false,
writable:false
},
age:{
value: '24',
configurable: false,
enumerable:false,
writable:false
}
});

12-1-1. 存取器属性

属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不在是一个普通属性,而变成了存取器属性。

get 和 set配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行get方法,将get方法得到的返回值作为属性值;如果给该属性赋值,则会运行set方法

意义:存取器属性最大的意义,在于可以控制属性的读取和赋值

注意:

  1. 不能再get方法中读取自身【无限递归】
  2. 不能再set方法中对自身进行赋值【无限递归】
  3. 如果配置了get和set,那么就不能配置value和writable,否则会报错(因为存取器是没有内存空间的,而value和writable都是配置内存空间的)

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const obj = {
b: 2
};

Object.defineProperty(obj, "a", {
get() {
console.log("运行了get")
return obj._a;
},
set(val) {
console.log("运行了set")
obj._a = val;
}
})

console.log(obj.a = 10);
console.log(obj.a);

12-2. Reflect 反射

12-2-1. Reflect是什么?

Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能;

由于它类似于其他语言的反射,因此取名为Reflect;

12-2-2. 它可以做什么?

使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在于对象中 等等功能;

12-2-3. 这些功能不是已经存在了吗?为什么还要用Reflect实现一次?

有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹;

这种理念很大程度上是受到函数式编程的影响;

ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象;

因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。

12-2-4. 它里面到底提供了哪些API呢?

  • Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value,等同于给对象的属性赋值
  • Reflect.get(target, propertyKey): 读取对象target的属性propertyKey,等同于读取对象的属性值
  • Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this和参数列表。等同于函数调用
  • Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
  • Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty,不同的是如果配置出现问题,返回false而不是报错
  • Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
  • Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
  • 其他API:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect]

12-3. Proxy 代理

代理:提供了修改底层实现的方式

创建:

1
2
3
4
5
6
7
/**
* 代理一个目标对象
* target: 目标对象
* handler: 是一个普通对象,其中可以重写底层实现
* 返回一个代理对象
*/
new Proxy(target, hander)

使用示例:

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
const obj = {
a: 1,
b: 2
};

//创建obj代理,并重写代理的底层实现
const proxy = new Proxy(obj, {
set(target, propertyKey, value){
// console.log(target === obj) 【true】
// console.log(target === proxy) 【false】
// 注意这里target 为代理的对象即obj,并不是代理proxy
// console.log(target, propertyKey, value);
// target[propertyKey] = value;
Reflect.set(target, propertyKey, value);
},
get(target, propertyKey){
if(Reflect.has(target, propertyKey)){
return Reflect.get(target, propertyKey);
}else{
return -1;
}
},
has(target, propertyKey){
return false;
}
});

proxy.a = 10; //执行set
console.log(proxy.a) //10 执行get
console.log(proxy.d) //-1 执行get
console.log("a" in proxy); //false 执行has

12-4. 【应用】观察者模式

有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。

示例代码:

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
//创建一个观察者
function observer(target) {
const div = document.getElementById('container');
const proxy = new Proxy(target, {
set(target, prop, value){
Reflect.set(target, prop, value)
render();
},
get(target, prop){
return Reflect.get(target, prop)
}
});
render();
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html+=`<p><span>${prop}</span>:<span>${target[prop]}</span></p>`
};
div.innerHTML = html;
};
return proxy;
};

const target = {
a: 1,
b: 2
};

var obj = observer(target);

12-5. 【应用】偷懒的构造函数

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User{

};

function ConstructorProxy(Class, ...propNames){
return new Proxy(Class, {
construct(target, argumentsList){
const obj = Reflect.construct(target, argumentsList);
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
});
return obj
}
});
};

const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age");

const obj = new UserProxy("彼", "方", 23);

console.log(obj);

12-6. 【应用】可验证的函数参数

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function sum(a, b){
return a + b;
};

function validatorFunction(func, ...types) {
const proxy = new Proxy(func, {
apply(target, thisArgument, argumentsList){
types.forEach((item, index) => {
if(typeof argumentsList[index] !== item){
throw new Error(`第${index + 1}个参数${argumentsList[index]}格式错误!`);
};
});
return Reflect.apply(target, thisArgument, argumentsList);
}
});

return proxy;
};

var sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 3));
console.log(sumProxy("1", 2));

13. 增强的数组功能

13-1. 新增的数组API

13-1-1. 静态方法

  • Array.of(…args): 使用指定的数组项创建一个新数组

    消除 new Array(), 参数为一个且为数值型时的歧义

  • Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组。

13-1-2. 实例方法

  • find(callback): 用于查找满足条件的第一个元素
  • findIndex(callback):用于查找满足条件的第一个元素的下标
  • fill(data):用指定的数据填充满数组所有的内容
  • copyWithin(target, start?, end?): 在数组内部完成复制
  • includes(data):判断数组中是否包含某个值,使用Object.is匹配

11-2. 【扩展】类型化数组

11-2-1. 数字存储的前置知识

  1. 计算机必须使用固定的位数来存储数字,无论存储的数字是大是小,在内存中占用的空间是固定的。

  2. n位的无符号整数能表示的数字是2^n个,取值范围是:0 ~ 2^n - 1

  3. n位的有符号整数能表示的数字是2^n个,取值范围是:-2^(n-1) ~ 2^(n-1) - 1

  4. 浮点数表示法可以用于表示整数和小数,目前分为两种标准:

    1. 32位浮点数:又称为单精度浮点数,它用1位表示符号,8位表示阶码,23位表示尾数
    2. 64位浮点数:又称为双精度浮点数,它用1位表示符号,11位表示阶码,52位表示尾数
  5. JS中的所有数字,均使用双精度浮点数保存

11-2-2. 类型化数组

类型化数组:用于优化多个数字的存储

具体分为:

  • Int8Array: 8位有符号整数(-128 ~ 127)
  • Uint8Array: 8位无符号整数(0 ~ 255)
  • Int16Array: …
  • Uint16Array: …
  • Int32Array: …
  • Uint32Array: …
  • Float32Array:
  • Float64Array
  1. 如何创建数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //数组构造函数: 上方所有类别(Int8Array...)

    //1.创建指定长度
    new 数组构造函数(长度)

    //2.创建指定内容
    数组构造函数.of(元素...)

    //3.转换类数组
    数组构造函数.from(可迭代对象)

    //4.通过已有的类型化数组复制一个新的数组(注意:高位转地位可能出现溢出导致数值异常)
    new 数组构造函数(其他类型化数组)
  2. 得到长度

    1
    2
    3

    数组.length //得到元素数量
    数组.byteLength //得到占用的字节数
  3. 其他的用法跟普通数组一致,但是:

    • 不能增加和删除数据,类型化数组的长度固定
    • 一些返回数组的方法,返回的数组是同类型化的新数组

11-3. 【扩展】ArrayBuffer

ArrayBuffer:一个对象,用于存储一块固定内存大小的数据。

创建:

1
new ArrayBuffer(字节数)
  • 可以通过属性 byteLength 得到字节数;
  • 可以通过方法 slice 得到新的ArrayBuffer;

示例:

1
2
3
4
5
6
7
//创建了一个用于存储10个字节的内存空间
const bf = new ArrayBuffer(10);

//不传参将得到一个相同的新数组
const bf2 = bf.slice(3, 5);

console.log(bf, bf2);

读写ArrayBuffer

  1. 使用DataView
    • 通常会在需要混用多种存储格式时使用DataView
  2. 使用类型化数组
    • 实际上,每一个类型化数组都对应一个ArrayBuffer,如果没有手动指定ArrayBuffer,类型化数组创建时,会新建一个ArrayBuffer

11-4. 【扩展】制作黑白图片

示例代码:

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
/*
* 画布中的1个图像是由多个像素点组成,每个像素点拥有4个数据:红、绿、蓝、alpha
* 把一个图像变成黑白,只需要将图像的每个像素点设置成为红绿蓝的平均数即可
*/
function change() {
const img = document.querySelector("img");
const cvs = document.querySelector("canvas");
const ctx = cvs.getContext("2d");
ctx.drawImage(img, 0, 0);

//得到画布某一个区域的图像信息
const imageData = ctx.getImageData(0, 0, img.width, img.height);

console.log(imageData);

for (let index = 0; index < imageData.data.length; index+=4) {
const r = imageData.data[index];
const g = imageData.data[index+1];
const b = imageData.data[index+2];
const avg = (r + g + b) / 3;

imageData.data[index] = imageData.data[index+1] = imageData.data[index+2] = avg;
};

//将图像数据设置到画布
ctx.putImageData(imageData, 0, 0);
};

获取图片的二进制信息示例:

1
2
3
4
5
6
7
//请求获取
async function getImage(){
const img = await fetch('./image.jpg');
const blob = await img.blob();
const bf = await blob.arrayBuffer();
console.log(bf);
};

__END__