Skip to content

Commit 6476bf6

Browse files
committed
加载策略 之 动态<script>以实现非阻塞下载 翻译完毕
1 parent 2ccb06c commit 6476bf6

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

chapter8.markdown

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,103 @@ HTTP协议支持“分块编码”。它允许将页面分成一块一块发送
715715
</html>
716716
<!-- end of chunk #2 -->
717717

718+
一个更好的办法是使用第三块内容,让它在页面尾部,只包含脚本。如果有一些每个页面都用到的静态的头部,也可以将这部分随和一块一起发送:
719+
720+
<!doctype html> <html>
721+
<head>
722+
<title>My App</title> </head>
723+
<body>
724+
<div id="header">
725+
<img src="logo.png" />
726+
...
727+
</div>
728+
<!-- end of chunk #1 -->
729+
730+
... The full body of the page ...
731+
732+
<!-- end of chunk #2 -->
733+
<script src="all_20100426.js"></script>
734+
</body>
735+
</html>
736+
<!-- end of chunk #3 -->
737+
738+
这种方法很适合使用渐进增强思想的网站(关键业务不依赖JavaScript)。当HTML的第二块发送完毕的时候,浏览器已经有了一个加载、显示完毕并且可用的页面,就像禁用JavaScript时的情况。当JavaScript随着第三块到达时,它会进一步增强页面,为页面锦上添花。
739+
740+
### 动态\<script\>元素实现非阻塞下载
741+
742+
前面已经说到过,JavaScript会阻塞后面文件的下载,但有一些模式可以防止阻塞:
743+
744+
- 使用XHR加载脚本,然后作为一个字符串使用eval()来执行。这种方法受同源策略的限制,而且引入了eval()这种“反模式”。
745+
- 使用defer和async属性,但有浏览器兼容性问题
746+
- 使用动态<script>元素
747+
748+
最后一种是一个很好并且实际可行的模式。和介绍JSONP时所做的一样,创建一个新的script元素,设置它的src属性,然后将它放到页面上。
749+
750+
这是一个异步加载JavaScript,不阻塞其它文件下载的示例:
751+
752+
var script = document.createElement("script");
753+
script.src = "all_20100426.js";
754+
document.documentElement.firstChild.appendChild(script);
755+
756+
这种模式的缺点是,在这之后加载的脚本不能依赖这个脚本。因为这个脚本是异步加载的,所以无法保证它什么时候会被加载进来,如果要依赖的话,很可能会访问到(因还未加载完毕导致的)未定义的对象。
757+
758+
如果要解决这个问题,可以让内联的脚本不立即执行,而是作为一个函数放到一个数组中。当依赖的脚本加载完毕后,再执行数组中的所有函数。所以一共有三个步骤。
759+
760+
首先,创建一个数组用来存储所有的内联代码,定义的位置尽量靠前:
761+
762+
var mynamespace = {
763+
inline_scripts: []
764+
};
765+
766+
然后你需要将这些单独的内联脚本包裹进一个函数中,然后将每个函数放到inline_scripts数组中,也就是这样:
767+
768+
// was:
769+
// <script>console.log("I am inline");</script>
770+
771+
// becomes:
772+
<script>
773+
mynamespace.inline_scripts.push(function () {
774+
console.log("I am inline");
775+
});
776+
</script>
777+
778+
最后一步是使用异步加载的脚本遍历这个数组,然后执行函数:
779+
780+
var i, scripts = mynamespace.inline_scripts, max = scripts.length;
781+
for (i = 0; i < max; max += 1) {
782+
scripts[i]();
783+
}
784+
785+
#### 插入\<script\>元素
786+
787+
通常脚本是插入到文档的<head>中的,但其实你可以插入任何元素中,包括body(像JSONP示例中那样)。
788+
789+
在前面的例子中,我们使用documentElement来插到\<head\>中,因为documentElement就是\<html\>,它的第一个子元素是\<head\>
790+
791+
document.documentElement.firstChild.appendChild(script);
792+
793+
通常也会这样写:
794+
795+
document.getElementsByTagName("head")[0].appendChild(script);
796+
797+
当你能控制结构的时候,这样做没有问题,但是如果你在写挂件(widget)或者是广告时,你并不知道托管它的是一个什么样的页面。甚至可能页面上连\<head\>\<body\>都没有,尽管document.body在绝大多数没有\<body\>标签的时候也可以工作:
798+
799+
document.body.appendChild(script);
800+
801+
可以肯定页面上一定存在的一个标签是你正在运行的脚本所处的位置——script标签。(对内联或者外部文件来说)如果没有script标签,那么代码就不会运行。可以利用这一事实,在页面的第一个script标签上使用insertBefore():
802+
803+
var first_script = document.getElementsByTagName('script')[0];
804+
first_script.parentNode.insertBefore(script, first_script);
805+
806+
frist_script是页面中一定存在的一个script标签,script是你创建的新的script元素。
807+
808+
809+
810+
811+
812+
813+
814+
718815

719816

720817

0 commit comments

Comments
 (0)