{"id":20093332,"url":"https://github.com/letiantian/how-to-load-dynamic-script","last_synced_at":"2025-08-21T10:31:18.501Z","repository":{"id":75432017,"uuid":"46793537","full_name":"letiantian/how-to-load-dynamic-script","owner":"letiantian","description":"The right way  to load javascript files dynamically. ","archived":false,"fork":false,"pushed_at":"2018-11-01T01:14:55.000Z","size":1648,"stargazers_count":219,"open_issues_count":1,"forks_count":25,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-12-07T00:43:33.979Z","etag":null,"topics":["javascript"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/letiantian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-11-24T13:31:59.000Z","updated_at":"2024-09-02T01:13:40.000Z","dependencies_parsed_at":"2023-06-06T11:45:21.018Z","dependency_job_id":null,"html_url":"https://github.com/letiantian/how-to-load-dynamic-script","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letiantian%2Fhow-to-load-dynamic-script","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letiantian%2Fhow-to-load-dynamic-script/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letiantian%2Fhow-to-load-dynamic-script/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letiantian%2Fhow-to-load-dynamic-script/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/letiantian","download_url":"https://codeload.github.com/letiantian/how-to-load-dynamic-script/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230507051,"owners_count":18236944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript"],"created_at":"2024-11-13T16:47:01.971Z","updated_at":"2024-12-19T22:08:12.902Z","avatar_url":"https://github.com/letiantian.png","language":"HTML","readme":"# 动态加载js文件的正确姿势\r\n\r\n\r\n**说明：**\r\n\r\n这个repository的结构：  \r\n* `img/`：图片\r\n* `LABjs-source/`：[LABjs](https://github.com/getify/LABjs)的源码，带注释，文中部分代码参考了该项目。\r\n* `lazyload-source`：[lazyload](https://github.com/rgrove/lazyload)的源码，带注释，文中部分代码参考了该项目。\r\n* `src/`：本文档中涉及的代码，**在Firefox 42中测试，使用Firebug观察和调试**。\r\n* `README.md`：本文档。\r\n\r\n本文中给出了多种解决方式，`方式1`对应的代码是`src/js/loader01.js`和`src/index01.js`，其他方式对应的代码位置类似。\r\n\r\n\u003e Gif图片使用[LICEcap](http://www.cockos.com/licecap/)生成。\r\n\r\n**目录：**\r\n\r\n[硬编码在html源码中的script是如何加载的](#硬编码在html源码中的script是如何加载的)  || [从一个例子出发](#从一个例子出发)\r\n || [方式1：一个错误的加载方式](#方式1一个错误的加载方式)  || [方式2](#方式2) || [方式3](#方式3) || [方式4](#方式4) || [方式5](#方式5) || [方式6 Promise 串行](#方式6-promise串行) || [方式7 Promise 并行](#方式7-promise并行) || [方式8 Generator Promise](#方式8-generatorpromise) || [现有哪些工具可以实现动态加载](#现有哪些工具可以实现动态加载) || [其他](#其他) || [资料](#资料)\r\n\r\n---\r\n\r\n\r\n最近在做一个为网页生成目录的工具[awesome-toc](https://github.com/someus/awesome-toc)，该工具提供了以jquery插件的形式使用的代码，也提供了一个基于[Bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet)（小书签）的浏览器插件。\r\n\r\n小书签需要向网页中注入多个js文件，也就相当于动态加载js文件。在编写这部分代码时候遇到坑了，于是深究了一段时间。\r\n\r\n我在这里整理了动态加载js文件的若干思路，**这对于理解异步编程很有用处，而且也适用于Nodejs**。\r\n\r\n## 硬编码在html源码中的script是如何加载的\r\n\r\n如果html中有：\r\n```html\r\n\u003cscript type=\"text/javascript\" src=\"1.js\"\u003e\u003c/script\u003e\r\n\u003cscript type=\"text/javascript\" src=\"2.js\"\u003e\u003c/script\u003e\r\n```\r\n那么，浏览器解析到\r\n```html\r\n\u003cscript type=\"text/javascript\" src=\"1.js\"\u003e\u003c/script\u003e\r\n```\r\n会停止渲染页面，去拉取`1.js`（IO操作），等到`1.js`的内容获取到后执行。\r\n1.js执行完毕后，浏览器解析到\r\n```html\r\n\u003cscript type=\"text/javascript\" src=\"2.js\"\u003e\u003c/script\u003e\r\n```\r\n进行和`1.js`类似的操作。\r\n\r\n不过现在部分浏览器支持async属性和defer属性，这个可以参考：\r\n\r\n[async vs defer attributes](http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html)  \r\n[script的defer和async](http://ued.ctrip.com/blog/script-defer-and-async.html)  \r\n\r\n[script -MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)指出：async对内联脚本（inline script）没有影响，defer的话因浏览器以及版本不同而影响不同。\r\n\r\n## 从一个例子出发\r\n\r\n举个实际的例子：  \r\n\r\n```html\r\n\u003chtml\u003e\r\n\u003chead\u003e\u003c/head\u003e\r\n\u003cbody\u003e\r\n\r\n    \u003cdiv id=\"container\"\u003e\r\n        \u003cdiv id=\"header\"\u003e\u003c/div\u003e\r\n        \u003cdiv id=\"body\"\u003e\r\n            \u003cbutton id=\"only-button\"\u003e hello world\u003c/button\u003e\r\n        \u003c/div\u003e\r\n        \u003cdiv id=\"footer\"\u003e\u003c/div\u003e\r\n    \u003c/div\u003e\r\n\r\n    \u003cscript src=\"http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\r\n    \u003cscript src=\"./your.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\r\n    \u003cscript src=\"./my.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\r\n    \r\n\u003c/body\u003e\r\n\u003c/html\u003e\r\n```\r\njs/your.js:\r\n```js\r\nconsole.log('your.js: time='+Date.parse(new Date()));\r\n\r\nfunction myAlert(msg) {\r\n    console.log('alert at ' + Date.parse(new Date()));\r\n    alert(msg);\r\n}\r\n\r\nfunction myLog(msg) {\r\n    console.log(msg);\r\n}\r\n```\r\n\r\njs/my.js：\r\n```js\r\nmyLog('my.js: time='+Date.parse(new Date()));\r\n$('#only-button').click(function() {\r\n    myAlert(\"hello world\");\r\n});\r\n```\r\n\r\n可以看出`jquery`、`js/your.js`、`js/my.js`三者的关系如下：\r\n\r\n* `js/my.js`依赖于`jquery`和`js/your.js`。\r\n* `jquery`和`js/your.js`之间没有依赖关系。\r\n\r\n浏览器打开`index00.html`，等待js加载完毕，点击按钮`hello world`将会触发`alert(\"hello world\");`。\r\n\r\nfirbug控制台输出：\r\n![](./img/blocking.gif)\r\n\r\n下面开始探索如何动态加载js文件。\r\n\r\n## 方式1：一个错误的加载方式\r\n\r\n文件js/loader01.js内容如下：\r\n```js\r\nLoader = (function() {\r\n\r\n  var loadScript = function(url) {\r\n    var script = document.createElement( 'script' );\r\n    script.setAttribute( 'src', url+'?'+'time='+Date.parse(new Date()));  // 不用缓存\r\n    document.body.appendChild( script );\r\n  };\r\n\r\n  var loadMultiScript = function(url_array) {\r\n    for (var idx=0; idx \u003c url_array.length; idx++) {\r\n      loadScript(url_array[idx]);\r\n    }\r\n  }\r\n\r\n  return {\r\n    load: loadMultiScript,\r\n  };\r\n\r\n})();  // end Loader\r\n```\r\n\r\nindex01.html内容如下：\r\n```html\r\n\u003chtml\u003e\r\n\u003chead\u003e\u003c/head\u003e\r\n\u003cbody\u003e\r\n\r\n    \u003cdiv id=\"container\"\u003e\r\n        \u003cdiv id=\"header\"\u003e\u003c/div\u003e\r\n        \u003cdiv id=\"body\"\u003e\r\n            \u003cbutton id=\"only-button\"\u003e hello world\u003c/button\u003e\r\n        \u003c/div\u003e\r\n        \u003cdiv id=\"footer\"\u003e\u003c/div\u003e\r\n    \u003c/div\u003e\r\n\r\n    \u003cscript src=\"./js/loader01.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\r\n    \u003cscript type=\"text/javascript\"\u003e\r\n        Loader.load([\r\n                    'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', \r\n                    './js/your.js',\r\n                    './js/my.js'\r\n                     ]);\r\n    \u003c/script\u003e\r\n    \r\n\u003c/body\u003e\r\n\u003c/html\u003e\r\n```\r\n\r\n浏览器打开`index01.html`，点击按钮`hello world`，会发现什么都没发生。打开firebug，进入控制台，可以看到这样的错误：\r\n\r\n![](./img/method01.gif)\r\n\r\n很明显，`my.js`没等jquery就先执行了。又由于存在依赖关系，脚本的执行出现了错误。这不是我想要的。\r\n\r\n在网上可以找到关于动态加载的一些说明，例如：\r\n\r\n\u003e Opera/Firefox（老版本）下：脚本执行的顺序与节点被插入页面的顺序一致\r\n\u003e \r\n\u003e IE/Safari/Chrome下：执行顺序无法得到保证\r\n\u003e \r\n\u003e 注意：\r\n\u003e\r\n\u003e   新版本的Firefox下，脚本执行的顺序与插入页面的顺序不一定一致，但可通过将script标签的async属性设置为false来保证顺序执行\r\n\u003e   老版本的Chrome下，脚本执行的顺序与插入页面的顺序不一定一致，但可通过将script标签的async属性设置为false来保证顺序执行\r\n\r\n真够乱的！！（这段描述来自：[LABJS源码浅析](http://www.cnblogs.com/chyingp/archive/2012/10/17/2726898.html)。）\r\n\r\n为了解决我们遇到的问题，我们可以在loadScript函数中修改script对象async的值：\r\n```js\r\nvar loadScript = function(url) {\r\n  var script = document.createElement('script');\r\n  script.async = false;  // 这里\r\n  script.setAttribute('src', url+'?'+'time='+Date.parse(new Date())); \r\n  document.body.appendChild(script);\r\n};\r\n```\r\n\r\n浏览器打开，发现可以正常执行！可惜该方法只在某些浏览器的某些版本中有效，没有通用性。[script browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Browser_compatibility)给出了下面的兼容性列表：\r\n\r\n![](./img/async-support.png)\r\n\r\n\r\n下面探索的方法都可以正确的加载和执行多个脚本，不过有些同样有兼容性问题（例如Pormise方式）。\r\n\r\n## 方式2\r\n\r\n可以认为绝大部分浏览器动态加载脚本的方式如下：\r\n\r\n1. 动态加载多个脚本时，这些脚本的加载（IO操作）可能并行，可能串行。  \r\n2. 一个脚本一旦加载完毕（IO结束），该脚本放入“待执行队列”，等待出队供js引擎去执行。 \r\n\r\n所以我们的示例中的三个js脚本的加载和执行顺序可以是下面的情况之一：\r\n\r\n1. `jquery`加载并执行，`js/your.js`加载并执行，`js/my.js`加载并执行。\r\n2. 和情况1类似，不过`js/your.js`在前，`jquery`在后。\r\n3. `jquery`和`js/your.js`并行加载，按照加载完毕的顺序来执行；等`jquery`和`js/your.js`都执行完毕后，加载并执行`js/my.js`。\r\n\r\n\r\n其中，“加载完毕”这是一个事件，浏览器的支持监测这个事件。这个事件在IE下是`onreadystatechange `，其他浏览器下是`onload `。\r\n\r\n据此，[Loading JavaScript without blocking](https://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/)给出了下面的代码：\r\n\r\n```js\r\nfunction loadScript(url, callback){\r\n\r\n    var script = document.createElement(\"script\")\r\n    script.type = \"text/javascript\";\r\n\r\n    if (script.readyState){  //IE\r\n        script.onreadystatechange = function(){\r\n            if (script.readyState == \"loaded\" ||\r\n                    script.readyState == \"complete\"){\r\n                script.onreadystatechange = null;\r\n                callback();\r\n            }\r\n        };\r\n    } else {  //Others\r\n        script.onload = function(){\r\n            callback();\r\n        };\r\n    }\r\n\r\n    script.src = url;\r\n    document.body.appendChild(script);\r\n}\r\n```\r\n\r\ncallback函数可以是去加载另外一个js，不过如果要加载的js文件较多，就成了“回调地狱”（callback hell）。\r\n\r\n回调地狱式可以通过一些模式来解决，例如下面给出的方式2：\r\n\r\n```js\r\nLoader = (function() {\r\n\r\n  var load_cursor = 0;\r\n  var load_queue;\r\n\r\n  var loadFinished = function() {\r\n    load_cursor ++;\r\n    if (load_cursor \u003c load_queue.length) {\r\n      loadScript();\r\n    }\r\n  }\r\n\r\n  function loadError (oError) {\r\n    console.error(\"The script \" + oError.target.src + \" is not accessible.\");\r\n  }\r\n\r\n\r\n  var loadScript = function() {\r\n    var url = load_queue[load_cursor];\r\n    var script = document.createElement('script');\r\n    script.type = \"text/javascript\";\r\n\r\n    if (script.readyState){  //IE\r\n        script.onreadystatechange = function(){\r\n            if (script.readyState == \"loaded\" ||\r\n                    script.readyState == \"complete\"){\r\n                script.onreadystatechange = null;\r\n                loadFinished();\r\n            }\r\n        };\r\n    } else {  //Others\r\n        script.onload = function(){\r\n            loadFinished();\r\n        };\r\n    }\r\n\r\n    script.onerror = loadError;\r\n\r\n    script.src = url+'?'+'time='+Date.parse(new Date());\r\n    document.body.appendChild(script);\r\n  };\r\n\r\n  var loadMultiScript = function(url_array) {\r\n    load_cursor = 0;\r\n    load_queue = url_array;\r\n    loadScript();\r\n  }\r\n\r\n  return {\r\n    load: loadMultiScript,\r\n  };\r\n\r\n})();  // end Loader\r\n\r\n//loading ...\r\nLoader.load([\r\n            'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', \r\n            './js/your.js',\r\n            './js/my.js'\r\n             ]);\r\n```\r\n\r\n`load_queue`是一个队列，保存需要依次加载的js的url。当一个js加载完毕后，`load_cursor++`用来模拟出队操作，然后加载下一个脚本。\r\n\r\nonerror事件也添加了回调，用来处理无法加载的js文件。当遇到无法加载的js文件时停止加载，剩下的文件也不会加载了。\r\n\r\n效果如下：\r\n\r\n![](./img/method02.gif)\r\n\r\n## 方式3\r\n方式2是串行的去加载，我们稍加改进，让可以并行加载的js脚本尽可能地并行加载。\r\n\r\n```js\r\nLoader = (function() {\r\n\r\n  var group_queue;      // group list\r\n  var group_cursor = 0; // current group cursor\r\n  var current_group_finished = 0;  \r\n\r\n\r\n  var loadFinished = function() {\r\n    current_group_finished ++;\r\n    if (current_group_finished == group_queue[group_cursor].length) {\r\n      next_group();\r\n      loadGroup();\r\n    }\r\n  };\r\n\r\n  var next_group = function() {\r\n    current_group_finished = 0;\r\n    group_cursor ++;\r\n  };\r\n\r\n  var loadError = function(oError) {\r\n    console.error(\"The script \" + oError.target.src + \" is not accessible.\");\r\n  };\r\n\r\n  var loadScript = function(url) {\r\n    console.log(\"load \"+url);\r\n    var script = document.createElement('script');\r\n    script.type = \"text/javascript\";\r\n\r\n    if (script.readyState){  //IE\r\n        script.onreadystatechange = function() {\r\n            if (script.readyState == \"loaded\" ||\r\n                    script.readyState == \"complete\") {\r\n                script.onreadystatechange = null;\r\n                loadFinished();\r\n            }\r\n        };\r\n    } else {  //Others\r\n        script.onload = function(){\r\n            loadFinished();\r\n        };\r\n    }\r\n\r\n    script.onerror = loadError;\r\n\r\n    script.src = url+'?'+'time='+Date.parse(new Date());\r\n    document.body.appendChild(script);\r\n  };\r\n\r\n  var loadGroup = function() {\r\n    if (group_cursor \u003e= group_queue.length) \r\n      return;\r\n    current_group_finished = 0;\r\n    for (var idx=0; idx \u003c group_queue[group_cursor].length; idx++) {\r\n      loadScript(group_queue[group_cursor][idx]);\r\n    }\r\n  };\r\n\r\n  var loadMultiGroup = function(url_groups) {\r\n    group_cursor = 0;\r\n    group_queue = url_groups;\r\n    loadGroup();\r\n  }\r\n\r\n  return {\r\n    load: loadMultiGroup,\r\n  };\r\n\r\n})();  // end Loader\r\n\r\n\r\n//loading\r\nvar jquery = 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',\r\n    your   = './js/your.js',\r\n    my     = './js/my.js'\r\n;\r\nLoader.load([ [jquery, your], [my] ]);\r\n```\r\n\r\n`Loader.load([ [jquery, your], [my] ]);`代表着`jquery`和`js/your.js`先尽可能快地加载和执行，等它们执行结束后，加载并执行`./js/my.js`。\r\n\r\n这里将每个子数组里的所有url看成一个group，group内部的脚本尽可能并行加载并执行，group之间则为串行。\r\n\r\n这段代码里使用了一个计数器`current_group_finished`记录当前group中完成的url的数量，在这个数量和url的总数一致时，进入下一个group。\r\n\r\n效果如下：  \r\n![](./img/method03.gif)\r\n\r\n\r\n## 方式4\r\n该方式是对方式3中代码的重构。\r\n\r\n```js\r\nLoader = (function() {\r\n\r\n  var group_queue = [];      // group list\r\n  var current_group_finished = 0;  \r\n  var finish_callback;\r\n  var finish_context;\r\n\r\n  var loadFinished = function() {\r\n    current_group_finished ++;\r\n    if (current_group_finished == group_queue[0].length) {\r\n      next_group();\r\n      loadGroup();\r\n    }\r\n  };\r\n\r\n  var next_group = function() {\r\n    group_queue.shift();\r\n  };\r\n\r\n  var loadError = function(oError) {\r\n    console.error(\"The script \" + oError.target.src + \" is not accessible.\");\r\n  };\r\n\r\n  var loadScript = function(url) {\r\n    console.log(\"load \"+url);\r\n    var script = document.createElement('script');\r\n    script.type = \"text/javascript\";\r\n\r\n    if (script.readyState){  //IE\r\n        script.onreadystatechange = function() {\r\n            if (script.readyState == \"loaded\" ||\r\n                    script.readyState == \"complete\") {\r\n                script.onreadystatechange = null;\r\n                loadFinished();\r\n            }\r\n        };\r\n    } else {  //Others\r\n        script.onload = function(){\r\n            loadFinished();\r\n        };\r\n    }\r\n\r\n    script.onerror = loadError;\r\n\r\n    script.src = url+'?'+'time='+Date.parse(new Date());\r\n    document.body.appendChild(script);\r\n  };\r\n\r\n  var loadGroup = function() {\r\n    if (group_queue.length == 0) {\r\n      finish_callback.call(finish_context);\r\n      return;\r\n    }\r\n    current_group_finished = 0; \r\n    for (var idx=0; idx \u003c group_queue[0].length; idx++) {\r\n      loadScript(group_queue[0][idx]);\r\n    }\r\n  };\r\n\r\n  var addGroup = function(url_array) {\r\n    if (url_array.length \u003e 0) {\r\n      group_queue.push(url_array);\r\n    }\r\n  };\r\n\r\n  var fire = function(callback, context) {\r\n    finish_callback = callback || function() {};\r\n    finish_context = context || {};\r\n    loadGroup();\r\n  };\r\n\r\n  var instanceAPI = {\r\n    load : function() {\r\n      addGroup([].slice.call(arguments));\r\n      return instanceAPI;\r\n    },\r\n\r\n    done : fire,\r\n  };\r\n\r\n  return instanceAPI;\r\n\r\n})();  // end Loader\r\n\r\n\r\n//loading\r\nvar jquery = 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',\r\n    your   = './js/your.js',\r\n    my     = './js/my.js'\r\n;\r\n// Loader.load(jquery, your).load(my).done();\r\nLoader.load(jquery, your)\r\n      .load(my)\r\n      .done(function(){console.log(this.msg)}, {msg: 'finished'});\r\n```\r\n\r\n在调用多次load()函数后，必须调用done()函数。done()函数用来触发所有脚本的load。\r\n\r\n## 方式5\r\n\r\n这个方式是对方式4的重写。改进为调用load()时候尽可能去触发实际的load操作。\r\n\r\n```js\r\n// 这里调试用的代码我没有删除\r\n\r\nLoader = (function() {\r\n\r\n    var group_queue  = [];      // group list\r\n\r\n    //// url_item = {url:str, start: false, finished：false}\r\n\r\n    // 用于调试\r\n    var log = function(msg) {\r\n        return;\r\n        console.log(msg);\r\n    }\r\n\r\n    var isFunc = function(obj) { \r\n        return Object.prototype.toString.call(obj) == \"[object Function]\"; \r\n    }\r\n\r\n    var isArray = function(obj) { \r\n        return Object.prototype.toString.call(obj) == \"[object Array]\"; \r\n    }\r\n\r\n    var isAllStart = function(url_items) {\r\n        for (var idx=0; idx\u003curl_items.length; ++idx) {\r\n            if (url_items[idx].start == false )\r\n                return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    var isAnyStart = function(url_items) {\r\n        for (var idx=0; idx\u003curl_items.length; ++idx) {\r\n            if (url_items[idx].start == true )\r\n                return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    var isAllFinished = function(url_items) {\r\n        for (var idx=0; idx\u003curl_items.length; ++idx) {\r\n            if (url_items[idx].finished == false )\r\n                return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    var isAnyFinished = function(url_items) {\r\n        for (var idx=0; idx\u003curl_items.length; ++idx) {\r\n            if (url_items[idx].finished == true )\r\n                return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    var loadFinished = function() {\r\n        nextGroup();\r\n    };\r\n\r\n    var showGroupInfo = function() {\r\n        for (var idx=0; idx\u003cgroup_queue.length; idx++) {\r\n            group = group_queue[idx];\r\n            if (isArray(group)) {\r\n                log('**********************');\r\n                for (var i=0; i\u003cgroup.length; i++) {\r\n                    log('url:     '+group[i].url);\r\n                    log('start:   '+group[i].start);\r\n                    log('finished:'+group[i].finished);\r\n                    log('-------------------');\r\n                }\r\n                log('isAllStart: ' + isAllStart(group));\r\n                log('isAnyStart: ' + isAnyStart(group));\r\n                log('isAllFinished: ' + isAllFinished(group));\r\n                log('isAnyFinished: ' + isAnyFinished(group));\r\n                log('**********************');\r\n            }\r\n        }\r\n    };\r\n\r\n    var nextGroup = function() {\r\n        while (group_queue.length \u003e 0) {\r\n            showGroupInfo();\r\n            // is Func\r\n            if (isFunc(group_queue[0])) {\r\n                log('## nextGroup: exec func');\r\n                group_queue[0]();  // exec\r\n                group_queue.shift();\r\n                continue;\r\n            // is Array\r\n            } else if (isAllFinished(group_queue[0])) {   \r\n                log('## current group all finished');\r\n                group_queue.shift();\r\n                continue;\r\n            } else if (!isAnyStart(group_queue[0])) {\r\n                log('## current group no one start!');\r\n                loadGroup();\r\n                break;\r\n            } else {\r\n                break;\r\n            }\r\n        }\r\n    };\r\n\r\n    var loadError = function(oError) {\r\n        console.error(\"The script \" + oError.target.src + \" is not accessible.\");\r\n    };\r\n\r\n    var loadScript = function(url_item) {\r\n        log(\"load \"+url_item.url);\r\n        url = url_item.url;\r\n        url_item.start = true;\r\n        var script = document.createElement('script');\r\n        script.type = \"text/javascript\";\r\n\r\n        if (script.readyState){  //IE\r\n            script.onreadystatechange = function() {\r\n                if (script.readyState == \"loaded\" ||\r\n                        script.readyState == \"complete\") {\r\n                    script.onreadystatechange = null;\r\n                    url_item.finished = true;\r\n                    loadFinished();\r\n                }\r\n            };\r\n        } else {  //Others\r\n            script.onload = function(){\r\n                url_item.finished = true;\r\n                loadFinished();\r\n            };\r\n        }\r\n\r\n        script.onerror = loadError;\r\n\r\n        script.src = url+'?'+'time='+Date.parse(new Date());\r\n        document.body.appendChild(script);\r\n    };\r\n\r\n    var loadGroup = function() {\r\n        for (var idx=0; idx \u003c group_queue[0].length; idx++) {\r\n            loadScript(group_queue[0][idx]);\r\n        }\r\n    };\r\n\r\n    var addGroup = function(url_array) {\r\n        log('add :' + url_array);\r\n        if (url_array.length \u003e 0) {\r\n            group = [];\r\n            for (var idx=0; idx\u003curl_array.length; idx++) {\r\n                url_item = {\r\n                    url: url_array[idx],\r\n                    start: false,\r\n                    finished: false,\r\n                };\r\n                group.push(url_item);\r\n            }\r\n            group_queue.push(group);\r\n        }\r\n        nextGroup();\r\n    };\r\n\r\n    var addFunc = function(callback) {\r\n        callback \u0026\u0026 isFunc(callback) \u0026\u0026  group_queue.push(callback);\r\n        log(group_queue);\r\n        nextGroup();\r\n    };\r\n\r\n    var instanceAPI = {\r\n        load : function() {\r\n            addGroup([].slice.call(arguments));\r\n            return instanceAPI;\r\n        },\r\n\r\n        wait : function(callback) {\r\n            addFunc(callback);\r\n            return instanceAPI;\r\n        }\r\n    };\r\n\r\n    return instanceAPI;\r\n\r\n})();  // end Loader，这尼玛就是一个状态机\r\n\r\n\r\n// loading\r\nvar jquery = 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',\r\n    your   = './js/your.js',\r\n    my     = './js/my.js'\r\n;\r\n// Loader.load(jquery, your).load(my);\r\nLoader.load(jquery, your)\r\n      .wait(function(){console.log(\"yeah, jquery and your.js were loaded\")})\r\n      .load(my)\r\n      .wait(function(){console.log(\"yeah, my.js was loaded\")});\r\n```\r\n\r\n上面的调用中，每次load时候会尝试马上加载和执行这些脚本，而不是像方式4那样要等done()被调用。\r\n\r\n另外出现了新的函数wait，当wait之前的load和wait执行结束后，该wait中的匿名函数会被调用。\r\n\r\n效果如下：  \r\n![](./img/method05.gif)\r\n\r\n## 方式6 Promise+串行\r\nPromise是一种设计模式。关于Promise，下面的几篇文章值得一看：\r\n\r\n* [Promise - MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\r\n* [JavaScript Promises](http://www.html5rocks.com/zh/tutorials/es6/promises/)\r\n* [JavaScript Promise迷你书（中文版）](http://liubin.github.io/promises-book/)\r\n* [An Implemention of Promise](https://www.promisejs.org/implementing/)\r\n\r\n当前浏览器对Promise的支持情况如下：\r\n\r\n![](./img/promise-support.png)\r\n\r\n使用Promise解决脚本动态加载问题的方案如下：\r\n\r\n```js\r\nfunction getJS(url) {\r\n    return new Promise(function(resolve, reject) {\r\n        var script = document.createElement('script');\r\n        script.type = \"text/javascript\";\r\n\r\n        if (script.readyState){  //IE\r\n            script.onreadystatechange = function() {\r\n                if (script.readyState == \"loaded\" ||\r\n                        script.readyState == \"complete\") {\r\n                    script.onreadystatechange = null;\r\n                    resolve('success: '+url);\r\n                }\r\n            };\r\n        } else {  //Others\r\n            script.onload = function(){\r\n                resolve('success: '+url);\r\n            };\r\n        }\r\n\r\n        script.onerror = function() {\r\n            reject(Error(url + 'load error!'));\r\n        };\r\n\r\n        script.src = url+'?'+'time='+Date.parse(new Date());\r\n        document.body.appendChild(script);\r\n\r\n    });\r\n}\r\n\r\n//loading\r\nvar jquery = 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',\r\n    your   = './js/your.js',\r\n    my     = './js/my.js'\r\n;\r\n\r\ngetJS(jquery).then(function(msg){\r\n    return getJS(your);\r\n}).then(function(msg){\r\n    return getJS(my);\r\n}).then(function(msg){\r\n    console.log(msg);\r\n});\r\n```\r\n\r\n这个实现中js是串行加载的。\r\n\r\n效果如下：  \r\n![](./img/method06.gif)\r\n\r\n## 方式7 Promise+并行\r\n可以使用[Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)使`jquery`和`js/your.js`并行加载。\r\n\r\n```js\r\nPromise.all([getJS(jquery), getJS(your)]).then(function(results){\r\n    return getJS(my);\r\n}).then(function(msg){\r\n    console.log(msg);\r\n});\r\n```\r\n\r\n![](./img/method07.gif)\r\n\r\n## 方式8 Generator+Promise\r\nPromise配合生成器（Generator）可以让js程序按照串行的思维编写。\r\n\r\n关于生成器，下面的几篇文章值得一看：  \r\n* [function* - MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*)\r\n* [The Basics Of ES6 Generators](https://davidwalsh.name/es6-generators)  \r\n\r\n浏览器的支持情况如下：  \r\n![](./img/generator-support.png)\r\n\r\n来两个典型的生成器示例：  \r\n\r\n示例1：  \r\n```\r\nfunction *addGenerator() {\r\n  var i = 0;\r\n  while (true) {\r\n    i += yield i;\r\n  }\r\n}\r\n\r\nvar adder = addGenerator();\r\nconsole.log( adder.next().value );  // yield i时候暂停 （循环1）\r\nconsole.log( adder.next(5).value ); // 循环1中yield i的结果为5，i+=5，进入下一个循环（循环2），循环2中yield i 暂停，返回5\r\nconsole.log( adder.next(5).value ); // 循环2中yield i的结果为5\r\nconsole.log( adder.next(5).value ); // 循环3中yield i的结果为5\r\nconsole.log( adder.next(50).value ); //循环4中yield i的结果为50，i+=50，进入循环6\r\n```\r\n\r\n输出：\r\n```plain\r\n0\r\n5\r\n10\r\n15\r\n65\r\n```\r\n\r\n示例2：\r\n```plain\r\nfunction* idMaker(){\r\n  var index = 0;\r\n  while(index \u003c 3)\r\n    yield index++;\r\n}\r\n\r\nvar gen = idMaker();\r\n\r\nwhile ( result = gen.next() ) {\r\n    if (!result.done) {\r\n        console.log(result.done + ':' + result.value);\r\n    } else{\r\n        console.log(result.done + ':' + result.value);\r\n        break;\r\n    }\r\n}\r\n```\r\n输出：\r\n```\r\nfalse:0\r\nfalse:1\r\nfalse:2\r\ntrue:undefined\r\n```\r\n\r\n下面的文章介绍了如何搭配Promise和Generator：  \r\n* [JavaScript Promises](http://www.html5rocks.com/zh/tutorials/es6/promises/) 的最后一节\r\n* [Generators with Promise](https://www.promisejs.org/generators/)\r\n\r\nGenerator+Promise实现js脚本动态加载的方式如下：\r\n\r\n```js\r\nfunction getJS(url) {\r\n    return new Promise(function(resolve, reject) {\r\n        var script = document.createElement('script');\r\n        script.type = \"text/javascript\";\r\n\r\n        if (script.readyState){  //IE\r\n            script.onreadystatechange = function() {\r\n                if (script.readyState == \"loaded\" ||\r\n                        script.readyState == \"complete\") {\r\n                    script.onreadystatechange = null;\r\n                    resolve('success: '+url);\r\n                }\r\n            };\r\n        } else {  //Others\r\n            script.onload = function() {\r\n                resolve('success: '+url);\r\n            };\r\n        }\r\n\r\n        script.onerror = function() {\r\n            reject(Error(url + 'load error!'));\r\n        };\r\n\r\n        script.src = url+'?'+'time='+Date.parse(new Date());\r\n        document.body.appendChild(script);\r\n\r\n    });\r\n}\r\n\r\nfunction spawn(generatorFunc) {\r\n  function continuer(verb, arg) {\r\n    var result;\r\n    try {\r\n      result = generator[verb](arg);  // 这个result是生成器的返回值，有value和done两个属性\r\n    } catch (err) {\r\n      return Promise.reject(err);\r\n    }\r\n    if (result.done) {\r\n      return result.value;\r\n    } else {\r\n      return Promise.resolve(result.value).then(onFulfilled, onRejected);  // result.value是promise对象\r\n    }\r\n  }\r\n  var generator = generatorFunc();\r\n  var onFulfilled = continuer.bind(continuer, \"next\");\r\n  var onRejected = continuer.bind(continuer, \"throw\");\r\n  return onFulfilled();\r\n}\r\n\r\n//// loading\r\n\r\nvar jquery = 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',\r\n    your   = './js/your.js',\r\n    my     = './js/my.js'\r\n;\r\n\r\n// “串行”代码在这里\r\nspawn(function*() {\r\n    try {\r\n        yield getJS(jquery);\r\n        console.log('jquery has loaded');\r\n        yield getJS(your);\r\n        console.log('your.js has loaded');\r\n        yield getJS(my);\r\n        console.log('my.js has loaded');\r\n    } catch (err) { \r\n        console.log(err);\r\n    }\r\n});\r\n```\r\n\r\n效果如下：  \r\n\r\n![](./img/method08.gif)\r\n\r\n\r\n\r\n## 现有哪些工具可以实现动态加载\r\n在[For Your Script Loading Needs](http://code.tutsplus.com/articles/for-your-script-loading-needs--net-19570)列出了许多工具，例如[lazyload](https://github.com/rgrove/lazyload)、[LABjs](https://github.com/getify/LABjs)、[RequireJS](http://requirejs.org/)等。\r\n\r\n有些工具也提供了新的思路，例如LABjs中可以使用ajax获取同域下的js文件。\r\n\r\n## 其他\r\n\r\n[async](https://github.com/caolan/async)\r\n\r\n[q](https://github.com/kriskowal/q)\r\n\r\n[co](https://github.com/tj/co)\r\n\r\n\r\n## 资料\r\n[Script Execution Control](https://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29)\r\n\r\n[LABJS源码浅析](http://www.cnblogs.com/chyingp/archive/2012/10/17/2726898.html)\r\n\r\n[Dynamic Script Execution Order](https://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order)\r\n\r\n[script节点的onload,onreadystatechange事件](http://javne.iteye.com/blog/691262)\r\n\r\n[readystatechange - MDN](https://developer.mozilla.org/en-US/docs/Web/Events/readystatechange)\r\n\r\n[readyState property - MSDN](https://msdn.microsoft.com/en-us/library/ms534359%28v=vs.85%29.aspx)  \r\n\r\n[Loading JavaScript without blocking](https://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/)  \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fletiantian%2Fhow-to-load-dynamic-script","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fletiantian%2Fhow-to-load-dynamic-script","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fletiantian%2Fhow-to-load-dynamic-script/lists"}