loadscript 是使用js去请求一个 script 到文档里,这样不会在首次加载时去下载和执行script,就为浏览器减少了首次渲染的工作量
打包后代码很可能是长这样,里面有好多 module,好多变量,好多 function,最后有一个 render 函数,调用 vue/react 的渲染函数去渲染 element
(function () {
/*
... const module= ...
... const data=...
... function action(){
...
}
一大串代码。有多长呢?
*/
function render() {
// render
ReactDOM.render(a, document.getElementById('root'))
}
render()
})()
浏览器对于html文档解析的大概顺序
看看知乎大佬回答
取几个关键点来看
浏览器是从上到下从左到右解析 html 文档的
script 中可能会有 dom 操作,浏览器在解析script标签后就阻塞 dom 树的构建
思考一下我把支持我渲染的代码放到 RenderScript 里,整个页面就是个 div 容器和 render script,这样就可以做到最快的渲染出页面。反过来说就是把代码中和渲染无关,甚至和首屏无关的代码,都可以抛弃,这样首屏渲染就不会被无关的js阻塞
使用js创建标签请求script
const script = document.createElement('script')
script.src = src
document.head.appendChild(script)
Promise
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
插入 script 的操作可以看作执行了一个动作(扔给浏览器一个 script 标签让他加载),但是并不是立马生效,浏览器要去下载,下载也可能因为网络失败,那他就是一个标准的未来才会结束的事件,并且有2个状态:成功:失败
状态理清了,在不同状态下执行不同的代码,这就是 promise 的好处。比如我请求 echarts 的 js,我请求成功后,我就可以调用echarts.init
开始画图了;如果失败了,我应该显示 error 画面,提醒用户,加载失败了,或者重试
来试试把 loadscript 封装成 promise
function LoadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
单例模式
相同的 script 不用重复请求,而是取上一次的结果就好了。理清了 promise ,那把这个请求 script 的 promise 操作按照src存起来就好了,我下一次按照 src 想调用loadScript
函数,我就可以拿到上次的请求结果啦
不过注意的是,上次 loadScript 如果失败了,那这个 promise 就永远是 rejected 状态了,要想重试的话,只能删除这个值,重新创建 script,所以我们要写一个delete
函数去清除存储的值
const LoadScript = (function () {
let instances = {}
return function (src) {
if (!instances[src]) {
instances[src] = new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
// 提供一个删除instace的接口
instances[src].deleteInstance = function () {
delete instances[src]
}
}
return instances[src]
}
})()
这样就改造好啦,不论多少次调用loadscript,实际上这个动作只执行1次,就实现多次loadscript
取上一次的结果了