异步加载脚本

我使用了几个 JQuery 的插件、自定义小部件和其他一些库。因此,我有几个。JS 和。CSS 文件。我需要为我的网站创建一个加载程序,因为它需要一些时间来加载。如果我能在导入所有以下内容之前显示加载程序就好了:

<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="js/myFunctions.js"></script>
<link type="text/css" href="css/main.css" rel="stylesheet" />
...
....
etc

我找到了几个教程,它们使我能够异步导入 JavaScript 库。例如,我可以这样做:

  (function () {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'js/jquery-ui-1.8.16.custom.min.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();

由于某种原因,当我做同样的事情,我的所有文件的页面不工作。我已经尝试了这么长时间,试图找到问题在哪里,但我就是找不到它。首先,我认为这可能是因为某些 javascript 函数依赖于其他函数。但是我使用超时功能按照正确的顺序加载它们当一个完成后,我继续下一个,页面仍然表现得很奇怪。例如,我不能点击链接等。.不过动画还能用。.

不管怎样

这是我一直在想的... 我相信浏览器有一个缓存,这就是为什么它需要很长的时间来加载页面的第一次和下一次它是快速的。所以我想做的是用一个异步加载所有这些文件的页面替换 index.html 页面。当 ajax 加载完所有这些文件后,重定向到我计划使用的页面。在使用该页面时,加载不会花费太长时间,因为文件应该已经包含在浏览器的缓存中。在我的索引页面(页面。JS 和。Css 文件被异步加载)我不关心得到错误。我将只是显示一个加载程序和重定向页面时,完成..。

这是一个好的替代方法吗? 或者我应该继续尝试实现异步方法?


剪辑

我加载所有异步程序的方式是这样的:

importScripts();


function importScripts()
{
//import: jquery-ui-1.8.16.custom.min.js
getContent("js/jquery-1.6.2.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
//s.async = true;
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext1,1);
});




//import: jquery-ui-1.8.16.custom.min.js
function insertNext1()
{
getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext2,1);
});
}


//import: jquery-ui-1.8.16.custom.css
function insertNext2()
{


getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext3,1);
});
}


//import: main.css
function insertNext3()
{


getContent("css/main.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext4,1);
});
}


//import: jquery.imgpreload.min.js
function insertNext4()
{
getContent("js/farinspace/jquery.imgpreload.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext5,1);
});
}




//import: marquee.js
function insertNext5()
{
getContent("js/marquee.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext6,1);
});
}




//import: marquee.css
function insertNext6()
{


getContent("css/marquee.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext,1);
});
}






function insertNext()
{
setTimeout(pageReadyMan,10);
}
}




// get the content of url and pass that content to specified function
function getContent( url, callBackFunction )
{
// attempt to create the XMLHttpRequest and make the request
try
{
var asyncRequest; // variable to hold XMLHttpRequest object
asyncRequest = new XMLHttpRequest(); // create request object


// register event handler
asyncRequest.onreadystatechange = function(){
stateChange(asyncRequest, callBackFunction);
}
asyncRequest.open( 'GET', url, true ); // prepare the request
asyncRequest.send( null ); // send the request
} // end try
catch ( exception )
{
alert( 'Request failed.' );
} // end catch
} // end function getContent


// call function whith content when ready
function stateChange(asyncRequest, callBackFunction)
{
if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
{
callBackFunction(asyncRequest.responseText);
} // end if
} // end function stateChange

奇怪的是所有的样式加上所有的 javascript 函数。页面因为某种原因被冻结了。

259760 次浏览

我建议先缩小文件,看看是否能给你足够大的速度提升。如果您的主机速度较慢,可以尝试将该静态内容放在 CDN 上。

您可能会对这篇 wiki 文章感兴趣: http://ajaxpatterns.org/On-Demand_Javascript

它解释了如何以及何时使用这种技术。

好的,x.parentNode返回 HEAD 元素,所以在 HEAD 标记之前插入脚本。也许这就是问题所在。

试试 x.parentNode.appendChild()

脚本加载速度如此之慢的一个原因是,如果在加载页面时运行所有脚本,比如:

callMyFunctions();

而不是:

$(window).load(function() {
callMyFunctions();
});

这第二段脚本要等到浏览器完全加载了所有 Javascript 代码之后才能开始执行任何脚本,这样用户就会觉得页面加载得更快了。

如果你想通过减少加载时间来增强用户体验,我不会选择“加载屏幕”选项。在我看来,这比只是让页面加载更慢要烦人得多。

异步加载的几个解决方案:

//this function will work cross-browser for loading scripts asynchronously
function loadScript(src, callback)
{
var s,
r,
t;
r = false;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = src;
s.onload = s.onreadystatechange = function() {
//console.log( this.readyState ); //uncomment this line to see which ready states are called.
if ( !r && (!this.readyState || this.readyState == 'complete') )
{
r = true;
callback();
}
};
t = document.getElementsByTagName('script')[0];
t.parentNode.insertBefore(s, t);
}

如果您已经在页面上使用了 jQuery,只需使用:

$.getScript(url, successCallback) *

此外,您的脚本可能在文档完成加载之前被加载/执行,这意味着您需要等待 document.ready,然后才能将事件绑定到元素。

如果没有看到代码,就不可能明确地告诉您的问题是什么。

最简单的解决方案是将所有脚本保持在页面底部的内联状态,这样它们在执行时就不会阻止 HTML 内容的加载。它还避免了必须异步加载每个所需脚本的问题。

如果您有一个特别奇特的交互,并不总是需要某种更大的脚本,那么在需要之前避免加载这个特定的脚本(延迟加载)可能是有用的。

* 装载了 $.getScript的脚本可能不会被缓存


对于那些能够使用 Promise对象等现代功能的人来说,loadScript功能变得明显更加简单:

function loadScript(src) {
return new Promise(function (resolve, reject) {
var s;
s = document.createElement('script');
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}

请注意,此版本不再接受 callback参数,因为返回的承诺将处理回调。以前的 loadScript(src, callback)现在是 loadScript(src).then(callback)

这有额外的好处,能够检测和处理故障,例如,一个可以调用..。

loadScript(cdnSource)
.catch(loadScript.bind(null, localSource))
.then(successCallback, failureCallback);

它可以优雅地处理 CDN 中断。

我建议你看看 现代化。它是一个小型的轻量级库,您可以异步加载您的 javascript 的特性,允许您检查文件是否已经加载,并在您指定的其他脚本中执行脚本。

下面是加载 jquery 的一个例子:

Modernizr.load([
{
load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js',
complete: function () {
if ( !window.jQuery ) {
Modernizr.load('js/libs/jquery-1.6.1.min.js');
}
}
},
{
// This will wait for the fallback to load and
// execute if it needs to.
load: 'needs-jQuery.js'
}
]);

当所有加载完成的脚本都被重定向到 index2.html,而 index2.html 使用相同的库时,我异步加载了这些脚本(html 5具有这个特性)。因为一旦页面重定向到 index2.html,浏览器就有一个缓存,所以 index2.html 在不到一秒钟的时间内就加载完毕,因为它已经具备了加载页面所需的所有东西。在 index.html 页面中,我还加载了计划使用的图像,以便浏览器将这些图像放在缓存中。所以我的 index.html 看起来像:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Project Management</title>


<!-- the purpose of this page is to load all the scripts on the browsers cache so that pages can load fast from now on -->


<script type="text/javascript">


function stylesheet(url) {
var s = document.createElement('link');
s.type = 'text/css';
s.async = true;
s.src = url;
var x = document.getElementsByTagName('head')[0];
x.appendChild(s);
}


function script(url) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = url;
var x = document.getElementsByTagName('head')[0];
x.appendChild(s);
}


//load scritps to the catche of browser
(function () {
stylesheet('css/custom-theme/jquery-ui-1.8.16.custom.css');
stylesheet('css/main.css');
stylesheet('css/marquee.css');
stylesheet('css/mainTable.css');


script('js/jquery-ui-1.8.16.custom.min.js');
script('js/jquery-1.6.2.min.js');
script('js/myFunctions.js');
script('js/farinspace/jquery.imgpreload.min.js');
script('js/marquee.js');
})();


</script>


<script type="text/javascript">
// once the page is loaded go to index2.html
window.onload = function () {
document.location = "index2.html";
}
</script>


</head>
<body>


<div id="cover" style="position:fixed; left:0px; top:0px; width:100%; height:100%; background-color:Black; z-index:100;">Loading</div>


<img src="images/home/background.png" />
<img src="images/home/3.png"/>
<img src="images/home/6.jpg"/>
<img src="images/home/4.png"/>
<img src="images/home/5.png"/>
<img src="images/home/8.jpg"/>
<img src="images/home/9.jpg"/>
<img src="images/logo.png"/>
<img src="images/logo.png"/>
<img src="images/theme/contentBorder.png"/>


</body>
</html>

另一个好处是,我可以在页面中放置一个加载程序,当页面加载完成时,加载程序将消失,并且在几毫秒内新页面将运行。

一些注释:

  • 对于 HTML5 doctype,s.async = true不是很正确,正确的是 s.async = 'async'(实际上使用的是 true 是正确的,感谢 在下面的 评论中指出了这一点)
  • 使用超时来控制顺序不是很好,也不安全,而且您还使加载时间大得多,等于所有超时的总和!

由于最近出现了异步加载文件的原因,但为了保证顺序,我建议使用一种更加功能驱动的方式(删除 console.log以便在生产中使用:) :

(function() {
var prot = ("https:"===document.location.protocol?"https://":"http://");


var scripts = [
"path/to/first.js",
"path/to/second.js",
"path/to/third.js"
];


function completed() { console.log('completed'); }  // FIXME: remove logs


function checkStateAndCall(path, callback) {
var _success = false;
return function() {
if (!_success && (!this.readyState || (this.readyState == 'complete'))) {
_success = true;
console.log(path, 'is ready'); // FIXME: remove logs
callback();
}
};
}


function asyncLoadScripts(files) {
function loadNext() { // chain element
if (!files.length) completed();
var path = files.shift();
var scriptElm = document.createElement('script');
scriptElm.type = 'text/javascript';
scriptElm.async = true;
scriptElm.src = prot+path;
scriptElm.onload = scriptElm.onreadystatechange = \
checkStateAndCall(path, loadNext); // load next file in chain when
// this one will be ready
var headElm = document.head || document.getElementsByTagName('head')[0];
headElm.appendChild(scriptElm);
}
loadNext(); // start a chain
}


asyncLoadScripts(scripts);
})();

我会检查是否存在回调并允许传递参数来完成 zzzzBov 的回答:

    function loadScript(src, callback, args) {
var s, r, t;
r = false;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = src;
if (typeof(callback) === 'function') {
s.onload = s.onreadystatechange = function() {
if (!r && (!this.readyState || this.readyState === 'complete')) {
r = true;
callback.apply(args);
}
};
};
t = document.getElementsByTagName('script')[0];
t.parent.insertBefore(s, t);
}

HTML5的新“异步”属性应该可以解决这个问题。如果你关心 IE,大多数浏览器也支持“延迟”。

异步-HTML

<script async src="siteScript.js" onload="myInit()"></script>

Defer-HTML

<script defer src="siteScript.js" onload="myInit()"></script>

在分析新的广告单元代码时,我注意到了这个属性,一个搜索引导我到这里: http://davidwalsh.name/html5-async

来自谷歌的例子

<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js?onload=onLoadCallback';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>

看看这个 https://github.com/stephen-lazarionok/async-resource-loader。它有一个示例演示如何一次性加载 JS、 CSS 和多个文件。

您可以使用 实验室RequreJS

LABJSRequireJS这样的脚本加载器将提高代码的速度和质量。

这里是我的自定义解决方案,以消除呈现阻塞 JavaScript:

// put all your JS files here, in correct order
const libs = {
"jquery": "https://code.jquery.com/jquery-2.1.4.min.js",
"bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js",
"angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js",
"ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js"
}
const loadedLibs = {}
let counter = 0


const loadAsync = function(lib) {
var http = new XMLHttpRequest()
http.open("GET", libs[lib], true)
http.onload = () => {
loadedLibs[lib] = http.responseText
if (++counter == Object.keys(libs).length) startScripts()
}
http.send()
}


const startScripts = function() {
for (var lib in libs) eval(loadedLibs[lib])
console.log("allLoaded")
}


for (var lib in libs) loadAsync(lib)

简而言之,它异步加载所有脚本,然后执行它们。

Github repo : < a href = “ https://Github.com/mudroljub/js-sync-loader”rel = “ nofollow norefrer”> https://Github.com/mudroljub/js-async-loader

多亏了 HTML5,您现在可以通过在标记中添加“ sync”来声明希望异步加载的脚本:

<script async>...</script>

注意: 异步属性仅用于外部脚本(并且应该只在 src 属性存在的情况下使用)。

注意: 有几种方法可以执行外部脚本:

  • 如果存在异步: 脚本将与页面的其余部分异步执行(脚本将在页面继续解析时执行)
  • 如果异步不存在并且已经存在,则在页完成解析时执行脚本
  • 如果不存在异步或延迟: 在浏览器继续解析页面之前,将立即获取并执行脚本

看这个: http://www.w3schools.com/tags/att_script_async.asp

你有没有考虑过使用抓取注射?我使用了一个名为 提取-注射的开源库来处理这样的情况。下面是您的加载程序使用 lib 时的样子:

fetcInject([
'js/jquery-1.6.2.min.js',
'js/marquee.js',
'css/marquee.css',
'css/custom-theme/jquery-ui-1.8.16.custom.css',
'css/main.css'
]).then(() => {
'js/jquery-ui-1.8.16.custom.min.js',
'js/farinspace/jquery.imgpreload.min.js'
})

为了获得向后兼容性,可以利用特征提取和退回到 XHR 注入或脚本 DOM 元素,或者简单地使用 document.write将标签内嵌到页面中。

这里有一个非常好的当代异步脚本加载解决方案,尽管它只用 异步错误处理 js 脚本。

有一个伟大的文章写在 Www.html5rocks.com-深入到脚本加载的浑水中

在考虑了许多可能的解决方案之后,作者得出结论,将 js 脚本添加到 body 元素的末尾是避免使用 js 脚本阻塞页面呈现的最佳可能方法。

与此同时,作者为那些渴望异步加载和执行脚本的人们添加了另一个不错的替代解决方案。

考虑到你有四个脚本命名为 script1.js, script2.js, script3.js, script4.js,然后你可以做它与 应用异步 = 错误:

[
'script1.js',
'script2.js',
'script3.js',
'script4.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});

现在,说明书上说: 一起下载,一下载就按顺序执行。

Firefox < 3.6,Opera 说: 我不知道这个“异步”是什么,但是碰巧我按照添加的顺序执行通过 JS 添加的脚本。

Safari 5.0说: 我理解“异步”,但不理解用 JS 将其设置为“ false”。你的剧本一落地,我就会执行无论什么顺序。

IE < 10说: 没有关于“异步”的想法,但是有一个使用“ onreadystatechange”的解决方案。

其他的都说: “我是你的朋友,我们要照章办事。”。

现在,使用 IE < 10解决方案的完整代码是:

var scripts = [
'script1.js',
'script2.js',
'script3.js',
'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];


// Watch scripts load in IE
function stateChange() {
// Execute as many scripts in order as we can
var pendingScript;
while (pendingScripts[0] && ( pendingScripts[0].readyState == 'loaded' || pendingScripts[0].readyState == 'complete' ) ) {
pendingScript = pendingScripts.shift();
// avoid future loading events from this script (eg, if src changes)
pendingScript.onreadystatechange = null;
// can't just appendChild, old IE bug if element isn't closed
firstScript.parentNode.insertBefore(pendingScript, firstScript);
}
}


// loop through our script urls
while (src = scripts.shift()) {
if ('async' in firstScript) { // modern browsers
script = document.createElement('script');
script.async = false;
script.src = src;
document.head.appendChild(script);
}
else if (firstScript.readyState) { // IE<10
// create a script and add it to our todo pile
script = document.createElement('script');
pendingScripts.push(script);
// listen for state changes
script.onreadystatechange = stateChange;
// must set src AFTER adding onreadystatechange listener
// else we’ll miss the loaded event for cached scripts
script.src = src;
}
else { // fall back to defer
document.write('<script src="' + src + '" defer></'+'script>');
}
}

我写了一个小帖子来帮助解决这个问题,你可以在这里阅读更多的 https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/,但是我已经在下面附上了助手类。它将自动等待脚本加载并返回指定的窗口属性。

export default class ScriptLoader {
constructor (options) {
const { src, global, protocol = document.location.protocol } = options
this.src = src
this.global = global
this.protocol = protocol
this.isLoaded = false
}


loadScript () {
return new Promise((resolve, reject) => {
// Create script element and set attributes
const script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.src = `${this.protocol}//${this.src}`


// Append the script to the DOM
const el = document.getElementsByTagName('script')[0]
el.parentNode.insertBefore(script, el)


// Resolve the promise once the script is loaded
script.addEventListener('load', () => {
this.isLoaded = true
resolve(script)
})


// Catch any errors while loading the script
script.addEventListener('error', () => {
reject(new Error(`${this.src} failed to load.`))
})
})
}


load () {
return new Promise(async (resolve, reject) => {
if (!this.isLoaded) {
try {
await this.loadScript()
resolve(window[this.global])
} catch (e) {
reject(e)
}
} else {
resolve(window[this.global])
}
})
}
}

用法如下:

const loader = new Loader({
src: 'cdn.segment.com/analytics.js',
global: 'Segment',
})


// scriptToLoad will now be a reference to `window.Segment`
const scriptToLoad = await loader.load()

这里有一个 ES6函数,如果有人想在 React 中使用它,例如

import {uniqueId} from 'lodash' // optional
/**
* @param {String} file The path of the file you want to load.
* @param {Function} callback (optional) The function to call when the script loads.
* @param {String} id (optional) The unique id of the file you want to load.
*/
export const loadAsyncScript = (file, callback, id) => {
const d = document
if (!id) { id = uniqueId('async_script') } // optional
if (!d.getElementById(id)) {
const tag = 'script'
let newScript = d.createElement(tag)
let firstScript = d.getElementsByTagName(tag)[0]
newScript.id = id
newScript.async = true
newScript.src = file
if (callback) {
// IE support
newScript.onreadystatechange = () => {
if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') {
newScript.onreadystatechange = null
callback(file)
}
}
// Other (non-IE) browsers support
newScript.onload = () => {
callback(file)
}
}
firstScript.parentNode.insertBefore(newScript, firstScript)
} else {
console.error(`The script with id ${id} is already loaded`)
}
}

对于 HTML5,您可以使用“预取”

<link rel="prefetch" href="/style.css" as="style" />

看看 js 的“预加载”。

<link rel="preload" href="used-later.js" as="script">

const dynamicScriptLoading = async (src) =>
{
const response = await fetch(src);
const dataResponse = await response.text();
eval.apply(null, [dataResponse]);
}

一个简明的答案,在代码中的解释

function loadScript(src) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
//
// script.async = false; // uncomment this line and scripts will be executed in the document order, like 'defer' option
//
// script.defer = true; // uncomment this line when scripts need whole DOM and/or relative order execution is important
//
// the script starts loading as it's append to the document and dynamic script behave as “async” by default
// other scripts don’t wait for 'async' scripts, and 'async' scripts don’t wait for them
// scripts that loads first – runs first (“load-first” order)
document.body.append(script);
}


loadScript('js/example01.js');
loadScript('js/example02.js');


/*
the 'defer' attribute tells the browser not to wait for the script
the 'async' attribute make script to load in the background and run when ready
the 'async' and 'defer' attribute are only for external scripts
'defer' is used for scripts that need the whole DOM and/or their relative execution order is important
'async' is used for independent scripts, like counters or ads, when their relative execution order does not matter
More: https://javascript.info/script-async-defer
*/