https://www.jb51.net/article/213413.htm
后续尝试了 alert() 和 prompt() 这两个跟 confirm 类似的弹对话框函数,情况都与此相同,它们都会跳过页面渲染先被执行。
还有更诡异的情况,我们给某一个 div 里赋值后,立刻 alert 此 div 里的内容,会发现 alert 显示正确的内容,而 div 里的内容却没有更新,并且会一直阻塞到我们点击确定。
alert、prompt、confirm 三个函数都类似,接下来我们就用最简单的 alert 来说。
JavaScript 的线程模型
JavaScript 引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行 JavaScript 程序,初衷是为了减少 DOM 等共享资源的冲突。可是单线程永远会面临着一个问题,那就是某一段代码阻塞会导致后续所有的任务都延迟。又由于 JavaScript 经常需要操作页面 DOM 和发送 HTTP 请求,这些 I/O 操作耗时一般都比较长,一旦阻塞,就会给用户非常差的使用体验。
于是便有了事件循环(event loop)的产生,JavaScript 将一些异步操作或 有I/O 阻塞的操作全都放到一个事件队列,先顺序执行同步 CPU代码,等到 JavaScript 引擎没有同步代码,CPU 空闲下来再读取事件队列的异步事件来依次执行。
这些事件包括:
setTimeout() 设置的异步延迟事件;
DOM 操作相关如布局和绘制事件;
网络 I/O 如 AJAX 请求事件;
用户操作事件,如鼠标点击、键盘敲击。
解决
明白了原理, 再解决这个问题就有了方向,我们来分析这个问题:
1.由于页面渲染是 DOM 操作,会被 JavaScript 引擎放入事件队列;
2.alert() 是 window 的内置函数,被认为是同步 CPU代码;
3.JavaScript 引擎会优先执行同步代码,alert 弹窗先出现;
4.alert 有特殊的阻塞性质,JavaScript 引擎的执行被阻塞住;
5.点击 alert 的“确定”,JavaScript 没有了阻塞,执行完同步代码后,又读取事件队列里的 DOM 操作,页面渲染完成。
由上述原因,导致了诡异的 “alert执行顺序问题”。 我们无法将页面渲染变成同步操作,那么只好把 alert() 变为异步代码,从而才能在页面渲染之后执行。
对于这个解决方向,我们有两种方法可以使用:
setTimeOut函数
setTimeout() 这个函数可以延迟执行某些代码。而对于延迟执行的代码,JavaScript 引擎总是把这些代码放到事件队列里去,再去检查是否已经到了执行时间,再适时执行。代码进入事件队列,就意味着代码变成和页面渲染事件一样异步了。由于事件队列是有序的,我们如果用 setTimeout 延时执行,就可以实现在页面渲染之后执行 alert 的功能了。
setTimeout 的函数原型为 setTimeout(code, msec),code 是要变为异步的代码或函数,msec 是要延时的时间,单位为毫秒。这里我们不需要它延时,只需要它变为异步就行了,所以可以将 msec 设置为 0;
同样,alert 之后的代码我们也需要处理,将它们跟 alert 一块放到 setTimeout 里异步执行。这样,代码就变为 setTimeout(“alert(‘msg’);doSomething();”, 0);,如果觉得代码不够美观或字符串不好处理的话,可以将后续代码封装成一个函数放到 doSomething() 里即可。
替换 Alert() 函数
首先我们考虑替换掉 alert 函数的功能。其实大多数情况下我们替换掉 alert 并不是它不符合我们期待的执行顺序,而是因为它实在是太丑了,而且也不支持各种美化,可以想像在一个某一特定主题的网站上忽然弹出来一个灰色单调的对话框是多么不和谐。
这个我们可以考虑 Bootstrap 的 modal 模块,Bootstrap 在绝大多数网站上都在应用,而多引入一个 modal 模块也不会有多大影响。我们使用 modal 构造一个弹出对话框的样子,使用 modal 的 modal(‘toggle’)/modal(‘show’)/modal(‘hide’) 方法可以很方便地控制 modal 的显隐。
替换掉对话框后,我们还需要解决后续代码执行的问题。使用 alert 函数时,我们点击确定后代码还会继续执行,而使用我们自定义的对话框可没有这种功能了,需要考虑把后续代码绑定在对话框的点击按钮上,这就需要使用 DOM 的 onclick 属性了,我们将后续函数内容抽出一个新的函数,在弹出对话框后将这个函数绑定在按钮的 onclick 事件上即可。
这里还需要注意,新函数内应该包括关闭 modal 对话框的内容。
当然,我们还可以再优化一下,抽象出来一个用来弹出对话框的函数替代 alert 函数,示例如下:
1 2 3 4 5 6 7 8 |
|
如此,我们在需要弹出框时调用新的 alert 函数,并传入 callbackFunc ,在里面做后续的事情就可以了。
小结
在上面的两个解决方案中,都利用了 JavaScript 的回调函数,前者将函数所为 alert 的参数并绑定到 DOM 的 onclick 事件,后者使用 setTimeout 将函数转为异步执行。