1. Promise
一种异步编程的解决方案
情景:网络请求时,请求是无法立即拿到结果的,需要等请求返回时,通过回调函数获取请求的结果,如果请求比较复杂,嵌套层级过多,则不利于编程和处理维护等
Promise可以解决回调地狱问题!!
缺点:不可取消
1.1 基本语法
使用格式:
// Promise接收一个函数作为参数,且函数接收两个函数作为参数,一个为成功的回调,一个为异常的回调
// then接收一个函数作为参数,该函数有参数,表示请求的结果
new Promise((resolve,reject)=>{
// 异步操作
}).then(res=>{
// 业务逻辑
}).catch(err=>{
// 失败逻辑
})
Promise
类就是用于封装异步操作的,所以如果存在异步都可以使用其封装。典型的setTimeout
new Promise((res,rej)=>{
// 异步请求
setTimeout(()=>{
// res()和rej()都可以接受参数,前者表示成功,后者表示失败
// 请求成功会进入到 then() 回调中!
// 请求失败会进入到 catch() 回调中!
try{
res() //成功
}
catch(e){
rej() // 失败
}
},1000)
}).then(()=>{
// 业务处理
// 业务处理完,如果需要根据处理结果发送异步请求,只需要继续返回一个Promise即可
return new Promise((res,rej)=>{
setTimeout(()=>{
res()
},1000)
}).then(()=>{
// 其他
}).catch()
注意:返回值为Promise
时,可以将将原来的嵌套变成了链式结构!!每个Promise
中进行了异步请求,在then
中处理了返回结果。
注意:rejec
t可传可不传
特别注意:then入参也可以是两个函数,则前一个为成功的回调,后一个为失败的回调
...
.then(res=>{},err=>{})
当页面切换时,如果后台还处于promise的等待结果状态则会发生错误,如果不想显示出来,想自己处理,则建议使用.catch()处理流程
1.2 Promise三种状态
sync:同步
async:异步
三种状态:
- Pending:等待
- Fulfilled:成功,调用resolve(val),然后进入then回调逻辑
- Rejected:失败,调用reject(val),然后进入catch回调逻辑
注意:在同步代码中使用try{} catch(){} finally{}
代码块来捕获错误,在异步函数中使用.catch()
回调来捕获异常
1.3 Promise链式调用
第一种方式是1.1中的new Promise
然后处理异步操作,在then
中处理返回结果
第二种方式是直接使用:Promise.resolve()
,这种适用于无需异步操作,只是为了回调then
第三种方式直接return xxx
,省略写法,xxx
为传递到then
中的参数
// 第一种 返回new Promise包裹异步操作,完成链式调用
.then(r=>{
// 业务操作
return new Promise(resolve=>{
// 异步操作
})}).then()
// 第二种,返回Promise.resolve() or Promise.reject(),直接回调then or catch
.then(r=>{
// 业务操作
return Promise.resolve(xxx)
}).then()
// 第三种,直接返回参数 or 抛出异常,直接回调then or catch
.then(r=>{
// 业务操作
return xxx;
// throw 'error message'
}).then()
1.4 Promise.all
用于同时请求多个异步,等待所有结果都返回时,再返回结果
- 如果都resolve进入then回调,参数是顺序的返回值数组
- 如果任何一个reject则进入catch回调,参数是错误信息
Promise.all
接收一个Promise
实例的数组,然后.then
中参数为返回的结果数组!!按照顺序。
Promise.all([
new Promise(),
new Promise()
]).then(resultArr=>{})
1.5 Promise.race
入参和all相同,特殊的是,仅仅返回最先resolve或reject的那个值,可用于控制请求超时:
const timeout = async(time)=>new Promise(resolve=>setTimeout(resolve('请求超时!'),time))
async function test(){
Promise.race([
异步请求,
timeout(2000)
])
.then()
.catch()
}
test()
此时,当异步请求返回时间超过2秒时,timeout函数会先返回!进入then接受到请求超时!
2 ES6中的箭头语法=>
()=>{}
:是ES6声明一个函数所使用的语法,同普通函数相比,该语法自动绑定环境中的this,即一层层从内向外查找this
箭头表达式有多种:
(参数1、参数2。。。)=>{函数}
(参数1、参数2。。。)=>表达式
(参数1)=>{函数}
参数1=>{函数}
参数1=>表达式
相当于:var f=function(参数){return 表达式}
注意:展开的函数声明中的this和箭头表达式this不是一个含义,前者代指函数(谁调用指谁),后者代指实例(绑定函数外环境中的this),为了使展开后含义不变,可以在函数闭包外引用this,然后在内部使用新引用变量:
let that=this;
this.timerID=setInterval(function(){return that.tick();},1000);
也可以使用bind
进行绑定:函数.bind(调用对象,参数)
bind用法:
bind接收的参数,第一个为函数内部this指向的对象,其后为函数传递的参数
bind返回值为修改了this指向的函数,原函数不变
注意:bind和call和apply区别:bind只会修改指向,不会立即调用
3 export和import和commonJS
import不能存在于动态语句中,如if
3.1 CommonJS
// 导出
module.export = {
flag: true,
run(){},
name: "pp"
}
// 导入
let {flag,run,name} = require('./xxx.js)
// 或
let x = require('./xxx.js)
let flag = x.flag
注意:require()
接受两个参数,第一个数组表示所要加载的Javascript文件,第二个是加载完成后所要运行的回调函数。
require([
"script1.js",
"script2-a.js",
"script2-b.js",
"script3.js"
],
function(){
initScript1();
initScript2();
initScript3();
}
);
原生的require()
不支持按次序加载,所以到底先加载哪个,无法事前知道,require()
只保证这四个文件全部加载完成之后,才会运行所指定的回调函数。
如果按次序加载对你很重要,你可以使用官方提供的order
插件。
3.2 ES6 export/import
// 导出:写法1
export let name = 'pp
export let age = 12
// 导出:写法2
let name = "pp"
let age = 213
export {name,age}
// 导入
import {flag,sum} from './xxx.js'
console.log(flag)
// html中使用
<script src="./xxx.js" type="module"></script>
3.2.1 export default
导出默认模块,普通导出:导出的名字和导入的名字必须相同才能正确获取
export default
导出则为匿名,可以在导入的时候去自定义名字,一个文件default
导出的只能存在一个,导入的时候不需要{}
,表示导入默认导出项
export default x
import y from 'xxx.js'
3.2.2 统一全部导出
由于对于非default
导入需要 保证导入的变量名和导出的相同,当导入变量很多,则可以采用导入全部作为一个变量,然后去使用
// 全部导出
import * as x from 'xxx.js'
console.log(x.name)
3.2.3 同时导出默认和普通模块
import 名称,{xx,xx,xx} from 模块 // 默认和按需同时导入,默认的必须写在前面
3.2.4 导出模块改名
对于默认导出模块,没有固定名称,导入时进行命名使用,对于普通模块,导入使用时必须和导出名相同,可以使用as进行重命名
import {xx,xx,xx} from 模块文件名字
import {xx as 别名,xx,xx} from 模块文件名字
3.3 es6和commonjs导出区别
- 在模块导出基本类型数据时,
require
方式无法获取内部最新数据而且修改变量值会脱钩,import
方式外部无法修改模块的数据(只读),但是非默认导出属性可以获取内部最新值。 - 在模块导出引用类型数据时,外部和内部的修改都可以被观测到!
4 class
class中定义的方法和属性都不可以直接通过类名调用,可以将class代码块看作静态的,无法运行的
class中定义在this上的,属于实例属性,需要初始化才能使用,所有挂载在this上的方法或者属性,在实例上都是会复制一份自己的。
class中的类方法是定义在prototype上,可以通过:类名.prototype.方法()
来调用,也可以实例化后通过:实例名.方法()
调用
class Person{
constructor(){
this.name="pp"
this.say=()=>{console.log('say')}
}
show(){
console.log('show')
}
}
var p = new Person()
console.log(p)
直接在class
类的prototype
上定义属性和方法可以在所有该类的实例上调用,同时也可在类上通过原型调用,相当于在类中定义类方法。
获取实例对象的属性(不包括原型上):Object.getOwnPropertyNames(实例对象)
注意:ES6中将toString改成了不可枚举类型。
注意:类.prototype 而 实例.proto ;这两者等价,在这上面添加属性都会影响所有实例
注意:class 不存在变量提升。
4.1 class的name
定义class有两种方式:
class Person{} // name为Person,可以new Person()获取实例
const P = class Person{} // name为P,可以new P()获取实例,里面的Person相当于无效,可简化
const P = class{} // 效果同上
class的立即执行式:
let person = new class{
constructor(name){
this.name=name
}
}("pp")
4.2 class私有方法
一种方法是用命名区分,前面加上下划线,但是依然可以访问到,另一种是,放到类外部:
//PrivateMethod.js
export default class PrivateMethod{
// 构造
constructor() {
}
getName(){
priName();
}
}
function priName(){
console.log('私有方法测试');
}
//index.js
import PriMe from './PrivateMethod';
let prime = new PriMe();
prime.getName();
4.3 class静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class StaticMethod{
//定义静态方法
static getAge(){
return '获取Age的静态方法';
}
}
//通过类名直接调用
console.log(StaticMethod.getAge());
如果A继承B,则A的静态方法,可以被B继承。调用父类的静态方法:super(xxx)
说明:静态方法只能在静态方法中调用,不能再实例方法中调用。
ES6中只有静态方法,没有静态属性,如需定义静态属性:A.xxx=xxx
这样来定义
ES7中:
//StaticMethod.js
//ES7提案 定义静态属性
static lastName = 'pcaca';
//ES7定义实例属性
height = '150cm'; // 如果不写this,在类中默认定义实例属性
参考:https://blog.csdn.net/pcaxb/article/details/53759637
5 async await
这是一个语法糖用于将异步转换成同步
async
用于将一个函数标识为异步函数,他的返回值会被编译器特殊处理,返回成一个Promise对象
await
用于暂停标识为异步的函数,直到该函数返回了结果,再继续往下执行代码。
这两个关键字常用于异步回调中:
// 使用then
async function test(){}
test().then().catch()
// 使用async await
try{
let result = await test()
}catch(e){}
特殊的:如果在多个地方使用await可能使性能下降!
async function(){
await test();// 假设3秒返回
await test();// 在前一个3秒返回后,执行当前再等3秒才能返回,总共花费6秒
}
解决办法:将异步函数放在变量中,再await
async function(){
cosnt t1 = test();
cosnt t2 = test();
await t1;
await t2;// 两个同时执行并等待,最终执行时间为3秒
}
6 解构赋值
可以用于结构数组和对象,达到快速赋值的效果:
let a = [1,2,3];
let [x,y,z] = a; // xyz分别赋值为123
let a = {name:'p',age:1}
let {name,age} = a; // name=p age=1
注意:解构赋值,如果所解构的原对象是一维数组或一维对象(对象中属性的值都是基本数据类型),其本质就是对基本数据类型进行等号赋值,那它就是深拷贝;
如果是多维数组或嵌套对象,其本质就是对引用类型数据进项等号赋值,那它就是浅拷贝;
最终的结论就是:解构赋值是浅拷贝(因为它确实不能对多维数组或对象达到深拷贝的作用);