ホームページ >ウェブフロントエンド >jsチュートリアル >me_javascript スキルを使用して JavaScript 非同期スクリプトの読み込みを学習する

me_javascript スキルを使用して JavaScript 非同期スクリプトの読み込みを学習する

WBOY
WBOYオリジナル
2016-05-16 15:30:591008ブラウズ

まず、このコード行を見てみましょう:

<script src = "allMyClientSideCode.js"></script>

これはちょっと…良くないですね。 「これをどこに配置すればよいでしょうか?」「上のほうの 93f0f5c25f18dab9d176bd4f6de5d30e タグの中に置くべきですか? それとも下のほうの 6c04bd5ca3fcae76e30b72ad730ca86d タグの中に置くべきですか? どちらのアプローチでもリッチなスクリプトが作成できます。サイトの終わりは悲劇的でした。」 。 93f0f5c25f18dab9d176bd4f6de5d30e タグ内の大きなスクリプトはすべてのページのレンダリング作業を停止させ、スクリプトがロードされるまでユーザーは「死の白い画面」状態になります。 6c04bd5ca3fcae76e30b72ad730ca86d タグの末尾にある大きなスクリプトでは、ユーザーには活気のない静的ページしか表示されず、クライアント側のレンダリングが実行されるべき場所には機能しないコントロールや空のボックスが散在しています。

この問題を完全に解決するには、スクリプトを分割して征服する必要があります。ページの見栄えを良くして使いやすくするスクリプトはすぐにロードする必要がありますが、後でロードできるスクリプトは後でロードする必要があります。しかし、呼び出し時に確実に利用できるようにしながら、これらのスクリプトを解凍するにはどうすればよいでしょうか?

1. 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグを再理解する

最新のブラウザの 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグは、クラシックとノンブロッキングの 2 つの新しいタイプに分類されます。これら 2 つのタグを使用してページをできるだけ早く読み込む方法について説明します。

1. ブロックしているスクリプトはどこに行きますか?

3f1c4e4b6b16bbbd69b2ee476dc4f83a タグの標準バージョンは、ブロッキング タグと呼ばれることがあります。この用語は文脈の中で理解する必要があります。最新のブラウザは、ブロッキング 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグを見つけると、ブロッキング ポイントをスキップして、ドキュメントの読み取りと他のリソース (スクリプトとスタイルシート) のダウンロードを続行します。ただし、スクリプトがダウンロードされて実行されるまで、ブラウザーはチョークポイントを超えてこれらのリソースを評価しません。したがって、Web ドキュメントの

タグ内に 5 つのブロック <script> タグがある場合、5 つのスクリプトすべてがダウンロードされて実行されるまで、ユーザーにはページ タイトルのみが表示されます。それだけでなく、これらのスクリプトが実行されたとしても、ブロック ポイントより前のドキュメントの部分しか表示されません。 6c04bd5ca3fcae76e30b72ad730ca86d タグでロードを待っているすべての機能を確認したい場合は、document.onreadystatechange のようなイベントにイベント ハンドラーをバインドする必要があります。 <p>上記の理由により、ページの 6c04bd5ca3fcae76e30b72ad730ca86d タグの最後にスクリプトを配置することがますます一般的になってきています。このようにして、ユーザーはページをより速く表示できる一方で、スクリプトはイベントがトリガーされるのを待たずに DOM にアクティブにアクセスすることもできます。ほとんどのスクリプトにとって、この「移動」は大きな改善です。 <p>しかし、すべてのスクリプトが同じように作成されているわけではありません。スクリプトを読み進める前に、自分自身に 2 つの質問をしてください。 <ul> <li>このスクリプトが 6c04bd5ca3fcae76e30b72ad730ca86d タグ内のインライン JavaScript によって直接呼び出される可能性はありますか?答えは明らかかもしれませんが、それでも確認する価値はあります。 <li>このスクリプトはレンダリングされたページの外観に影響しますか? Typekit ホスト フォントは一例です。 Typekit スクリプトをドキュメントの最後に配置すると、ページ テキストはドキュメントが読み取られた直後とスクリプトの実行時に 2 回レンダリングされます。 <p>上記の質問の答えのいずれかが「はい」である場合、スクリプトは 93f0f5c25f18dab9d176bd4f6de5d30e タグに配置する必要があります。それ以外の場合は、<body> タグに配置できます。 🎜> <br /> <div class="jb51code"> <pre class="brush:php;toolbar:false"> &lt;html&gt; &lt;head&gt; &lt;!--metadata and stylesheets go here --&gt; &lt;script src=&quot;headScripts.js&quot;&gt;&lt;/scripts&gt; &lt;/head&gt; &lt;body&gt; &lt;!-- content goes here --&gt; &lt;script src=&quot;bodyScripts.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </pre> これにより読み込み時間が大幅に短縮されますが、bodyScripts.js が読み込まれる前にユーザーがページを操作する機会が与えられる可能性があることに注意してください。 <p> <p><span style="color: #800000">2. スクリプトの早期ロードと遅延実行<strong> ほとんどのスクリプトは 6c04bd5ca3fcae76e30b72ad730ca86d に配置することをお勧めします。これにより、ユーザーは Web ページをより速く表示でき、DOM を操作する前に「ready」イベントをバインドするオーバーヘッドを回避できます。ただし、この方法には <p> という欠点もあります。つまり、ブラウザーはドキュメント全体をロードする前にこれらのスクリプトをロードできないため、低速の接続で送信される大きなドキュメントのボトルネックになります。 <strong> 理想的には、スクリプトはドキュメントのロードと同時にロードされる必要があり、DOM のレンダリングには影響しません。この方法では、スクリプトは 3f1c4e4b6b16bbbd69b2ee476dc4f83a タグの順序ですでに読み込まれているため、ドキュメントの準備ができたらスクリプトを実行できます。 <p> ここまで読んだ方は、そのようなニーズを満たすカスタム Ajax スクリプト ローダーを作成したくなるはずです。ただし、ほとんどのブラウザは、より単純なソリューションをサポートしています。 <p> <div class="jb51code"> <pre class="brush:js;"> &lt;script defer src = &quot;deferredScript.js&quot;&gt;</pre> <p>添加defer(延迟)属性相当于对浏览器说:“请马上开始加载这个脚本吧,但是,请等到文档就绪且所有此前具有defer 属性的脚本都结束运行之后再运行它。”在文档93f0f5c25f18dab9d176bd4f6de5d30e标签里放入延迟脚本,既能带来脚本置于6c04bd5ca3fcae76e30b72ad730ca86d标签时的全部好处,又能让大文档的加载速度大幅提升! <p>不足之处就是,并非所有浏览器都支持defer属性。这意味着,如果想确保自己的延迟脚本能在文档加载后运行,就必须将所有延迟脚本的代码都封装在诸如jQuery 之$(document).ready 之类的结构中。 <p>上一节的页面例子改进如下: <div class="jb51code"> <pre class="brush:php;toolbar:false"> &lt;html&gt; &lt;head&gt; &lt;!-- metadata and stylesheets go here --&gt; &lt;script src=&quot;headScripts.js&quot;&gt;&lt;/scripts&gt; &lt;script defer src=&quot;deferredScripts.js&quot;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;!-- content goes here --&gt; &lt;/body&gt; &lt;/html&gt; </pre> <p>请记住deferredScripts 的封装很重要,这样即使浏览器不支持defer,deferredScripts 也会在文档就绪事件之后才运行。如果页面主体内容远远超过几千字节,那么付出这点代价是完全值得的。 <p><span style="color: #800000"><strong>3、 脚本的并行加载 <p>如果你是斤斤计较到毫秒级页面加载时间的完美主义者,那么defer也许就像是淡而无味的薄盐酱油。你可不想一直等到此前所有的defer 脚本都运行结束,当然也肯定不想等到文档就绪之后才运行这些脚本,你就是想尽快加载并且尽快运行这些脚本。这也正是现代浏览器提供了async(异步)属性的原因。 <div class="jb51code"> <pre class="brush:js;"> &lt;script async src = &quot;speedyGonzales.js&quot;&gt; &lt;script async src = &quot;roadRunner.js&quot;&gt;</pre> <p>如果说defer 让我们想到一种静静等待文档加载的有序排队场景,那么async 就会让我们想到混乱的无政府状态。前面给出的那两个脚本会以任意次序运行,而且只要JavaScript 引擎可用就会立即运行,而不论文档就绪与否。<br /> 对大多数脚本来说,async 是一块难以下咽的鸡肋。async 不像defer那样得到广泛的支持。同时,由于异步脚本会在任意时刻运行,它实在太容易引起海森堡蚁虫之灾了(脚本刚好结束加载时就会蚁虫四起)。<br /> 当我们加载一些第三方脚本,而且也不在乎它们谁先运行谁后运行。因此,对这些第三方脚本使用async 属性,相当于一分钱没花就提升了它们的运行速度。<br /> 上一个页面示例再添加两个独立的第三方小部件,得到的结果如下:<br /> <div class="jb51code"> <pre class="brush:js;"> &lt;html&gt; &lt;head&gt; &lt;!-- metadata and stylesheets go here --&gt; &lt;script src=&quot;headScripts.js&quot;&gt;&lt;/scripts&gt; &lt;script src=&quot;deferredScripts.js&quot; defer&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;!-- content goes here --&gt; &lt;script async defer src=&quot;feedbackWidget.js&quot;&gt;&lt;/script&gt; &lt;script async defer src=&quot;chatWidget.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </pre> <p>这个页面结构清晰展示了脚本的优先次序。对于绝大多数浏览器,DOM的渲染只会延迟至headScripts.js 结束运行时。进行DOM渲染的同时会在后台加载deferredScripts.js。接着,在DOM 渲染结束时将运行deferredScripts.js 和那两个小部件脚本。这两个小部件脚本在那些支持async 的浏览器中会做无序运行。如果不确定这是否妥当,请勿使用async!<br /> <strong>二、可编程的脚本加载<br /> 虽然3f1c4e4b6b16bbbd69b2ee476dc4f83a标签简单得令人心动,但有些情况确实需要更精致的脚本加载方式。我们可能只想给那些满足一定条件的用户加载某个脚本,譬如白金会员或达到一定级别的玩家,也可能只想当用户单击激活时才加载某个特性,譬如聊天小部件。<br /> <span style="color: #800000"><strong>1、直接加载脚本<br /> 我们可以用类似下面这样的代码来插入3f1c4e4b6b16bbbd69b2ee476dc4f83a标签。<br /> <div class="jb51code"> <pre class="brush:js;"> var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.src = '/js/feature.js'; head.appendChild(script);</pre> <p>稍等,我们如何才能知道脚本何时加载结束呢?我们可以给脚本本身添加一些代码以触发事件,但如果要为每个待加载脚本都添加这样的代码,那也太闹心了。或者是另外一种情况,即我们不可能给第三方服务器上的脚本添加这样的代码。HTML5 规范定义了一个可以绑定回调的onload 属性。 <div class="jb51code"> <pre class="brush:js;"> script.onload = function() { // 现在可以调用脚本里定义的函数了 };</pre> <p>不过, IE8 及更老的版本并不支持onload , 它们支持的是onreadystatechange。某些浏览器在插入3f1c4e4b6b16bbbd69b2ee476dc4f83a标签时还会出现一些“灵异事件”。而且,这里甚至还没谈到错误处理呢!为了避免 <br /> 所有这些令人头疼的问题,在此强烈建议使用脚本加载库。 <p><span style="color: #000000"><strong>三、yepnope的条件加载<br /> yepnope是一个简单的、轻量级的脚本加载库(压缩后的精简版只有1.7KB),其设计目标就是真诚服务于最常见的动态脚本加载需求。<br /> yepnope 最简单的用法是,加载脚本并对脚本完成运行这一事件返回一个回调。<br /> <div class="jb51code"> <pre class="brush:js;"> yepnope({ load: 'oompaLoompas.js', callback: function() { console.log('oompa-Loompas ready!'); } }); </pre> <p>还是无动于衷?下面我们要用yepnope 来并行加载多个脚本并按给定次序运行它们。举个例子,假设我们想加载Backbone.js,而这个脚本又依赖于Underscore.js。为此,我们只需用数组形式提供这两个脚本的位置作为加载参数。 <div class="jb51code"> <pre class="brush:js;"> yepnope({ load: ['underscore.js', 'backbone.js'], complete: function() { // 这里是Backbone 的业务逻辑 } }); </pre> <p>请注意,这里使用了complete(完成)而不是callback(回调)。 <p>其差别在于,脚本加载列表中的每个资源均会运行callback,而只有当所有脚本都加载完成后才会运行complete。yepnope 的标志性特征是条件加载。给定test 参数,yepnope 会根据该参数值是否为真而加载不同的资源。举个例子,可以以一定的准确度判断用户是否在用触摸屏设备,从而据此相应地加载不同的样式表及脚本。 <div class="jb51code"> <pre class="brush:js;"> yepnope({ test: Modernizr.touch, yep: ['touchStyles.css', 'touchApplication.js'], nope: ['mouseStyles.css', 'mouseApplication.js'], complete: function() { // 不管是哪一种情况,应用程序均已就绪! } }); </pre> <p>我们只用寥寥几行代码就搭好了舞台,可以基于用户的接入设备而给他们完全不同的使用体验。当然,不是所有的条件加载都需要备齐yep(是)和nope(否)这两种测试结果。yepnope 最常见的用法之一就是加载垫片脚本以弥补老式浏览器缺失的功能。 <div class="jb51code"> <pre class="brush:js;"> yepnope({ test: window.json,nope: ['json2.js'], complete: function() { // 现在可以放心地用JSON 了 } }); </pre> <p>页面使用了yepnope 之后应该变成下面这种漂亮的标记结构:<br /> <div class="jb51code"> <pre class="brush:php;toolbar:false"> &lt;html&gt; &lt;head&gt; &lt;!-- metadata and stylesheets go here --&gt; &lt;script src=&quot;headScripts.js&quot;&gt;&lt;/scripts&gt; &lt;script src=&quot;deferredScripts.js&quot; defer&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;!-- content goes here --&gt; &lt;/body&gt; &lt;/html&gt; </pre> <p>很眼熟?这个结构和讨论defer 属性那一节给出的结构一样,唯一的区别是这里的某个脚本文件已经拼接了yepnope.js(很可能就在deferredScripts.js 的顶部),这样就可以独立地加载那些根据条件再加载的脚本(因为浏览器需要垫片脚本)和那些想要动态加载的脚本(以便回应用户的动作)。结果将是一个更小巧的deferredScripts.js。<br /> <strong>四、Require.js/AMD 模块化加载<br /> 开发人员想通过脚本加载器让混乱不堪的富脚本应用变得更规整有序一些,而Require.js 就是这样一种选择。Require.js 这个强大的工具包能够自动和AMD技术一起捋顺哪怕最复杂的脚本依赖图。<br /> 现在先来看一个用到Require.js 同名函数的简单脚本加载示例。 <div class="jb51code"> <pre class="brush:js;"> require(['moment'], function(moment) { console.log(moment().format('dddd')); // 星期几 }); </pre> <p>require 函数接受一个由模块名称构成的数组,然后并行地加载所有这些脚本模块。与yepnope 不同,Require.js 不会保证按顺序运行目标脚本,只是保证它们的运行次序能满足各自的依赖性要求,但前提是 <br /> 这些脚本的定义遵守了AMD(Asynchronous Module Definition,异步模块定义)规范。 <br /> <span style="color: #800000"><strong>案例一: 加载 JavaScript 文件 <div class="jb51code"> <pre class="brush:js;"> &lt;script src=&quot;./js/require.js&quot;&gt;&lt;/script&gt; &lt;script&gt; require([&quot;./js/a.js&quot;, &quot;./js/b.js&quot;], function() { myFunctionA(); myFunctionB(); }); &lt;/script&gt; </pre> <p>如案例一 所示,有两个 JavaScript 文件 a.js 和 b.js,里面各自定义了 myFunctionA 和 myFunctionB 两个方法,通过下面这个方式可以用 RequireJS 来加载这两个文件,在 function 部分的代码可以引用这两个文件里的方法。<br /> <p>require 方法里的这个字符串数组参数可以允许不同的值,当字符串是以”.js”结尾,或者以”/”开头,或者就是一个 URL 时,RequireJS 会认为用户是在直接加载一个 JavaScript 文件,否则,当字符串是类似”my/module”的时候,它会认为这是一个模块,并且会以用户配置的 baseUrl 和 paths 来加载相应的模块所在的 JavaScript 文件。配置的部分会在稍后详细介绍。<br /> <p>这里要指出的是,RequireJS 默认情况下并没有保证 myFunctionA 和 myFunctionB 一定是在页面加载完成以后执行的,在有需要保证页面加载以后执行脚本时,RequireJS 提供了一个独立的 domReady 模块,需要去 RequireJS 官方网站下载这个模块,它并没有包含在 RequireJS 中。有了 domReady 模块,案例一 的代码稍做修改加上对 domReady 的依赖就可以了。<br /> <span style="color: #800000"><strong>案例二: 页面加载后执行 JavaScript<strong><br /> <div class="jb51code"> <pre class="brush:js;"> &lt;script src=&quot;./js/require.js&quot;&gt;&lt;/script&gt; &lt;script&gt; require([&quot;domReady!&quot;, &quot;./js/a.js&quot;, &quot;./js/b.js&quot;], function() { myFunctionA(); myFunctionB(); }); &lt;/script&gt; </pre> <p>执行案例二的代码后,通过 Firebug 可以看到 RequireJS 会在当前的页面上插入为 a.js 和 b.js 分别声明了一个 27835793f4768f4164d1421d99e293bc 标签,用于异步方式下载 JavaScript 文件。async 属性目前绝大部分浏览器已经支持,它表明了这个 27835793f4768f4164d1421d99e293bc 标签中的 js 文件不会阻塞其他页面内容的下载。<br /> <span style="color: #800000"><strong>案例三:RequireJS 插入的 27835793f4768f4164d1421d99e293bc<br /> <div class="jb51code"> <pre class="brush:js;"> &lt;script type=&quot;text/javascript&quot; charset=&quot;utf-8&quot; async=&quot;&quot; data-requirecontext=&quot;_&quot; data-requiremodule=&quot;js/a.js&quot; src=&quot;js/a.js&quot;&gt;&lt;/script&gt; </pre> <p>AMD推行一个由Require.js 负责提供的名叫define 的全局函数,该函数有3 个参数: <ul> <li>模块名称, <li>模块依赖性列表, <li>在那些依赖性模块加载结束时触发的回调。 <p><strong>使用 RequireJS 来定义 JavaScript 模块<br /> 这里的 JavaScript 模块与传统的 JavaScript 代码不一样的地方在于它无须访问全局的变量。模块化的设计使得 JavaScript 代码在需要访问”全局变量”的时候,都可以通过依赖关系,把这些”全局变量”作为参数传递到模块的实现体里,在实现中就避免了访问或者声明全局的变量或者函数,有效的避免大量而且复杂的命名空间管理。<br /> 如同 CommonJS 的 AMD 规范所述,定义 JavaScript 模块是通过 define 方法来实现的。<br /> 下面我们先来看一个简单的例子,这个例子通过定义一个 student 模块和一个 class 模块,在主程序中实现创建 student 对象并将 student 对象放到 class 中去。<br /> <span style="color: #800000"><strong>案例四: student 模块,student.js<strong><br /> <div class="jb51code"> <pre class="brush:js;"> define(function(){ return { createStudent: function(name, gender){ return { name: name, gender: gender }; } }; }); </pre> <p><span style="color: #800000"><strong>案例五:class 模块,class.js<br /> <div class="jb51code"> <pre class="brush:js;"> define(function() { var allStudents = []; return { classID: &quot;001&quot;, department: &quot;computer&quot;, addToClass: function(student) { allStudents.push(student); }, getClassSize: function() { return allStudents.length; } }; } ); </pre> <p><span style="color: #800000"><strong>案例六: 主程序<br /> <div class="jb51code"> <pre class="brush:js;"> require([&quot;js/student&quot;, &quot;js/class&quot;], function(student, clz) { clz.addToClass(student.createStudent(&quot;Jack&quot;, &quot;male&quot;)); clz.addToClass(student.createStudent(&quot;Rose&quot;, &quot;female&quot;)); console.log(clz.getClassSize()); // 输出 2 }); </pre> <p>student 模块和 class 模块都是独立的模块,下面我们再定义一个新的模块,这个模块依赖 student 和 class 模块,这样主程序部分的逻辑也可以包装进去了。<br /> <span style="color: #800000"><strong>案例七:依赖 student 和 class 模块的 manager 模块,manager.js<br /> <div class="jb51code"> <pre class="brush:vb;"> define([&quot;js/student&quot;, &quot;js/class&quot;], function(student, clz){ return { addNewStudent: function(name, gender){ clz.addToClass(student.createStudent(name, gender)); }, getMyClassSize: function(){ return clz.getClassSize(); } }; }); </pre> <p><span style="color: #800000"><strong>案例八:新的主程序<br /> <div class="jb51code"> <pre class="brush:js;"> require([&quot;js/manager&quot;], function(manager) { manager.addNewStudent(&quot;Jack&quot;, &quot;male&quot;); manager.addNewStudent(&quot;Rose&quot;, &quot;female&quot;); console.log(manager.getMyClassSize());// 输出 2 }); </pre> <p>通过上面的代码示例,我们已经清楚的了解了如何写一个模块,这个模块如何被使用,模块间的依赖关系如何定义。 <p>其实要想让自己的站点更快捷,可以异步加载那些暂时用不到的脚本。为此最简单的做法是审慎地使用defer 属性和async 属性。如果要求根据条件来加载脚本,请考虑像yepnope 这样的脚本加载器。如果站点存在大量相互依赖的脚本,请考虑Require.js。选择最适合任务的工具,然后使用它,享受它带来的便捷。 <p>以上就是关于javascript的异步脚本加载的全部内容,想对大家的学习有所帮助。 </script>
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。