如何延迟内联Javascript?
How to defer inline Javascript?
我有以下 html 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
<!-- 26 dec flexslider js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
<script defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
</head>
<body>
</body>
</html>
我得到一个错误,说 jQuery 没有定义。现在,即使我从我的内联 JS 代码中删除 defer,它也会说 jQuery 未定义。出于某种原因,我必须将 jQuery 插件保留在头部并保持我的 JS 代码内联。我的问题是:
当 defer
属性存在时,为什么内联 Javascript 代码不会被延迟?
有没有办法模仿我的内联 Javascript 代码的延迟行为?如果需要,我可以把它放在 body 标签的末尾。
来自 MDN 文档:
defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded
. The defer attribute should only be used on external scripts.
这称为 IIFE (立即调用的函数表达式),它在 DOM 可用之前执行。所以,在那种情况下 jQuery
是未定义的,因为它不在 DOM 中。
具有 defer
属性的脚本按指定顺序加载,但不在 文档本身加载之前。由于 defer
对 script
标签没有影响,除非它们也具有 src
属性,因此第一个执行的脚本是您的内联脚本。所以那个时候jQuery还没有加载
您至少可以通过两种方式解决此问题:
将您的内联脚本放入 .js
文件并使用 src
属性引用它(除了您已经拥有的 defer
属性) , 或
让您的内联脚本等待文档和延迟脚本的加载。 DOMContentLoaded
事件将在发生时触发:
<script>
window.addEventListener('DOMContentLoaded', function() {
(function($) {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
})(jQuery);
});
</script>
注意:请注意,在后一种情况下,不再包含 $(document).ready(function()
,因为它会等待相同的事件 (DOMContentLoaded
)。您 可以 仍然像在原始代码中那样包含它,但是 jQuery 只会立即 执行回调,这不会实际差异。
您可以从脚本中创建一个 Base64 URL 并将其放入 src!
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer>
</script>
我构建了一个快速测试以查看它的运行情况。
如果 defer
正在工作,您应该最后看到带有 Hello world!
的警报:
<script defer>
alert('Why no defer?!?');
</script>
<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer></script>
<script>
alert('Buh-bye world!');
</script>
手动操作有点费力,所以如果您有幸以某种方式(Handlebars、Angular 等)编译您的 HTML,那将大有帮助。
我目前正在使用:
<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
defer>
</script>
Defer/async 脚本标签不够好
有一个常识,你应该使用 <script src=".." async defer>
(或者在分配 src
之前设置 script.async = true
,当你从 JS 执行它时) and/or 把你的脚本在页面的最底部,以便尽可能快地加载页面并呈现给用户。
defer.js(注意:我是这个脚本的作者)是用普通的JavaScript编写的,使得延迟加载其他内容更加快速和性能。您可以有效地延迟任何 javascript 文件以及内联脚本块。
如果您的页面只是一个 HTML 页面,用一些 JavaScript 进行了增强,那么只需要 <script async>
就可以了。浏览器解析和执行这些脚本需要时间,并且每次 UI 更改都可能重新布局您的布局,使您的加载速度更慢,没有人喜欢盯着空白的白页;用户不耐烦,会很快离开。
在各种情况下,使用 async
或 defer
提供的页面速度并不比 defer.js 快。
延迟加载纯文本数据 URI - Chrome 和 FF
#noLib #vanillaJS
建议不要在 Cross Browser PRODuction 上使用
直到 MS IE 死掉并且 MS Edge 将采用 Chromium 开源 ;)
延迟脚本的唯一方法是外部文件或Data_URI(不使用事件 DOMContentLoaded)
推迟
规格 script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"
Data_URI
规格Data_URI
正确 type "text/javascript" 根本不需要 base64... ;)
使用纯文本 所以你可以使用简单的:
<script defer src="data:text/javascript,
//do something with b-lazy plugin, lightbox plugin and then with flexslider
lightbox.option({
resizeDuration: 200,
wrapAround: true
})
">
是的,这有点奇怪,但是 <script type="module">
默认情况下会延迟,没有其他选项可以 按照确切的顺序混合 :
- 模块外部文件 - 默认延迟
- 模块内联脚本 - 默认延迟
- 外部文件 - 可选延迟
- 内联脚本 - 只有这个 hack - 据我所知(没有 libraries/frameworks)
您也可以使用 type="module"
:
<meta charset="utf-8">
<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>
<h1 id="top">Top Questions</h1>
https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type
我检查了所有建议的解决方案,但都有其缺点。所以我自己发明了。
将此内联脚本放入您的 head 标记中或紧跟在 body 标记开始之后:
<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>
这个衬里将收集您想要延迟的所有内联脚本,并在文档完全加载后分别 运行 它们。现在任何时候你需要 运行 延迟的内联脚本,只需像这样注册它:
<script>
alert('This alert will show immediately.');
Defer.push(function() {
alert('This alert will show only after document is loaded.');
// You can use anything which is not loaded yet, like jQuery
$(".selector").doSomeJqueryStuff();
});
// You can use it as many times as you like and in any place of your DOM.
Defer.push(function() {
// Any inline code you want to defer
});
</script>
此内联脚本将 运行 仅在加载文档后才会出现。这意味着您可以 运行 内联 jQuery 脚本,让您的 jQuery 留在 DOM.
的末尾
您可以使用此数据 url 作为 src 属性
data:application/javascript,eval(document.currentScript.textContent)
它采用当前脚本标签并评估其内容,就好像它在外部文件中一样。
它也适用于惰性属性。
它使用 IE 浏览器不支持的 document.currentScript。
<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
console.log('defered', typeof Vue); // function
</script>
<script>
console.log('not defered', typeof Vue); // undefined
</script>
如果问题是 jQuery 变量 $ 没有定义,也许你可以创建一个伪造的 $ 函数,returns 一个等待 DOMContentLoaded 的就绪函数?
我所有的内联脚本都有 $(document).ready(..... 问题是 $ 没有定义,因为头脚本被推迟了。
所以,只需在 head 的内联脚本中添加一个假的 $ 即可:
<script type="text/javascript">
var $ = function(element) {
return {
ready: function(callback) {
// in case the document is already rendered
if (document.readyState!="loading") callback();
// modern browsers
else if (document.addEventListener)
document.addEventListener("DOMContentLoaded", callback);
// IE <= 8
else document.attachEvent("onreadystatechange", function(){
if (document.readyState=="complete") callback();
});
}
};
};
</script>
有一种不那么晦涩的方式来完成延迟,它不需要回调、承诺或数据 url ......尽管它在后台做了一些 DOM 操作。微型库(109 字节 compressed/gziped)https://www.npmjs.com/package/deferscript 让您来做这件事。下面的例子是根据原来的post.
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
您所要做的就是插入一个值为 ./deferscript.js
的 src
属性。
我有以下 html 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
<!-- 26 dec flexslider js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
<script defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
</head>
<body>
</body>
</html>
我得到一个错误,说 jQuery 没有定义。现在,即使我从我的内联 JS 代码中删除 defer,它也会说 jQuery 未定义。出于某种原因,我必须将 jQuery 插件保留在头部并保持我的 JS 代码内联。我的问题是:
当
defer
属性存在时,为什么内联 Javascript 代码不会被延迟?有没有办法模仿我的内联 Javascript 代码的延迟行为?如果需要,我可以把它放在 body 标签的末尾。
来自 MDN 文档:
defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firingDOMContentLoaded
. The defer attribute should only be used on external scripts.
这称为 IIFE (立即调用的函数表达式),它在 DOM 可用之前执行。所以,在那种情况下 jQuery
是未定义的,因为它不在 DOM 中。
具有 defer
属性的脚本按指定顺序加载,但不在 文档本身加载之前。由于 defer
对 script
标签没有影响,除非它们也具有 src
属性,因此第一个执行的脚本是您的内联脚本。所以那个时候jQuery还没有加载
您至少可以通过两种方式解决此问题:
将您的内联脚本放入
.js
文件并使用src
属性引用它(除了您已经拥有的defer
属性) , 或让您的内联脚本等待文档和延迟脚本的加载。
DOMContentLoaded
事件将在发生时触发:<script> window.addEventListener('DOMContentLoaded', function() { (function($) { //do something with b-lazy plugin, lightbox plugin and then with flexslider })(jQuery); }); </script>
注意:请注意,在后一种情况下,不再包含 $(document).ready(function()
,因为它会等待相同的事件 (DOMContentLoaded
)。您 可以 仍然像在原始代码中那样包含它,但是 jQuery 只会立即 执行回调,这不会实际差异。
您可以从脚本中创建一个 Base64 URL 并将其放入 src!
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer>
</script>
我构建了一个快速测试以查看它的运行情况。
如果 defer
正在工作,您应该最后看到带有 Hello world!
的警报:
<script defer>
alert('Why no defer?!?');
</script>
<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer></script>
<script>
alert('Buh-bye world!');
</script>
手动操作有点费力,所以如果您有幸以某种方式(Handlebars、Angular 等)编译您的 HTML,那将大有帮助。
我目前正在使用:
<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
defer>
</script>
Defer/async 脚本标签不够好
有一个常识,你应该使用 <script src=".." async defer>
(或者在分配 src
之前设置 script.async = true
,当你从 JS 执行它时) and/or 把你的脚本在页面的最底部,以便尽可能快地加载页面并呈现给用户。
defer.js(注意:我是这个脚本的作者)是用普通的JavaScript编写的,使得延迟加载其他内容更加快速和性能。您可以有效地延迟任何 javascript 文件以及内联脚本块。
如果您的页面只是一个 HTML 页面,用一些 JavaScript 进行了增强,那么只需要 <script async>
就可以了。浏览器解析和执行这些脚本需要时间,并且每次 UI 更改都可能重新布局您的布局,使您的加载速度更慢,没有人喜欢盯着空白的白页;用户不耐烦,会很快离开。
在各种情况下,使用 async
或 defer
提供的页面速度并不比 defer.js 快。
延迟加载纯文本数据 URI - Chrome 和 FF
#noLib #vanillaJS
建议不要在 Cross Browser PRODuction 上使用
直到 MS IE 死掉并且 MS Edge 将采用 Chromium 开源 ;)
延迟脚本的唯一方法是外部文件或Data_URI(不使用事件 DOMContentLoaded)
推迟
规格 script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"
Data_URI
规格Data_URI
正确 type "text/javascript" 根本不需要 base64... ;)
使用纯文本 所以你可以使用简单的:
<script defer src="data:text/javascript,
//do something with b-lazy plugin, lightbox plugin and then with flexslider
lightbox.option({
resizeDuration: 200,
wrapAround: true
})
">
是的,这有点奇怪,但是 <script type="module">
默认情况下会延迟,没有其他选项可以 按照确切的顺序混合 :
- 模块外部文件 - 默认延迟
- 模块内联脚本 - 默认延迟
- 外部文件 - 可选延迟
- 内联脚本 - 只有这个 hack - 据我所知(没有 libraries/frameworks)
您也可以使用 type="module"
:
<meta charset="utf-8">
<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>
<h1 id="top">Top Questions</h1>
https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type
我检查了所有建议的解决方案,但都有其缺点。所以我自己发明了。
将此内联脚本放入您的 head 标记中或紧跟在 body 标记开始之后:
<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>
这个衬里将收集您想要延迟的所有内联脚本,并在文档完全加载后分别 运行 它们。现在任何时候你需要 运行 延迟的内联脚本,只需像这样注册它:
<script>
alert('This alert will show immediately.');
Defer.push(function() {
alert('This alert will show only after document is loaded.');
// You can use anything which is not loaded yet, like jQuery
$(".selector").doSomeJqueryStuff();
});
// You can use it as many times as you like and in any place of your DOM.
Defer.push(function() {
// Any inline code you want to defer
});
</script>
此内联脚本将 运行 仅在加载文档后才会出现。这意味着您可以 运行 内联 jQuery 脚本,让您的 jQuery 留在 DOM.
的末尾您可以使用此数据 url 作为 src 属性
data:application/javascript,eval(document.currentScript.textContent)
它采用当前脚本标签并评估其内容,就好像它在外部文件中一样。 它也适用于惰性属性。 它使用 IE 浏览器不支持的 document.currentScript。
<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
console.log('defered', typeof Vue); // function
</script>
<script>
console.log('not defered', typeof Vue); // undefined
</script>
如果问题是 jQuery 变量 $ 没有定义,也许你可以创建一个伪造的 $ 函数,returns 一个等待 DOMContentLoaded 的就绪函数?
我所有的内联脚本都有 $(document).ready(..... 问题是 $ 没有定义,因为头脚本被推迟了。
所以,只需在 head 的内联脚本中添加一个假的 $ 即可:
<script type="text/javascript">
var $ = function(element) {
return {
ready: function(callback) {
// in case the document is already rendered
if (document.readyState!="loading") callback();
// modern browsers
else if (document.addEventListener)
document.addEventListener("DOMContentLoaded", callback);
// IE <= 8
else document.attachEvent("onreadystatechange", function(){
if (document.readyState=="complete") callback();
});
}
};
};
</script>
有一种不那么晦涩的方式来完成延迟,它不需要回调、承诺或数据 url ......尽管它在后台做了一些 DOM 操作。微型库(109 字节 compressed/gziped)https://www.npmjs.com/package/deferscript 让您来做这件事。下面的例子是根据原来的post.
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
您所要做的就是插入一个值为 ./deferscript.js
的 src
属性。