预加载脚本而不执行
Posted
技术标签:
【中文标题】预加载脚本而不执行【英文标题】:Preload script without execute 【发布时间】:2012-06-21 14:09:55 【问题描述】:问题
为了提高页面性能我需要预加载脚本,我需要在底部页面上运行。
我想控制脚本的解析、编译和执行时间。
我必须避免使用脚本标签,因为它是blocker for common render engines(geeko 等)。
我无法使用 defer 属性加载它,因为我需要控制脚本何时执行。 此外,async 属性是不可能的。
样本:
<html><head>
//preload scripts ie: a.js without use the script
</head><body> ..... all my nice html here
//execute here a.js
</body></html>
这让我可以最大限度地提高页面的渲染性能,因为浏览器将开始下载脚本内容,并同时并行渲染页面。最后,我可以添加脚本标签,这样浏览器就会解析、编译和执行代码。
我能做到这一点的唯一方法是使用隐藏的图像标签。 (这是一个简化的version of Stoyan)
即
<html><head>
<img src="a.js" style=display:none;>
</head><body> ..... all my nice html here
<script src="a.js">
</body></html>
问题
我没有发现使用这种技术有任何问题,但是有人知道更好的方法吗? 是否有任何元预取?
其他信息
我正在使用 requirejs,所以我尝试预加载模块代码,而不执行它,因为此代码依赖于 DOM 元素。
【问题讨论】:
什么是“公共渲染引擎”,为什么必须避免使用脚本标签?为什么不把你要运行的脚本放到一个函数中,然后在 DOM 使用 jQuery 加载完毕后执行该函数? 动态附加的 SCRIPT 元素是非阻塞的... @SaniHuttunen:我已经添加了信息,脚本是 requirejs 模块,我正在底部加载 requirejs lib。 @ŠimeVidas:确实如此,但您无法控制浏览器何时解析、编译和执行它们。 【参考方案1】:使用类似的技术,您可以使用 img
为 Internet Explorer 预加载脚本和样式表,并为所有其他浏览器使用 object
标记。
var isMSIE = /*@cc_on!@*/false;
var resources = ['a.js', 'b.js', 'c.css'];
for (var i=0; i<resources.length; i++)
if (isMSIE)
new Image().src = resources[i];
else
var o = document.createElement('object');
o.data = resources[i];
document.body.appendChild(o);
有一篇博客文章描述了这种技术并概述了注意事项:Preload CSS/javascript without execution。
但是您为什么不想像其他答案中建议的那样只使用动态添加的脚本,这可能会导致更清洁的解决方案,具有更多的控制权。
【讨论】:
是的@Juicy,这似乎是解决问题的最佳方法。我的版本是该脚本的简化副本。为什么不使用动态添加的脚本?因为,我想控制脚本何时被解析、编译和执行。你知道为什么在非 IE 浏览器上使用对象而不是图像更好吗? 请注意,此建议已过时且不起作用。当 url 具有相同来源的 xframe 选项并且它中断时,尝试使用对象加载脚本标记。将其移动到图像并且它可以工作。该帖子中列出的问题(它不会在 FF 中缓存,似乎不再适用0 我尝试了这种方法,但我遇到了问题。我有一个 Ember 应用程序,它包含大约 150 个单独的文件,页面上有这么多对象,浏览器变得非常慢。不幸的是,对我来说不行。 也许这有额外的帮助? phpied.com/preload-cssjavascript-without-execution【参考方案2】:您可以使用链接标签的prefetch
属性来预加载任何资源,包括javascript。在撰写本文时(2016 年 8 月 10 日),Safari 不支持它,但几乎在其他任何地方都支持:
<link rel="prefetch" href="(url)">
更多关于支持的信息: http://caniuse.com/#search=prefetch
请注意,IE 9,10 未列在 caniuse
矩阵中,因为 Microsoft 已停止对它们的支持。
More info here 和 more options for preloading, like prerender and more
【讨论】:
预取只对下一页有用,对当前页面无效。因此,如果你的单页应用突然需要一个额外的 JS 模块,它将无法使用,无论如何都需要下载。【参考方案3】:对于您想下载而不执行的每个脚本,创建一个包含名称和 url 的对象,并将这些对象放入一个数组中。
遍历数组,使用jQuery.ajax
和dataType: "text"
将脚本下载为文本。在 ajax 调用的done
处理程序中,将文件的文本内容(在第一个参数中传递)存储在适当的对象中,增加一个计数器,并在该计数器等于数字时调用“alldone”函数以这种方式下载的文件数量。
在“alldone”函数(或更高版本)中执行以下操作:再次循环遍历您的数组,对于每个条目,使用 document.createElement("script")
、document.createTextNode(...)
和 (...scriptNode...).appendChild(...)
动态生成具有预期源代码的脚本,而不是通过“src”属性。最后,执行document.head.appendChild(...scriptNode...)
,即执行该脚本的时间点。
我在需要使用框架的项目中使用了这种技术,其中几个框架和/或框架集需要相同的 JavaScript 文件,以确保每个文件只从服务器请求一次。
代码(测试和工作)如下
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<html>
<head>
<script id="scriptData">
var scriptData = [
name: "foo" , url: "path/to/foo" ,
name: "bar" , url: "path/to/bar"
];
</script>
<script id="scriptLoader">
var LOADER =
loadedCount: 0,
toBeLoadedCount: 0,
load_jQuery: function ()
var jqNode = document.createElement("script");
jqNode.setAttribute("src", "/path/to/jquery");
jqNode.setAttribute("onload", "LOADER.loadScripts();");
jqNode.setAttribute("id", "jquery");
document.head.appendChild(jqNode);
,
loadScripts: function ()
var scriptDataLookup = this.scriptDataLookup = ;
var scriptNodes = this.scriptNodes = ;
var scriptNodesArr = this.scriptNodesArr = [];
for (var j=0; j<scriptData.length; j++)
var theEntry = scriptData[j];
scriptDataLookup[theEntry.name] = theEntry;
//console.log(JSON.stringify(scriptDataLookup, null, 4));
for (var i=0; i<scriptData.length; i++)
var entry = scriptData[i];
var name = entry.name;
var theURL = entry.url;
this.toBeLoadedCount++;
var node = document.createElement("script");
node.setAttribute("id", name);
scriptNodes[name] = node;
scriptNodesArr.push(node);
jQuery.ajax(
method : "GET",
url : theURL,
dataType : "text"
).done(this.makeHandler(name, node)).fail(this.makeFailHandler(name, node));
,
makeFailHandler: function(name, node)
var THIS = this;
return function(xhr, errorName, errorMessage)
console.log(name, "FAIL");
console.log(xhr);
console.log(errorName);
console.log(errorMessage);
debugger;
,
makeHandler: function(name, node)
var THIS = this;
return function (fileContents, status, xhr)
THIS.loadedCount++;
//console.log("loaded", name, "content length", fileContents.length, "status", status);
//console.log("loaded:", THIS.loadedCount, "/", THIS.toBeLoadedCount);
THIS.scriptDataLookup[name].fileContents = fileContents;
if (THIS.loadedCount >= THIS.toBeLoadedCount)
THIS.allScriptsLoaded();
,
allScriptsLoaded: function()
for (var i=0; i<this.scriptNodesArr.length; i++)
var scriptNode = this.scriptNodesArr[i];
var name = scriptNode.id;
var data = this.scriptDataLookup[name];
var fileContents = data.fileContents;
var textNode = document.createTextNode(fileContents);
scriptNode.appendChild(textNode);
document.head.appendChild(scriptNode); // execution is here
//console.log(scriptNode);
// call code to make the frames here
;
</script>
</head>
<frameset rows="200pixels,*" onload="LOADER.load_jQuery();">
<frame src="about:blank"></frame>
<frame src="about:blank"></frame>
</frameset>
</html>
other question与上述方法密切相关 other related question
【讨论】:
【参考方案4】:你应该看看以下链接:
http://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/
http://tomdale.net/2012/01/amd-is-not-the-answer/
以及 ember.js 如何使用名为 minispade 的工具和使用 ruby 进行预处理来加快加载、解析和运行 javascript 模块的过程。
【讨论】:
不错的文章,我正在做一些类似于惰性评估的事情,但是为了减少 http 请求的数量,我将所有模块都放在一个优化的文件中。它类似于此示例: Files = ;文件['main.js'] = "require(\"controllers/app_controller.js\");"; Files['controllers/app_controller.js'] = "alert(\"Hello world!\");"; 那么也许你应该考虑 rake 管道。有一个项目框架,他们在这里使用 minispade 和 rake papeline 生成一个优化文件:github.com/interline/ember-skeleton【参考方案5】:为什么不试试这个?
var script = document.createElement('script');
script.src = 'http://path/to/your/script.js';
script.onload = function()
// do something here
document.head.appendChild(script);
您可以使用 .onload 事件来控制何时加载。一个警告是 .onload() 在 IE 中不起作用,您可以使用它:
script.onreadystatechange = function()
if (/^loaded|complete$/i.test(this.readyState))
// loaded
;
另外通过 DOM 添加脚本是非阻塞的,我相信您可以通过这种方法完美地实现您的目标。
【讨论】:
谢谢,我知道这种技术,但我需要控制脚本何时解析和执行。【参考方案6】:我在那里回答了同样的问题:
https://***.com/a/46121439/1951947
只需使用<link>
标签来预加载您的脚本,然后您就可以将它与<script>
标签一起使用
例如:<link href="/js/script-to-preload.js" rel="preload" as="script">
【讨论】:
以上是关于预加载脚本而不执行的主要内容,如果未能解决你的问题,请参考以下文章