背景:
搭建前端框架时,需要对提示框进行定制化。开始查看MessageBox的源码,在梳理源码代码的过程中,我发现MessageBox里使用到了Promise。当我们使用提示框代码的时候可以在函数后面加then和catch分别处理确定和取消按钮的回调,可以肯定使用了Promise对象,这一点毫无疑问。但是它是如何实现的在点击按钮的时候才会触发then或catch的回调函数的呢?带着这个疑问,我开始梳理MessageBox组件的源码。
MessageBox组件的文件组成:
- index.js文件:导出MessageBox对象,实际上是导出main.js里导出的MessageBox对象,可以直接查看src/main.js文件
- src/main.js: 该文件是核心文件,它定义了MessageBox对象。它并不是面向对象编程,所以直接在MessageBox对象上设置了alert、confirm、prompt提示框。
- src/main.vue: 该组件主要提供了提示框的VNODE以及提示框关闭等逻辑
至此,对于我们前端框架的提示框定制化任务就已经可以进行了,通过模仿src/main.js定制alert、confirm和prompt提示框的方法在MessageBox上进行定制就可以大功告成了。但还是想知道里面具体是如何实现的,那就只能继续探究了。
关键代码
第一遍我先对整体代码做了大概的了解,梳理过程中发现了MessageBox函数对象的定义,在里面有一段代码引起了我的注意,如代码段1:
return new Promise((resolve, reject) => { // eslint-disable-line
msgQueue.push({
options: merge({}, defaults, MessageBox.defaults, options),
callback: callback,
resolve: resolve,
reject: reject
});
复制代码
在这段代码里是将resolve和reject的作为对象的属性进行了引用,并不是像我们之前使用的直接resolve()或者reject(),而msgQueue又是一个数组队列,该队列里保存了新组建的对象。至此可以推断出msgQueue队列里保存的是提示框的配置信息,由于会存在多个提示框的情况。那么resolve和reject在何时会触发调用呢?但基本上可以肯定的一点是resolve和reject执行的时候,就是我们自己绑定then和catch回调函数执行的时刻。
调用执行resolve和reject的代码:
通过代码搜索,发现在src/main.js文件里定义了defaultCallback里调用执行了resolve,如代码段2:
if (action === 'confirm') {
if (instance.showInput) {
currentMsg.resolve({ value: instance.inputValue, action });
} else {
currentMsg.resolve(action);
}
} else if (currentMsg.reject && (action === 'cancel' || action === 'close')) { // 如果用户点击的是取消按钮,则将取消按钮的值传递给catch函数
currentMsg.reject(action);
}
复制代码
而且这里出现了confirm、cancel和close关键字段。这些关键字段可以在我们绑定的then和catch的回调函数里获取到。这样就实现了点击确定按钮后执行then里的回调,点击取消按钮执行catch里的回调函数。实现原理并不复杂,但归根结底是对代码底层原理的熟练掌握。
引申思考:
- 之前大家应该都遇到过JavaScript定义线程挂起的代码,代码段3:
function sleep(time){
return new Promise(function(resolve,reject){
setTimeout(resolve,time)
})
}
async function main(){
await sleep(5000)
console.log('hello world')
}
复制代码
执行结果是5秒中之后打印”hello world”由于JavaScript引擎是单线程执行这样就可以实现线程挂起的效果。而setTimeout将resolve的执行延迟了,和MessageBox里的resolve被其他对象引用出去执行的效果是一样的。普通人遇到这段代码后是简单的记忆,大牛是会对这段代码背后的原理进行分析。向大牛致敬!!!
MessageBox组件构建思路梳理:
之前一位大牛说过,“有思路就是王道”,这句话一点没错。目前MessageBox的核心的问题已经弄清楚,那就继续梳理下MessageBox组件的整体构建思路吧。
首先,我们平时使用的时候一般都是这样,如代码段4:
open() {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
复制代码
然后页面弹出提示框:
首先,先分析this.$confirm()。在src/main.js里可以找到定义的函数,该函数返回了MessageBox函数执行的结果,如果支持Promise,则返回Promise对象;不支持Promise,则返回null,则不能使用then。
MessageBox函数将用户触发的提示框信息封装到新的对象,并保存到msgQueue队列。该函数并不是立即弹出提示框,如代码段1所示。
MessageBox函数执行的同时调用了showNextMsg函数,该函数的目的是弹出提示框,同时该函数将msgQueue里保存的对象出队,并排队执行弹窗。
弹窗是如何实现的呢?看下面这段代码,代码段5:
import msgboxVue from './main.vue';
const MessageBoxConstructor = Vue.extend(msgboxVue);
复制代码
在Vue框架里,vue组件也都是函数。Vue.extend方法能够创建组件的构造函数,这样可以实现创建新的基于组件的实例对象,如代码段6:
const initInstance = () => {
instance = new MessageBoxConstructor({
el: document.createElement('div')
});
instance.callback = defaultCallback;
};
复制代码
main.vue组件里,对visible属性进行了监听。在main.js里创建的instance对象上绑定了visible属性。这样就可以控制提示框的显示和隐藏了。
最后,当用户点击提示框里所有按钮,提示框都会隐藏,在main.vue里会调用this.doClose()方法,该方法内又调用了this.callback(), 该方法内调用了this.defaultCallback(), 然后就触发了then或catch里的回调。
整体构建思路流程图如下:
总结,代码编程的基础能力比较重要,但如果仅仅只是停留在调用API的层面,则无法扩展自己的能力。看别人的代码主要分析思路,然后才是语法。而看别人代码又比较耗时,尤其是没有任何注释。我使用的方法是先按照自己猜测,然后去代码里找去印证。看源码不需要太在意细节,先梳理清楚。
后面会编写一篇总结分析Promise的使用的文章,敬请期待。