
本文共 35717 字,大约阅读时间需要 119 分钟。
文章目录
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库,都要求数据不可变,使用常量可以一定程度上保证这一点。
注意的细节:
- 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
- 常量的命名
- 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
- 普通的常量:使用和之前一样的命名即可
- 在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的缺陷:
- 如果和形参配合使用,容易导致混乱
- 从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图
ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
语法:
function (...形参名){ }
细节:
- 一个函数,仅能出现一个剩余参数
- 一个函数,如果有剩余参数,剩余参数必须是最后一个参数
3.展开运算符
使用方式:...要展开的东西
4.明确函数用途
ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用
new.target //该表达式,得到的是:如果没有使用new来调用函数,则返回undefined//如果使用new调用函数,则得到的是new关键字后面的函数本身
5.箭头函数
回顾:this指向
- 通过对象调用函数,this指向对象
- 直接调用函数,this指向全局对象
- 如果通过new调用函数,this指向新创建的对象
- 如果通过apply、call、bind调用函数,this指向指定的数据
- 如果是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(); //0obj.print1(); //undefined
简写:
如果箭头函数参数只有一个参数可以省略()
参数=>{ }
如果箭头函数只有返回语句
,可以省略大括号和()
参数 => num % 2 !== 0 //判断是否是奇数
简写时,如果返回对象时,返回的对象要写在()内
const sum = (a,b) =>({ a, b,sum:a+b })
注意细节
- 箭头函数里面没有
this
、arguments、new.target,如果使用了,则使用的是函数外层对应的。 - 箭头函数没有prototype
- 箭头函数不能
使用场景
- 临时性使用的函数,并不会可以调用它,比如
- 事件处理函数
- 异步处理函数
- 其他临时性的函数
- 为了绑定外层this的函数
- 在不影响其他代码的情况下保持代码的简洁性
ES6对象
1. 新增的对象字面量语法
- 成员速写
如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写
let obj = { loginId, loginPwd, id: Math.random() }
- 方法速写
对象字面初始化时,方法可以省略冒号和function关键字
const user = { sayHello(){ console.log('方法速写') }}
- 计算属性名
有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在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产生改动,然后返回obj1const 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 //2new Test().b //1class 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个线程:
- JS引擎:负责执行执行栈的最顶部代码
- GUI线程:负责渲染页面
- 事件监听线程:负责监听各种事件
- 计时线程:负责计时
- 网络线程:负责网络通信
当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。
JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环
。
事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:
- 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
- 微任务(队列):MutationObserver,Promise产生的回调进入微队列
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为状态数据})
细节
-
未决阶段的处理函数是同步的,会立即执行
-
thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
-
pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数
-
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
-
一旦状态推向了已决阶段,无法再对状态做任何更改
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查询老师信息 Promiseconst 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的问题
- 所有的功能全部集中在同一个对象上,容易书写出混乱不易维护的代码
- 采用传统的事件驱动模式,无法适配新的 Promise Api
Fetch Api 的特点
- 并非取代 AJAX,而是对 AJAX 传统 API 的改进,也是AJAX的一种新的实现方式,(Ajax是一套标准,一种模式)
- 精细的功能分割:头部信息、请求信息、响应信息等均分布到不同的对象,更利于处理各种复杂的 AJAX 场景
- 使用 Promise Api,更利于异步代码的书写
- Fetch Api 并非 ES6 的内容,属于 HTML5 新增的 Web Api
- 需要掌握网络通信的知识
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
:字符串,请求方法,默认值GETheaders
:对象,请求头信息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.文件上传
流程:
- 客户端将文件数据发送给服务器
- 服务器保存上传的文件数据到服务器端
- 服务器响应给客户端一个文件访问地址
测试地址: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.迭代器
背景知识
- 什么是迭代?
从一个数据集合中按照一定的顺序,不断取出数据的过程
- 迭代和遍历的区别?
迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完
遍历强调的是要把整个数据依次全部取出
- 迭代器
对迭代过程的封装,在不同的语言中有不同的表现形式,通常为对象
- 迭代模式
一种设计模式,用于统一迭代过程,并规范了迭代器规格:
- 迭代器应该具有得到下一个数据的能力
- 迭代器应该具有判断是否还有后续数据的能力
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用于存放不重复的数据
- 如何创建set集合
new Set(); //创建一个没有任何内容的set集合new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
- 如何对set集合进行后续操作
add
(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作- set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
has
(数据): 判断set中是否存在对应的数据delete
(数据):删除匹配的数据,返回是否删除成功clea
r():清空整个set集合size
: 获取set集合中的元素数量,只读属性,无法重新赋值
- 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值const arr = [...s];
- 如何遍历
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.如何创建map
new Map(); //创建一个空的mapnew Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
- 如何进行后续操作
- size:只读属性,获取当前map中键的数量
- set(key, value):设置一个键值对,键和值可以是任何类型
- 如果键不存在,则添加一项
- 如果键已存在,则修改它的值
- 比较键的方式和set相同
- get(value): 根据一个键得到对应的值
- has(value):判断某个键是否存在
- delete(value):删除指定的键
- clear(): 清空map
- 和数组互相转换
和set一样
- 遍历
- 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反射
- Reflect是什么?
Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能
由于它类似于其他语言的反射,因此取名为Reflect
- 它可以做什么?
使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能
- 这些功能不是已经存在了吗?为什么还需要用Reflect实现一次?
有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹
这种理念很大程度上是受到函数式编程的影响
ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象
因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。
- 它里面到底提供了哪些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 +=`${ prop}: ${ target[prop]}
`} 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实现。
- 它里面到底提供了哪些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 +=`${ prop}: ${ target[prop]}
`} 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匹配
发表评论
最新留言
关于作者
