ES6知识点

ES6概述

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无法开发大型应用的语言层面的问题。

ES6块级绑定

1.var声明变量产生的问题

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

2.let声明

ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念

块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域

解决声明变量的问题

  • 全局变量挂载到全局对象:全局对象成员污染问题

    let声明的变量不会挂载到全局对象

  • 允许重复的变量声明:导致数据被覆盖

    let声明的变量,不允许当前作用域范围内重复声明

    块级作用域中用let定义的变量,在作用域外不能访问

  • 变量提升:怪异的数据访问、闭包问题

    使用let不会有变量提升,因此,不能在定义let变量之前使用它。

    底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access ‘a’ before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。

    在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)

    循环中使用let声明的循环变量,在循环结束后会销毁

3.const声明

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

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

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

注意的细节:

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

ES6字符串和正则表达式

1.更好的Unicode支持

早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字。我们将一个16位的二进制编码叫做一个码元(Code Unit)。

后来,由于技术的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)。

ES6为了解决这个困扰,为字符串提供了方法:codePointAt,根据字符串码元的位置得到其码点。

同时,ES6为正则表达式添加了一个flag: u,如果添加了该配置,则匹配时,使用码点匹配

charCodeAt() 获取码元    codePointAt(码点)

2.更多的字符串API

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

  • includes 判断字符串中是否包含指定的子字符串

  • startsWith 判断字符串中是否以指定的字符串开始

  • endsWith 判断字符串中是否以指定的字符串结尾

  • repeat 将字符串重复指定的次数,然后返回一个新字符串。

ES6函数

1.参数默认值

使用

​ es6之前参数默认值为undefined

​ es6的时候可以在书写形参时,可以直接给形参赋值,当传入未定义的值的时候,实参使用默认参数

function add(a,b=1,c=1){
	return a+b+c;
}
add(1); //3

​ 参数可以是字面量 也可以是表达式

对arguments 的影响

​ 严格模式下arguments与形参时脱离的,没有关联

​ 只要给函数加上参数默认值时,该函数自动使用严格模式下的规则

暂时性死区

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

2.剩余参数

arguments的缺陷:

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

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

语法:

function (...形参名){

}

细节:

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

3.展开运算符

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

4.明确函数用途

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

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

5.箭头函数

回顾:this指向

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

this指向:决定怎么使用函数,箭头函数的出现解决的this的一些指向问题

语法:箭头函数是一个函数表达式 不会提升到全局对象中

(参数1,参数2,) => {}

​ 箭头函数中的this指向,取决于定义自己时的this指向,而与如何调无关;

let obj = {
				count:0,
				print:function(){
					console.log(this)  //当前作用域为obj
					window.onclick = ()=>{
						console.log(this)
						console.log(this.count); //this指向obj
					}
				},
				print1:()=>{
					console.log(this)  //当前作用域为window
					// this.count++;
					console.log(this.count); //this指向window
				}
			}
obj.print();  //0
obj.print1();  //undefined

简写

​ 如果箭头函数参数只有一个参数可以省略()

参数=>{  }

​ 如果箭头函数只有返回语句,可以省略大括号和()

参数 => num % 2 !== 0   //判断是否是奇数

​ 简写时,如果返回对象时,返回的对象要写在()内

const sum = (a,b) =>({ a, b,sum:a+b })
注意细节
  • ​ 箭头函数里面没有this、arguments、new.target,如果使用了,则使用的是函数外层对应的。
  • ​ 箭头函数没有prototype
  • ​ 箭头函数不能
使用场景
  • 临时性使用的函数,并不会可以调用它,比如
    • 事件处理函数
    • 异步处理函数
    • 其他临时性的函数
  • 为了绑定外层this的函数
  • 在不影响其他代码的情况下保持代码的简洁性

ES6对象

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

  1. 成员速写

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

let obj = {
        loginId,
        loginPwd,
        id: Math.random()
   		}
  1. 方法速写

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

const user = {
    sayHello(){
        console.log('方法速写')
    }
}
  1. 计算属性名

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

const prop1 = "name";
const prop2 = "age";
const prop3 = "sayHello";
const user = {
[prop1]: "任兵齐",
[prop2]: 100,
[prop3](){
      console.log(this[prop1], this[prop2])
	}
}

2. Object的新增API

1.Object.is

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

  • NaN和NaN相等
  • +0和-0不相等

2.Object.assign

​ 用于混合对象

const obj1 = {
    a: 123,
    b: 456,
    c: "abc"
}
const obj2 = {
    a: 789,
    d: "kkk"
}
//将obj2的数据,覆盖到obj1,并且会对obj1产生改动,然后返回obj1
const obj = Object.assign(obj1, obj2);  //{...obj1,...obj2}

3.Object.getOwnPropertyNames 的枚举顺序

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

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

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

4.Object.setPrototypeOf

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

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

3.面向对象简介

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

对比面向过程:

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

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

传统的构造函数的问题

  • 属性和原型方法定义分离,降低了可读性
  • 原型成员可以被枚举
  • 默认情况下,构造函数仍然可以被当作普通函数使用

类的特点

  • 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
  • 类中的所有代码均在严格模式下执行
  • 类的所有方法都是不可枚举的
  • 类的所有方法都无法被当作构造函数使用
  • 类的构造器必须使用 new 来调用

5.其他类的书写方式

getter和setter 访问器

​ ES5中Object.defineProperty可定义某个对象成员的读取和设置

​ 未定义get或者set就不能读或者写

​ 使用get和set的属性不在原型上,在实例上

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

set age(age){
	this.age = age;
} //创建一个age属性,并给它加上一个setter,给该属性赋值时,会运行该函数
//obj.age = 100 === setAge(100)

静态成员 写在构造函数上的成员,可以直接通过类名调用

//定义静态成员 实例中不存在   只有类上有
	static width = 50;  //ES6不能直接给静态成员变量赋值
	static method(){}

字段初始化器 (ES7)

  • 使用static的字段初始化,添加的是静态成员
  • 不使用static的字段初始化器,添加的成员位于对象上
  • 箭头函数在字段初始化是,指向当前对象,切函数定义在实例上,不是原型上
//实例成员不在原型链上 
class Test{
    b = 1;     //在实例对象上
static	a = 2;   //在类上
}
Test.a //2
new Test().b  //1

class Test{
				a = 1;
				b = 2;
				static c = 1;
				print=()=>{}
			}
let obj1 = new Test();

类表达式

const A = class{//匿名类,类表达式
	a = 1;
	b = 2;
}

装饰器 Decorator 标记属性或者方法过时了

function Obsolete(target,methodName,descript){
    const oldFunc = descript.value;//原函数
    descript.value = function(...arg){
        
    }
    console.log(target,methodName,descript);
}

6.类的继承

新的关键字

extends 表示继承

super

​ 直接当作函数调用,表示父类构造函数

​ 如果当作对象使用,则表示父类的原型

// 以前继承
function Animal(type, name, age) {
	this.type = type;
	this.name = name;
	this.age = age;
}
Animal.prototype.print = function() {
	return this.name
};

function Dog(name, age) {
	Animal.call(this, '犬', name, age);
}
let dog = new Dog('a', 1);
//Object.setPrototypeof 底层圣杯模式
Object.setPrototypeOf(Dog.prototype, Animal.prototype);


//ES6继承
class Animal {
	constructor(type, name, age) {
		this.type = type;
		this.name = name;
		this.age = age;
	}
	print() {
		return this.name;
	}
	jiao() {
		return '动物怎么叫';
	}
}

class Dog extends Animal {
	constructor(type, nam, age, sex) {
		super(type, name, age);
		this.sex = sex;
	}
	jiao() {
		super.jiao();
	}
}
const d = new Dog('犬', 1, 1, '男');
注意
  • ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数super(),
  • 如果子类不写构造函数,则由默认的构造函数,与父类一直,并且自动调用父类的构造器

【扩展】

  • 如果一个类不能被实例化,他就是一个抽象类(ES7暂未实现)
  • 正常情况下,this始终指向具体的类的对象

ES6解构

解构就是将一个对象或者数组的属性,提取出来一个变量中,不会影响原对象

1.对象解构

let obj = {
				name:'rbq',
				age:18,
			}
let name,age;
//解构 一般写法
({name,age} = obj);
//语法糖
let {name, age} = obj;  //name = rbq  age = 18

//解构不到 为undefined 可以直接赋值(默认值)  
//结构不到 可以使用默认值 如果可以解构到 使用解构的值
let {name, age,sex='男'} = obj; 

//非同名属性解构
let {name, age:gender,sex='男'} = obj;  //name = 'rbq' gender = 18 sex = '男'

2.数组解构

const numbers = [1,2,3,4];

//数组解构 
const {1:n1,2:n2} = numbers;  //n1 = 1 n2 = 2  类似对象解构

//语法糖
const [n1,n2] = numbers;  //n1 = 1 n2 = 2

//得到数组下标为4的数组的属性
const arr = [1,2,3,[1,2]];
const [,,,[,n] = arr; //n = 2
       
//解构剩余项,将剩余项 放在一个新的数组中 对象也可以
const [num,...nums] = arr;  //num = 1 nums= [2,3,[1,2]]

//数值交换
let a = 1, b = 2;
[b,a] = [a,b];

3.参数解构(解构应用最多就是解构参数4)

//形参位置直接解构
function print({name,age,sex}){
    console.log(name);
    console.log(sex);
    console.log(age);
}
const user = {
    name:'rbq',
    age:11,
    sex:'男',
}

ES6符号

符号是es6新增的数据类型,他是使用函数symbol(符号描述)来创建

最初的属性,是为了给对象设置私有属性

1.符号具有以下特点:

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

hero.gongji();

2.共享符号

根据符号名称得到同一个符号

Symbol.for('符号名/符号描述')  //获取共享符号

3.知名符号

一些具有特殊含义的共享符号,通过Symbol的静态属性得到

Sysmbol.hasInstance

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

obj  instanceof  A  ===   A[Symbol.hasInstance](obj)

ES6异步处理 Promise

1.事件循环

js运行的环境 被称为宿主环境

执行栈:

​ call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境

​ JS引擎永远执行的是执行栈的最顶部

异步函数

​ 某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserverPromise产生的回调进入微队列

MutationObserver用于监听某个DOM对象的变化

执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务

2.事件和回调函数的缺陷

​ 我们习惯于使用传统的回调或事件处理来解决异步问题。

事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数

dom.onclick = function(){
		
}

回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。

dom.addEventListener("click", function(){

})

本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。

一直以来,该模式都运作良好。

直到前端工程越来越复杂…

目前,该模式主要面临以下两个问题:

回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套,难以阅读。

//获取李华所在班级的老师的信息
ajax({
	url: "./data/students.json",
	success: function(data) {
		for (let i = 0; i < data.length; i++) {
			if (data[i].name === "李华") {
				const cid = data[i].classId;
				ajax({
					url: "./data/classes.json" + cid,
					success: function(data) {
						for (let i = 0; i < data.length; i++) {
							if (data[i].id === cid) {
								const tid = data[i].teacherId;
								ajax({
									url: "./data/teachers.json" + tid,
									success: function(data) {
										for (let i = 0; i < data.length; i++) {
											if (data[i].id === tid) {
												console.log(data[i]);
											}
										}
									}
								});
								return;
							}
						}
					}
				});
				return;
			}
		}
	}
}); //代码复杂度高,难以理解不便于维护

异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增

/*
            你同时给二十个女神表白,如果有女神同意,就拒绝其他的女神
            并且,当所有的女神回复完成后,你要把所有的回复都记录到日志进行分析
            用代码模拟上面的场景
        */
        function biaobai(god, callback) {
            console.log(`你向女神【${god}】发出了表白短信`);
            setTimeout(() => {
                if (Math.random() < 0.05) {
                    //女神同意拉
                    callback(true);
                } else {
                    callback(false);
                }
            }, Math.floor(Math.random() * (3000 - 1000) + 1000));
        }
        let agreeGod = null; //同意你的第一个女神
        const results = []; //用于记录回复结果的数组
        for (let i = 1; i <= 20; i++) {
            biaobai(`女神${i}`, result => {
                results.push(result);
                if (result) {
                    console.log(`女神${i}同意了`)
                    if (agreeGod) {
                        console.log(`你回复女神${i}: 不好意思,刚才朋友用我手机,乱发的`)
                    } else {
                        agreeGod = `女神${i}`;
                        console.log(`你终于找到了真爱`);
                    }
                } else {
                    console.log(`女神${i}拒绝了`)
                }
                

                if (results.length === 20) {
                    console.log("日志记录", results)
                }
            })
        }
		//这样要判断前一个事件完成时必须写一个标识量来标识,大型项目中,标识量过多会增加代码的复杂度,不便于维护

3.异步处理的通用模型

​ ES官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景。

值得注意的是,为了兼容旧系统,ES6 并不打算抛弃掉过去的做法,只是基于该模型推出一个全新的 API,使用该API,会让异步处理更加的简洁优雅。

​ 理解该 API,最重要的,是理解它的异步模型

1.ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled 和 settled

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBYqyj3v-1616035657470)(WEB/DuYi-ES6-master/DuYi-ES6-master/8. 异步处理/8-2. 异步处理的通用模型/assets/2019-10-18-17-28-30.png)]

  • unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
  • settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转

事情总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。

2.ES6将事情划分为三种状态: pending、fulfilled、rejected

  • pending: 挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
  • fulfilled:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
  • rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误

既然未决阶段有权力决定事情的走向,因此,未决阶段可以决定事情最终的状态!

我们将 把事情变为resolved状态的过程叫做:resolve,推向该状态时,可能会传递一些数据

我们将 把事情变为fulfilled状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息

始终记住,无论是阶段,还是状态,是不可逆的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXhUh8Zg-1616035657473)(WEB/DuYi-ES6-master/DuYi-ES6-master/8. 异步处理/8-2. 异步处理的通用模型/assets/2019-10-18-18-10-18.png)]

3.当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理。

  • fulfilled状态:这是一个正常的已决状态,后续处理表示为 thenable
  • rejected状态:这是一个非正常的已决状态,后续处理表示为 catchable

后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iY7oB2lP-1616035657475)(WEB/DuYi-ES6-master/DuYi-ES6-master/8. 异步处理/8-2. 异步处理的通用模型/assets/2019-10-18-18-10-38.png)]

4.整件事称之为Promise

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffQtrBhy-1616035657478)(WEB/DuYi-ES6-master/DuYi-ES6-master/8. 异步处理/8-2. 异步处理的通用模型/assets/2019-10-18-18-15-52.png)]

4.Promise的基本使用

const pro = new Promise((resolve, reject)=>{
    // 未决阶段的处理
    // 通过调用resolve函数将Promise推向已决阶段的fulfilled状态
    // 通过调用reject函数将Promise推向已决阶段的rejected状态
    // resolve和reject均可以传递最多一个参数,表示推向状态的数据
})

pro.then(data=>{
    //这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
    //如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
    //data为状态数据
}, err=>{
    //这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
    //如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
    //err为状态数据
})

细节

  1. 未决阶段的处理函数是同步的,会立即执行

  2. thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列

  3. pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数

  4. 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获

  5. 一旦状态推向了已决阶段,无法再对状态做任何更改

    Promise并没有消除回调,只是让回调变得可控

5.Promise的串联

当后续的Promise需要用到之前的Promise的处理结果时,需要Promise的串联

Promise对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:

  • 如果当前的Promise是未决的,得到的新的Promise是挂起状态
  • 如果当前的Promise是已决的,会运行相应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。

后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态

const pro1 = new Promise((resolve,reject)=>{
				resolve(1);
			})
			console.log(pro1) //fulfilled
			const pro2 = pro1.then(data => data*3,
			err => err*2);
			console.log(pro2);  //fulfilled
			pro2.then(data=>{
				console.log(data)
			})

如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。

const pro1 = new Promise((resolve, reject) => {
				resolve(1);
			})
			console.log(pro1) //fulfilled
			const pro2 = new Promise((resolve,reject) => {
					setTimeout(() => {
						resolve(2);
					}, 3000);
			});
			pro1.then(data => {
				console.log('结果出来了');
				return pro2;
			}).then(data => { 
				console.log(data); //2
			}).then(data => {
				console.log(data);  //undefined
			});

用Promise解决之前用回调请求老师信息的例子,消除了之前的回调地狱,让回调变得可控

//promise//获取李华所在班级的老师的信息  有先后顺序
//1. 获取李华的班级id   Promise
//2. 根据班级id获取李华所在班级的老师id   Promise
//3. 根据老师的id查询老师信息   Promise
const pro1 = ajax({
	url: './data/students.json',
})
pro1.then(data => {
	console.log(data)
	let cid;
	data.forEach(ele => {
		if (ele.name == '李华') {
			console.log(ele.classId);
			cid = ele.classId;
		}
	});
	return cid; //返回所在班级;
}).then(cid => {
	let tid;
	return ajax({
		url: './data/classes.json'
	}).then(cls => {
		cls.forEach(ele => {
			if (ele.id == cid)
				tid = ele.teacherId;
		});
		return tid; //返回班级老师id
	});
}).then(tid => {
	console.log(tid);
	return ajax({
		url: './data/teachers.json'
	}).then(tea => {
		console.log(tea, tid);
		tea.forEach(ele => {
			if (ele.id == tid) {
				console.log(ele); //老师信息
				return ele;
			}
		});
	})
})
//promise是为了消除了之前的回调地狱  让回调变得可控

6.PromiseAPI

  • then:注册一个后续处理函数,当Promise为fulfilled状态时运行该函数
  • catch:注册一个后续处理函数,当Promise为rejected状态时运行该函数
  • finally:[ES2018]注册一个后续处理函数(无参),当Promise为已决时运行该函数

构造函数成员 (静态成员)

  • resolve(数据):该方法返回一个fulfilled状态的Promise,传递的数据作为状态数据
  const pro  = Promise.resolve(1);
  //等效于
  pro = new Promise((resolve,reject)=>{
      resolve(1);
  })
  • 特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象

  • reject(数据):该方法返回一个rejected状态的Promise,传递的数据作为状态数据

    const pro  = Promise.reject(1);
    //等效于
    pro = new Promise((resolve,reject)=>{
        reject(1);
    })
    
  • all(iterable):这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。

    const arr = [];
    			for(let i=0;i<10;i++){
    				arr.push(new Promise((resolve,reject)=>{
    					setTimeout(()=>{
    						console.log(i+'完成');
    						resolve(i);
    					},Math.random()*2000)
    				}));
    			}
    			const pro = Promise.all(arr);
    			pro.then(data=>{
    				console.log(data);//等所有promise到已决状态再执行
    			})
    
  • race(iterable):当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象.如果有一个子promise已决,自己执行

8.async和await 语法糖

async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。

async

目的是简化在函数的返回值中对Promise的创建

async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。


async function test(){
    console.log(1);
    return 2;
}

//等效于

function test(){
    return new Promise((resolve, reject)=>{
        console.log(1);
        resolve(2);
    })
}

await

await关键字必须出现在async函数中!!!!

await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。


async function test1(){
    console.log(1);
    return 2;
}

async function test2(){
    const result = await test1();//await后续代码相当于在test1().then()中执行
    console.log(result); 
}

test2();

等效于:


function test1(){
    return new Promise((resolve, reject)=>{
        console.log(1);
        resolve(2);
    })
}

function test2(){
    return new Promise((resolve, reject)=>{
        test1().then(data => {
            const result = data;
            console.log(result);
            resolve();
        })
    })
}

test2();

如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行

利用async和awit解决上面的学生问题

async function getTeachers() {
	const stus = await ajax({
		url: './data/students.json'
	});
	let cid;
	stus.forEach(ele => {
		if (ele.name == '李华') {
			console.log(ele.classId);
			cid = ele.classId;
		}
	});

	const cls = await ajax({
		url: './data/classes.json'
	})
	let tid;
	cls.forEach(ele => {
		if (ele.id == cid)
			tid = ele.teacherId;
	});

	const teas = await ajax({
		url: './data/teachers.json'
	})
	let tea;
	teas.forEach(ele => {
		if (ele.id == tid) {
			console.log(ele); //老师信息
			tea = ele;
		}
	});
	return tea;
}
getTeachers();

Fecth Api

1.Fecth Api 概述

XMLHttpRequest的问题

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

Fetch Api 的特点

  1. 并非取代 AJAX,而是对 AJAX 传统 API 的改进,也是AJAX的一种新的实现方式,(Ajax是一套标准,一种模式)
  2. 精细的功能分割:头部信息、请求信息、响应信息等均分布到不同的对象,更利于处理各种复杂的 AJAX 场景
  3. 使用 Promise Api,更利于异步代码的书写
  4. Fetch Api 并非 ES6 的内容,属于 HTML5 新增的 Web Api
  5. 需要掌握网络通信的知识

2.基本使用

参数

该函数有两个参数:

  1. 必填,字符串,请求地址
  2. 选填,对象,请求配置
async function getPrivonces(){
				const pro = await fetch('http://101.132.72.36:5100/api/local');
				try{
					console.log(pro);
				}catch(err){
					console.log(err);
				}
			}

请求配置对象

  • 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”时有效).

返回值

fetch 函数返回一个 Promise 对象

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

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 的响应。

3.Request对象

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

req = new Request(url地址, 配置)
fetch(req);

注意点:3

尽量保证每次请求都是一个新的Request对象

function getRequestInfo() {
	if (!req) {
		const url = "http://101.132.72.36:5100/api/local";
		req = new Request(url, {});
		console.log(req);
	}
	return req.clone(); //克隆一个全新的request对象,配置一致
}

async function getProvinces() {
	const resp = await fetch(getRequestInfo())
	const result = await resp.json();
	console.log(result)
}

4.Headers对象

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

Headers对象中的方法:

  • has(key):检查请求头中是否存在指定的key值
  • get(key): 得到请求头中对应的key值
  • set(key, value):修改对应的键值对
  • append(key, value):添加对应的键值对
  • keys(): 得到所有的请求头键的集合
  • values(): 得到所有的请求头中的值的集合
  • entries(): 得到所有请求头中的键值对的集合
function getCommonHeaders() {
            return new Headers({
                a: 1,
                b: 2
            })
        }

        function printHeaders(headers) {
            const datas = headers.entries();
            for (const pair of datas) {
                console.log(`key: ${pair[0]},value: ${pair[1]}`);
            }
        }

5.文件上传

流程:

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

测试地址:http://101.132.72.36:5100/api/upload
键的名称(表单域名称):imagefile

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

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

const formData = new FormData(); //构建请求体
			
            formData.append("imagefile", inp.files[0]);
            const url = "http://101.132.72.36:5100/api/upload"
            const resp = await fetch(url, {
                method: "POST",
                body: formData //自动修改请求头中的表单格式
            });
            const result = await resp.json();
            return result;

ES6迭代器和生成器

1.迭代器

背景知识

  1. 什么是迭代?

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

  1. 迭代和遍历的区别?

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

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

  1. 迭代器

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

  1. 迭代模式

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

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

JS中的迭代器

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

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

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

含义:

  • next方法:用于得到下一个数据
  • 返回的对象
    • value:下一个数据的值
    • done:boolean,是否迭代完成
		//创建一个斐波拉契数列的迭代器
        function createFeiboIterator() {
            let prev1 = 1,
                prev2 = 1, //当前位置的前1位和前2位
                n = 1; //当前是第几位

            return {
                next(){
                    let value;
                    if (n <= 2) {
                        value = 1;
                    } else {
                        value = prev1 + prev2;
                    }
                    const result = {
                        value,
                        done: false
                    };
                    prev2 = prev1;
                    prev1 = result.value;
                    n++;
                    return result;
                }
            }
        }

        const iterator = createFeiboIterator();

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

概念回顾

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

可迭代协议

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

const arr = [5, 7, 2, 3, 6];
const iterator = arr[Symbol.iterator]();//迭代器创建函数
let result = iterator.next();
while (!result.done) {
const item = result.value; //取出数据
console.log(item);
//下一次迭代
result = iterator.next();
}

思考:如何知晓一个对象是否是可迭代的?
思考:如何遍历一个可迭代对象?

for-of 循环

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

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

展开运算符与可迭代对象

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

var obj = {
	a: 1,
	b: 2,
	[Symbol.iterator]:()=>{
		const keys = Object.keys(this);
		let i = 0;
		return {
			next: () => {
				const propName = keys[i];
				const propValue = this[propName];

				const result = {
					value: {
						propName,
						propValue
					},
					done: i >= keys.length
				}
				i++;
				return result;
			}
		}
	}
}
let arr = [...obj];
for (const item of obj) {
	console.log(item); // {propName:"a", propValue:1}
}

3.生成器 (Generator)

1.什么是生成器?

生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象

2.如何创建生成器?

生成器的创建,必须使用生成器函数(Generator Function)

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

//这是一个生成器函数,该函数一定返回一个生成器
function* method(){

}//调用生成器函数时 生成器内部并不执行那个

4.生成器函数内部是如何执行的?

生成器函数内部是为了给生成器的每次迭代提供的数据

每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置

yield是一个关键字,该关键字只能在生成器函数内部使用,表达“产生”一个迭代数据。

function* test() {
	//生成器内部是给生成器每次迭代提供数据
	//每次调用它的next()方法时      会导致生成器运行到下一个yield关键字的位置
	console.log('第1次');
	yield 1; //第一次next()时运行    yield产生一个数据
	console.log('第2次');
	yield 2; //第二次next()时运行
	console.log('第3次');
	yield 3; //第三次next()时运行
	console.log('第4次');
}
const generator = test();
console.log(generator.next()); //value = 1;           		done =false;
console.log(generator.next()); //value = 2;				done =false;
console.log(generator.next()); //value = 3;				done =false;
console.log(generator.next()); //value = undefined;		done =true;

5.有哪些需要注意的细节?

  • 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中,可以提前结束迭代
	yield 1;  //第一次next()时运行    yield产生一个数据
	return 10;  //第二次next()时运行 截至迭代  value =10
	yield 2;  //后续不运行
  • 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值
function* test1(){
	let inf = yield 1;   //inf = 第二次next传入的值
	yield 2 + inf;  
	}
  • 第一次调用next方法时,传参没有任何意义
  • 在生成器函数内部,可以调用其他生成器函数,但是要注意加上*号
yield* test1()

6.生成器的其他API

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

7.生成器的应用

​ 异步任务控制 ES6简化Promise的思想

function* task() {
    const d = yield 1;
    console.log(d)
	const resp = yield fetch("http://101.132.72.36:5100/api/local")
	console.log(resp);
    const result = yield resp.json();
    console.log(result);
        }

        run(task)

        function run(generatorFunc) {
            const generator = generatorFunc();
            let result = generator.next(); //启动任务(开始迭代), 得到迭代数据
            handleResult();
            //对result进行处理
            function handleResult() {
                if (result.done) {
                    return; //迭代完成,不处理
                }
                //迭代没有完成,分为两种情况
                //1. 迭代的数据是一个Promise
                //2. 迭代的数据是其他数据
                if (typeof result.value.then === "function") {
                    //1. 迭代的数据是一个Promise
                    //等待Promise完成后,再进行下一次迭代
                    result.value.then(data => {
                        result = generator.next(data)
                        handleResult();
                    })
                } else {
                    //2. 迭代的数据是其他数据,直接进行下一次迭代
                    result = generator.next(result.value)
                    handleResult();
                }
            }
        }

ES6集合

1.set 集合

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

set用于存放不重复的数据

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

new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果

  1. 如何对set集合进行后续操作
  • add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
    • set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
  • has(数据): 判断set中是否存在对应的数据
  • delete(数据):删除匹配的数据,返回是否删除成功
  • clear():清空整个set集合
  • size: 获取set集合中的元素数量,只读属性,无法重新赋值
  1. 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
  1. 如何遍历

1). 使用for-of循环
2). 使用set中的实例方法forEach

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

应用

// 两个数组的并集、交集、差集 (不能出现重复项),得到的结果是一个新数组
   const arr1 = [33, 22, 55, 33, 11, 33, 5];
   const arr2 = [22, 55, 77, 88, 88, 99, 99];

//并集
// const result = [...new Set(arr1.concat(arr2))];
console.log("并集", [...new Set([...arr1, ...arr2])]);
//交集
const cross = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
//差集
console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => cross.indexOf(item) < 0))

手动封装set

class Myset {
	constructor(iterator = []) {
		if (typeof iterator[Symbol.iterator] !== 'function') {
			throw new TypeError('你提供的不是一个可迭代的对象')
		}
		this._data = [];
		//原始数据加入到_data中
		for (const item of iterator) {
			this.add(item);
		}
	}
    //添加数据
	add(item) {
		if (!this.has(item)) {
			this._data.push(item);
		}
	}
	has(data) {
		for (const item of this._data) {
			return this.isEqual(data, item);
		}
	}
	isEqual(data1, data2) {
		if (data1 === 0 && data2 === 0) {
			return true;
		}
		return Object.is(data1, data2);
	}
	delete(data) {
			for (let i = 0; i < this._data.length; i++) {
				const element = this._datas[i];
				if (this.isEqual(data, element)) {
					this._data.splice(i, 1);
				}
			}
		}
		*[Symbol.iterator]() {
			for (const item of this._data) {
				yield item;
			}
		}
	getSize() {
		return this._data.length;
	}
}

2.Map集合

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

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

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

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

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

1.如何创建map

new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
  1. 如何进行后续操作
  • size:只读属性,获取当前map中键的数量
  • set(key, value):设置一个键值对,键和值可以是任何类型
    • 如果键不存在,则添加一项
    • 如果键已存在,则修改它的值
    • 比较键的方式和set相同
  • get(value): 根据一个键得到对应的值
  • has(value):判断某个键是否存在
  • delete(value):删除指定的键
  • clear(): 清空map
  1. 和数组互相转换

和set一样

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

手写map

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 = item[0];
            const value = item[1];
            this.set(key, value);
        }

    }
    set(key, value) {
        const obj = this._getObj(key);
        if (obj) {
            //修改
            obj.value = value;
        }
        else {
            this._datas.push({
                key,
                value
            })
        }
    }
    get(key) {
        const item = this._getObj(key);
        if (item) {
            return item.value;
        }
        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 
     */
    _getObj(key) {
        for (const item of this._datas) {
            if (this.isEqual(item.key, key)) {
                return item;
            }
        }
    }

    has(key) {
        return this._getObj(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);
        }
    }
}

ES6代理与反射

1.属性描述符

Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息

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

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

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

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

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

存取器属性

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

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

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

2.Reflect反射

  1. Reflect是什么?

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

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

  1. 它可以做什么?

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

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

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

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

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

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

  1. 它里面到底提供了哪些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

3.Proxy代理

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

//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象
new Proxy(target, handler)

4.应用

观察者模式

//反射和代理实现观察者模式
function observer(target){
const div = document.getElementsByClassName('container')[0];
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(let prop of Object.keys(target)){
		html +=`
			<p>
				<span>${prop}:</span>
				<span>${target[prop]}</span>
			</p>
						`}
		div.innerHTML = html;
	}
	return proxy;
}
let obj2 ={
	a : 1,
	b : 2,
} 
let obj1 = observer(obj2);

ES6增强数组

1.新增API

静态方法

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

实例方法

  • find(callback): 用于查找满足条件的第一个元素
  • findIndex(callback):用于查找满足条件的第一个元素的下标
  • fill(data):用指定的数据填充满数组所有的内容
  • copyWithin(target, start?, end?): 在数组内部完成复制
    为什么还需要用Reflect实现一次?**

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

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

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

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

  1. 它里面到底提供了哪些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

3.Proxy代理

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

//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象
new Proxy(target, handler)

4.应用

观察者模式

//反射和代理实现观察者模式
function observer(target){
const div = document.getElementsByClassName('container')[0];
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(let prop of Object.keys(target)){
		html +=`
			<p>
				<span>${prop}:</span>
				<span>${target[prop]}</span>
			</p>
						`}
		div.innerHTML = html;
	}
	return proxy;
}
let obj2 ={
	a : 1,
	b : 2,
} 
let obj1 = observer(obj2);

ES6增强数组

1.新增API

静态方法

  • Array.of(…args): 使用指定的数组项创建一个新数组
  • Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组。将类数组可迭代对象转为新的数组

实例方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值