(参照尚硅谷教学视频,带了点自己的理解)
前期准备
一些概念
实例对象与函数对象
首先,我们要知道什么是函数,函数就是Function的一个实例,如
function Hello(){
console.log('hello')
}
当我们用new的方式去调用该函数时,它就充当了构造函数的作用,返回一个实例对象,如
var h = new Hello(); //此时Hello为构造函数,它返回了一个实例对象
那么什么时候Hello为函数对象呢?当Hello调用.XXX的时候,它就作为一个函数对象了,如
Hello.bind({})
因为Hello其实它本身是由Function构造函数构造出来的,所以其也能调用Fuction.prototype里的一些方法,如bind,call,apply等。
同步回调与异步回调
同步回调就是回调函数会立即执行,由于高阶函数必须等待同步回调执行完成,所以同步回调也称为阻塞回调——回调的执行会阻塞调用者函数的执行。如:数组的forEach,map,filter等
异步回调就是回调函数不会立即执行,它会把回调函数放入到回调队列中等待处理,如:定时器回调,ajax回调等。高阶函数是派发回调任务的函数,而不是运行它的函数。
要检验一个回调函数是同步回调还是异步回调,只需要在回调函数内部和之后的地方打一个输出,然后观察输出的先后顺序即可。
常见的内置Error
ReferenceError:引用的变量不存在。如
console.log(a) //a没有声明
TypeError:数据类型不正确,有两种常见情景会引发这个错误。
第一个情景
var a = null
console.log(a.xxx) //报错:cannot read property 'xxx' of null
第二个情景
var a = {}
a.xxx() //报错:a.xxx is not a function
RangeError:其值超出了该类型允许的范围,如
function fn(){
fn()
}
fn()
//报错:maximum call stack size exceeded
//递归次数超出了递归调用栈允许的调用次数
SyntaxError:语法错误,如
a=""""
错误处理
try..catch..捕获错误
我们可以把可能出错的代码段放入try语句块中,在catch语句块中做相应的错误处理,如
try{
var a = null
console.log(a.xxx)
}
catch(err){
console.log(err)
}
另外,如果想看err对象里的详细内容,可以通过以下操作查看。
1. 在F12->source中点击对应的代码文件,在catch块中对代码打断点(直接点击行数就是打断点),如图所示:
2. 重新刷新页面后,在souce界面中的右侧应该能看到具体的err信息,如图所示
这里可以看到error主要有两个属性:message和stack,message描述了错误信息,stack是错误的调用堆栈信息。
通过throw new Error()主动抛出错误
这种情况更适合处理不是内置错误的情况,如:
function divide(a, b) {
if (b === 0) throw new Error("被除数为0,结果溢出");
return a / b;
}
try {
divide(100, 0);
} catch (error) {
console.log(error);
}
promise的理解和使用
promise是什么
1. 抽象表达:promise是js中解决异步编程的新的解决方案(旧的是谁?什么是异步编程?)
2. 具象表达:从语法层面讲,promise就是一个构造函数,从功能层面讲,promise对象可以封装异步操作并获取其结果
补充:关于第一点中的,旧的是谁?什么是异步编程?
在没有出现promise之前,都是通过回调函数的方式来实现异步编程的。那么,什么是异步编程呢?
首先,要知道什么是异步和同步,同步是指按照代码的书写顺序一行行的去执行,上一段代码执行完毕才能执行下一段代码,平时我们编写的js代码都是同步代码,但在某些场景下如设置定时器,发送Ajax请求,事件绑定这些我们编写的才是异步代码。异步可以理解为一种并行的处理方式,被系统认为是异步的任务(代码块),系统会把这个任务丢到一个队列中,然后继续执行同步代码,这样做的好处是如果该任务耗时较长则不会阻塞主线程(同步代码)的执行。在第一次同步代码执行结束后,系统会开始从任务队列中取出之前的回调函数执行。
在编写异步代码时,我们有时会遇到这样的需求,比如先发起一个ajax请求,在这个请求返回数据后,再发起一个ajax请求并且要附带前面返回的数据,那么这里就涉及到一个同步等待结果的问题,也就是异步编程的问题了。在promise出现之前,传统的解决方式是,给请求的api传入一个回调函数,在结果返回时API会自动调用这个回调函数,通常这种回调的参数API都会设计好给我们。但这样也会存在一些瑕疵,后面会讲。
promise状态改变
promise有三种状态,分别是pending,resolved,rejected。
如果在new promise的过程中没有调用resolve或者reject,那么promise对象默认是pending状态;
如果调用了resolve,那么对象为resolved状态;
如果调用了reject,那么对象为rejected状态。
而且,一旦状态变为resolved或者rejected后,状态就不再改变。
为什么要用promise
1. 指定回调函数的方式更加灵活
对于旧方式,必须在启动异步任务前指定好回调函数。
这就是我所说的那个瑕疵,回调函数什么时候给?只能在api传递回调参数的时候给,如果我想用这个返回结果的时机不是在结果马上返回后呢,这就有点麻烦了,目前我想到的解决办法就是用一个全局变量来接收结果,然后在想用的时机用上。
对于新方式promise,在启动异步任务后会得到一个promise对象,我可以在我想要的时机调用then方法,并设置成功和失败的回调函数,而不需要考虑如何保存上一个异步任务返回的结果。
2. 支持链式调用,避免回调地狱。
什么是回调地狱?举个例子,我发起一个请求A等待响应结果,然后再拿这个响应结果发起请求B等待响应结果,再拿这个响应结果发起请求C。
requestA(data,success(resp1){
requestB(resp1,success(resp2){
requestC(resp2,success(resp3){
...
})
})
})
我们发现上面代码大量使用了回调函数(将一个函数作为参数传递给另个函数)并且有许多 })
结尾的符号,使得代码看起来很混乱,这就叫回调地狱。
在Promise中,因为Promise.then方法会返回一个promise对象,这个对象中有一个状态表示这个承诺是否被实现了,以及保存了对应的结果数据,比如如果状态是resolved,那么当你在then方法中设置成功的回调时,then就会调用你这个回调并且将成功的结果数据带进去,因此可以利用then方法进行链式调用,正好避免了回调地狱的问题。例子代码如下:
//假设request方法返回的是一个promise对象
requestA(data)
.then((resp)=>{
return requestB(resp)
})
.then((resp)=>{
return requestC(resp)
})
.catch(err=> console.error(err))