14

问题

为了提高页面性能,我需要预加载需要在底部页面上运行的脚本。

我想控制脚本何时被解析、编译和执行。

我必须避免使用 script 标签,因为它会阻碍常见的渲染引擎(geeko 等)。

我无法使用defer属性加载它,因为我需要控制脚本何时执行。此外,异步属性是不可能的。

样本:

<html><head>
//preload scripts ie: a.js  without use the script
</head><body> ..... all my nice html here
//execute here a.js
</body></html>

这使我可以最大限度地提高页面的渲染性能,因为浏览器将开始下载脚本内容,并且将同时并行渲染页面。最后,我可以添加脚本标签,这样浏览器就会解析、编译和执行代码。

我能做到这一点的唯一方法是使用隐藏的图像标签。(这是Stoyan的简化版本)

IE

 <html><head>
 <img src="a.js" style=display:none;>
</head><body> ..... all my nice html here
 <script src="a.js">  
</body></html>

问题

使用这种技术我没有发现任何问题,但是有人知道更好的方法吗?是否有任何元预取?

附加信息

我正在使用requirejs,所以我尝试预加载模块代码,而不执行它,因为此代码取决于 DOM 元素。

4

6 回答 6

6

使用类似的技术,您可以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

但是,为什么您不想像其他答案中建议的那样只使用动态添加的脚本,这可能会导致更清洁的解决方案,并具有更多的控制权。

于 2012-06-19T14:25:26.873 回答
5

您可以使用prefetch链接标签的属性来预加载任何资源,包括 javascript。截至撰写本文时(2016 年 8 月 10 日),Safari 不支持它,但几乎在其他任何地方都支持它:

<link rel="prefetch" href="(url)">

有关支持的更多信息:http: //caniuse.com/#search=prefetch

请注意,IE 9,10 未在caniuse矩阵中列出,因为 Microsoft 已停止对它们的支持。

此处提供更多信息以及更多预加载选项,例如预渲染等

于 2016-08-10T13:25:40.300 回答
1

对于您想在不执行的情况下下载的每个脚本,创建一个包含名称和 url 的对象,并将这些对象放入一个数组中。

遍历数组,使用jQuery.ajaxwithdataType: "text"将脚本下载为文本。在doneajax 调用的处理程序中,将文件的文本内容(在第一个参数中传递)存储在适当的对象中,增加一个计数器,并在该计数器等于您的文件数时调用“alldone”函数正在以这种方式下载。

在“alldone”函数(或更高版本)中执行以下操作:再次循环遍历您的数组,并为每个条目使用document.createElement("script"),document.createTextNode(...)(...scriptNode...).appendChild(...)动态生成具有预期源代码的脚本,而不是通过“src”属性。最后, do 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>

与上述方法密切相关的 其他问题其他相关问题

于 2017-02-27T13:32:36.220 回答
0

您应该查看以下链接:

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 模块的过程。

于 2012-06-19T20:30:28.690 回答
0

为什么不试试这个?

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 添加脚本是非阻塞的,我相信您可以使用这种方法完美地实现您的目标。

于 2012-06-19T14:08:27.867 回答
0

我在那里回答了同样的问题:

https://stackoverflow.com/a/46121439/1951947

只需使用<link>标签预加载您的脚本,然后您可以将其与<script>标签一起使用

例如:<link href="/js/script-to-preload.js" rel="preload" as="script">

于 2017-09-08T17:09:23.820 回答