简体   繁体   English

如何推迟内联Javascript?

[英]How to defer inline Javascript?

I have the following html code:我有以下 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>

I get an error, saying jQuery is not defined.我收到一个错误,说 jQuery 未定义。 Now even if I remove defer from my inline JS code, it says jQuery is undefined.现在,即使我从内联 JS 代码中删除了 defer,它也会说 jQuery 未定义。 For some reason I have to keep the jQuery plugins in the head and keep my JS code inline.出于某种原因,我必须保留 jQuery 插件并保持我的 JS 代码内联。 My question is:我的问题是:

  1. Why doesn't inline Javascript code get deferred when defer attribute is present on it?为什么内联 Javascript 代码存在defer属性时不会被延迟?

  2. Is there a way to imitate the defer behavior on my inline Javascript code?有没有办法模仿我的内联 Javascript 代码上的延迟行为? I can put that at the end of body tag if required.如果需要,我可以把它放在 body 标签的末尾。

The scripts with the defer attribute load in the order they are specified, but not before the document itself has been loaded.带有defer属性的脚本会defer指定的顺序加载,但不会在文档本身加载之前加载。 As defer has no effect on script tags unless they also have the src attribute, the first script that gets executed is your inline script.由于deferscript标签没有影响,除非它们也有src属性,因此第一个执行的脚本是您的内联脚本。 So at that time jQuery is not loaded yet.所以当时 jQuery 还没有加载。

You can solve this in at least two ways:您至少可以通过两种方式解决此问题:

  • Put your inline script in a .js file and reference it with a src attribute (in addition to the defer attribute which you already had there), or将您的内联脚本放在.js文件中,并使用src属性(除了您已经在那里拥有的defer属性之外)引用它,或者

  • Let your inline script wait for the document and the deferred scripts to be loaded.让您的内联脚本等待文档和延迟脚本被加载。 The DOMContentLoaded event will fire when that has happened: DOMContentLoaded事件将在发生时触发:

     <script> window.addEventListener('DOMContentLoaded', function() { (function($) { //do something with b-lazy plugin, lightbox plugin and then with flexslider })(jQuery); }); </script>

NB: Notice that in the latter case $(document).ready(function() is not included any more, as that would wait for the same event ( DOMContentLoaded ). You could still include it like you had in your original code, but then jQuery would just execute the callback immediately , which makes no practical difference.注:请注意,在后一种情况下$(document).ready(function()不包含任何更多,因为这将等待同一事件( DOMContentLoaded )。您仍然可以包括像你在原来的代码有,但然后 jQuery 会立即执行回调,这没有实际区别。

You can create a Base64 URL out of the script and put it into the src!您可以从脚本中创建一个 Base64 URL 并将其放入 src 中!

<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
        defer>
</script>

I built a quick test to see it in action.我建立了一个快速测试来查看它的运行情况。 You should see an alert with Hello world!您应该会看到一个带有Hello world!的警报Hello world! last if defer is working:最后如果defer工作:

 <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>

Doing it manually is a little laborious so if you have the luxury of compiling your HTML in some way (Handlebars, Angular, etc.) then that helps a lot.手动完成它有点费力,因此如果您有幸以某种方式(Handlebars、Angular 等)编译 HTML,那么这将有很大帮助。

I'm currently using:我目前正在使用:

<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
        defer>
</script>

From MDN docs:来自 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 .此布尔属性设置为向浏览器指示该脚本将在解析文档之后但在触发DOMContentLoaded之前执行。 The defer attribute should only be used on external scripts. defer 属性只能用于外部脚本。

This is called an IIFE (Immediately Invoked Function Expression) which gets executed before DOM is available.这称为 IIFE (立即调用的函数表达式) ,它在 DOM 可用之前执行。 So, in that case jQuery is undefined because it it not in the DOM.因此,在这种情况下, jQuery是未定义的,因为它不在 DOM 中。

defer loading with plain text Data URI - Chrome and FF使用纯文本数据 URI 延迟加载 - Chrome 和 FF

#noLib #vanillaJS #noLib #vanillaJS

suggest not to use on Cross Browser PRODuction建议不要在跨浏览器生产中使用

until MS IE dies and MS Edge will adopt the Chromium open source ;)直到 MS IE 消亡,MS Edge 将采用 Chromium 开源;)

the only way to defer script is external file or Data_URI (without using event DOMContentLoaded)延迟脚本的唯一方法是外部文件Data_URI (不使用事件 DOMContentLoaded)

defer推迟

spec script#attr-defer (MDN web docs) : "This attribute must not be used if the src attribute is absent (ie for inline scripts), in this case it would have no effect.)" spec script#attr-defer(MDN 网络文档) :“如果 src 属性不存在(即对于内联脚本),则不得使用此属性,在这种情况下它将不起作用。)”

Data_URI数据_URI

spec Data_URI规格数据_URI

with right type "text/javascript" there is no need to base64 at all... ;)使用正确的类型“text/javascript”根本不需要base64 ...;)

using plain text so you can use simple:使用纯文本,因此您可以使用简单的:

<script defer src="data:text/javascript,

//do something with b-lazy plugin, lightbox plugin and then with flexslider

lightbox.option({
  resizeDuration: 200,
  wrapAround: true
})

">

yes, it's little bit weird hack, but <script type="module"> are deferred by default, there is no other option to mix following in exact order :是的,这有点奇怪,但是<script type="module">默认是延迟的,没有其他选项可以按确切顺序混合以下内容

  • module external files - deferred by default模块外部文件 -默认延迟
  • module inline scripts - deferred by default模块内联脚本 -默认延迟
  • external files - optionally deferred外部文件 -可选择延迟
  • inline scripts - only with this hack - as I know (without libraries/frameworks)内联脚本 - 只有这个hack - 据我所知(没有库/框架)

Defer/async script tags are not good enough延迟/异步脚本标签不够好

There is a common knowledge that you should use <script src=".." async defer> (or set script.async = true before assigning src , when you do it from JS) and/or put your scripts at the very bottom of the page, so that as much as possible of the page gets loaded and rendered to the user, as fast as possible.有一个常识,您应该使用<script src=".." async defer> (或在分配src之前设置script.async = true ,当您从 JS 执行此操作时)和/或将您的脚本放在最底部页面,以便尽可能快地加载并呈现给用户。

defer.js (note: I am the author of this script ) is written in plain JavaScript, making lazy-loading other contents more fast and performant. defer.js (注意:我是这个脚本的作者)是用纯 JavaScript 编写的,这使得延迟加载其他内容更加快速和高效 You can defer any javascript files as well as inline script blocks efficiently.您可以有效地延迟任何 javascript 文件以及内联脚本块。

延迟加载 JavaScript

If your page is just an HTML page enhanced with some JavaScript, then you're good with just <script async> .如果您的页面只是使用一些 JavaScript 增强的 HTML 页面,那么您只需使用<script async> It takes time for browser to parse and execute those scripts, and each UI change may reflow your layout, make your load speed more slow, no one likes staring at a blank white page;浏览器解析和执行这些脚本需要时间,每次UI变化都可能重排你的布局,让你的加载速度变慢,没有人喜欢盯着一个空白的页面; users are impatient and will leave quickly.用户不耐烦,很快就会离开。

In various cases, using async or defer does not deliver faster page speed than defer.js does.在各种情况下,使用asyncdefer不会提供比defer.js更快的页面速度。

You can also use type="module" :您还可以使用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 https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type

I checked all the proposed solutions but all have their disadvantages.我检查了所有建议的解决方案,但都有其缺点。 So I invented my own.所以我发明了自己的。

Put this inline script into your head tag or right after the start of body tag:将此内联脚本放入您的 head 标签或 body 标签开始之后:

<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>

This one liner will collect all the inline scripts you want to defer and run them respectively as soon as document is fully loaded.这个一个 liner 将收集所有您想要延迟的内联脚本,并在文档完全加载后立即分别运行它们。 Now anytime you need to run an inline script deferred, just register it like:现在,无论何时您需要延迟运行内联脚本,只需像这样注册它:

<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>

This inline script will run only after document is loaded.此内联脚本仅在加载文档后运行。 That means you can run inline jQuery script having your jQuery stay at the end of your DOM.这意味着您可以运行内联 jQuery 脚本,让您的 jQuery 停留在 DOM 的末尾。

You can use this data url as src attribute您可以将此数据网址用作 src 属性

data:application/javascript,eval(document.currentScript.textContent)

which takes this current script tag and evaluate its content as if it was inside an external file.它接受这个当前的脚本标签并评估它的内容,就好像它在一个外部文件中一样。 it also works with lazy attribute.它也适用于惰性属性。 it uses document.currentScript which not supported by IE browsers.它使用 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>

If the problem is that jQuery variable $ is not defined, maybe you can create a fake $ function that returns a ready function waiting for the DOMContentLoaded?如果问题是未定义 jQuery 变量 $,也许您可​​以创建一个假 $ 函数,该函数返回一个等待 DOMContentLoaded 的就绪函数?

All my inline scripts has $(document).ready(..... and the problem is that $ is not defined as the header scripts are deferred.我所有的内联脚本都有 $(document).ready(..... 问题是 $ 没有定义为头脚本被推迟。

So, just add a fake $ in an inline script in head:因此,只需在 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM