文章目录
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(); //0
obj.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产生改动,然后返回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个线程:
- 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查询老师信息 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的问题
- 所有的功能全部集中在同一个对象上,容易书写出混乱不易维护的代码
- 采用传统的事件驱动模式,无法适配新的 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(); //创建一个空的map
new 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 +=`
<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实现。
- 它里面到底提供了哪些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匹配