💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 你真的了解script标签吗? 先来看一个简单的html文档: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>Examples</title> <meta name="description" content=""> <meta name="keywords" content=""> <link href=".style.css" rel="stylesheet"> <script src="./app.js"></script> </head> <body> <h1>hello Word</h1> </body> </html> ``` 这是一份最简单不过的html文档了,我们只关注script标签,js的加载就可以了。 还有常见的这样的: ``` …… <script src="./jquery.js"></script> <script src="./jquery.xxx.js"></script> …… ``` 我们都知道依赖于jQuery的插件必须放在jQuery加载之后引入,否则会出错。 所以我们将插件加载的script标签写在后面,这样就不会出错了。 但是有一个问题,我们这样写真的能保证jQuery在之前加载吗,如果jQuery的cdn很慢呢,浏览器一定会保证加载顺序一定是按照我们写的顺序吗?以script标签的形式加载时浏览器是串行加载的吗? 是的,浏览器的确是保证加载顺序**按照我们写的script标签的顺序来依次加载并执行**(注意这里强调的**按照书写的script标签的顺序**,**依次加载并执行**),并且保证是串行加载的,所以我们这样写代码没有问题。所以如果cdn很慢,那么页面会一直卡在那里显示白屏,不会解析内容给用户,并且你可以尝试一下,在./app.js里面写一个死循环也是这样的,也就是说**浏览器是单进程执行js和加载js的,并且解析html和执行js也是单进程的,是阻塞的**,这种特性很多时候我们很容易感受到,比如弹窗时页面会“卡住”,加载时页面白屏等等,所以通常我们都建议不重要的js放在文档的最后body标签的前面就可以了,这样不会阻塞页面解析渲染。 (其实这里面浏览器有很复杂的细节,DOM书写顺序是执行顺序没错,但是加载是串行阻塞的还是并行的浏览器有自己的策略吧,这里面涉及的细节很复杂,不过可以肯定的是,DOM的顺序就表示了依赖关系,依赖的要写在上面,并且script的特性就是加载了就会执行里面的脚本,所以上面说加载并执行) > 但为了不破坏您的页面渲染,我们还是建议您将涉及到javascript标签的代码,放到页面的最底部。 ([百度分享](http://share.baidu.com/help/installation?type=icon)代码安装也是这样说明的) 为了简单,确保程序按照我们预期的运行,我们一直以来就是这样写script标签吗,并且它也是按照我们所想的运行,没有问题,但其实我们忽略了script标签的更多细节。 他还有两个很有用的参数,很多时候我们并没有使用过,甚至不知道。因为在我们的印象里面,浏览器按照写标签的顺序来依次加载,同时阻塞页面,这似乎是天经地义的,但其实有一些场景我们还是需要用到异步加载,异步执行的,这是就不会阻塞浏览器解析html给用户了,那么就不会显示白屏了,此时只会浏览器一直显示加载状态,但是页面已经呈现给用户了。 * * * * * ### 思考 **如何实现js非阻塞、并行加载,甚至能保持执行顺序呢?** 对于外部js我们的理解是“按照书写顺序依次加载并执行”(并且默认会阻塞浏览器的继续解析和渲染),虽然是这样的,但其实浏览器实际处理时不是这样的,不过我们平时这么说也没有错,但是最好还是要理解浏览器究竟是怎么做的。(如果依次/串行加载资源那效率太低了) 浏览器是可以并行加载资源的,而网络资源加载速度可能不同,那么如果是并行加载资源,就不可能是按顺序的加载完成了,那么浏览器是怎么保证“按顺序执行”呢?其实这里有一个机制,我们暂时称**X机制**,X机制能够保证浏览器最大效率的并行加载资源,但是资源加载完毕后并不立即被解析执行,而是被浏览器引擎“缓存”着了,直到最前依赖的加载完才会解析执行,比如同时加载1,2,3三个资源,2先返回,缓存着,3接着返回,继续缓存着,1返回,由于1是最前依赖,所以执行1,2已经返回了,直接执行,3同理,否则等待返回。也就是说,X机制在并行加载资源时,**保证JS执行顺序和书写顺序/依赖顺序一致,尽管它们的加载顺序不是一致的。** 即:浏览器要能够实现js非阻塞、并行加载,并且能保持执行顺序和书写顺序一致。 不过我们平常并不需要知道这个机制,这是浏览器内部的机制,**对我们来说,按照书写顺序依次加载并执行”这样说也是没错的,至少我们看到的代码就是这样运行的。** ~~~text 阮老师请教下,外部js的加载方式是:“按照书写顺序依次加载并执行”(并且默认会阻塞浏览器的继续解析和渲染),但其实浏览器并不是串行加载资源的,而是并行加载的(最大6个资源一起加载),如果是并行加载资源的,那么资源返回时间可能都不一样,那怎么保证加载完成按照顺序执行呢。这里面有些细节还不是很明白,不知道阮老师有没有写过类似的文章。谢谢。 不是“一起加载”,是“一起下载”。 (下载不等同于加载;下载:单纯的网络请求,文件下载。加载:包含下载和下载后的其它处理。) ~~~ > 或许这是我猜想的,对于普通的外部js,浏览器就是串行加载,依次加载并执行的。 [javascript在html中的加载顺序 - CBDoctor - 博客园](https://www.cnblogs.com/CBDoctor/p/3745246.html) [浅析JavaScript执行顺序 « 邵珠庆の博客](http://shaozhuqing.com/?p=2756) [Javascript 装载和执行 | | 酷 壳 - CoolShell](https://coolshell.cn/articles/9749.html) > 对于IE来说,这个标签会让IE并行下载js文件,并且把其执行hold到了整个DOM装载完毕(DOMContentLoaded),多个defer的\<script\>在执行时也会按照其出现的顺序来运行。**(就是我上面说的X机制)** [JavaScript并发下载 | 四火的唠叨](http://www.raychase.net/123) [js的并行加载与顺序执行 - Hello.NET - 博客园](https://www.cnblogs.com/mfc-itblog/p/5938851.html) [headjs实现网站并行加载但顺序执行JS_javascript技巧_脚本之家](http://www.jb51.net/article/98595.htm) [js并行加载,顺序执行 - CSDN博客](http://blog.csdn.net/yemou_blog/article/details/50292339) > 并行加载与顺序执行 [JS异步加载的三种方式 - CSDN博客](http://blog.csdn.net/l522703297/article/details/50754695) [JS模块加载器加载原理是怎么样的? - 知乎](https://www.zhihu.com/question/21157540) > 原理我用大白话告诉你,通过一个入口,加载所有依赖,每次完成一次onload,查看是否还有未完成和未开始的脚本,一直到最后一个加载完毕(加载过程是不阻塞的,当然也还是会分级)之后,按照依赖关系依次执行,结果保存到cache,下次再跑,就不执行了,确保factory只执行一次。 [浏览器加载 JS 文件的先后顺序同具体的解析和执行有什么关系? - 知乎](https://www.zhihu.com/question/20531965) [前端 - 浏览器的并行加载机制是怎样的? - SegmentFault](https://segmentfault.com/q/1010000008784923) * * * * * [【requireJS源码学习03】细究requireJS的加载流程 - 叶小钗 - 博客园](https://www.cnblogs.com/yexiaochai/p/3650379.html) > 执行checkLoaded方法,这里会开始递归的检查模块是否加载结束,一定要在主干模块depCount为0 时候才会执行其回调。(每次onload都检查就能知道是否全部加载完毕了) [requireJS原理解析 - CSDN博客](http://blog.csdn.net/cde7070/article/details/65935888) requireJS是通过动态创建\<script\>标签的方式加载资源的,这样虽然可以以 **异步不阻塞并行的方式加载文件** ,但是却无法保持执行顺序。不过也没事,每次完成一次onload就检查是否加载完全部依赖,等到全部加载完成后才执行回调,这样也是没问题的。但是对于不标准的AMD库,那就没办法了,毕竟标准的库都是define,factory在回调里面,所以即使加载完成也没有执行factory,但是非标准的就不同了,只要加载了就会立即执行,不知道对于这种情况是怎么处理的。(关于模块化怎么支持的可以看这里,[js.cookie.js](https://github.com/js-cookie/js-cookie/blob/master/src/js.cookie.js)、[layer.js](https://github.com/sentsin/layer/blob/master/src/layer.js)) 估计对于非标准的AMD文件,requireJS不是采用的\<script\>标签的方式加载资源的吧。 由于requireJS的加载机制,并行加载,加载就执行脚本(脚本文件执行了而是factory),所以执行顺序是得不到保障的,保障的只是factory执行顺序和入口的回调。所以这里就有一个问题需要注意,**模块里面factory最好不要有单独直接部分,即使有,一定不要有依赖,否则就会出现依赖不存在的问题,所以一定要注意这点。** * * * * * ### 扩展 试想一下如果某个cdn坏了,那么页面会一直显示加载白屏吗? 做个试验就知道了,其实浏览器都有一个超时时间,如果超时没有返回,那么会加载失败,浏览器就会放弃加载这个js,直接跳过了,如果之后的js依赖这个文件,那么就有可能会出问题了。 ``` 加载失败,报错: GET http://h.qhimg.com/js/iwt.js net::ERR_CONNECTION_TIMED_OUT ``` 至于css的加载,可以参考link标签,link标签没有这些复杂的其他参数,它就很简单,很单纯,那就是按标签书写顺序依次加载,并且同步阻塞的,所以页面重要样式一定要写在head里面,不然可能会出现用户看到一个坏的页面,然后突然又好了,所以样式一般都放在头部,这没有什么可说的,对于cdn坏掉还是跟js一样的,有一个超时时间,加载失败接。 不过要注意上面有一些表现在不同浏览器上面可能是不同的。 #### 名词解释: **同步(sync),是串行,阻塞的** **异步(Asynchronous ),是并行,非阻塞的** > 这两组词往往都是成组出现的 ## 参考 ``` 处理脚本和样式表的顺序 脚本   网络的模型是同步的。网页作者希望解析器遇到 <script> 标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。此模型已经使用了多年,也在 HTML4 和 HTML5 规范中进行了指定。作者也可以将脚本标注为“defer”,这样它就不会停止文档解析,而是等到解析结束才执行。HTML5 增加了一个选项,可将脚本标记为异步,以便由其他线程解析和执行。 预解析   Webkit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。 样式表   另一方面,样式表有着不同的模型。理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。这看上去是一个非典型案例,但事实上非常普遍。Firefox 在样式表加载和解析的过程中,会禁止所有脚本。而对于 Webkit 而言,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。 ``` - [前端文摘:深入解析浏览器的幕后工作原理 - 梦想天空(山边小溪) - 博客园](http://www.cnblogs.com/lhb25/p/how-browsers-work.html#Webkit_CSS_parser) - [HTML <link> 标签](http://www.w3school.com.cn/tags/tag_link.asp) - [HTML \<script\> 标签](http://www.w3school.com.cn/tags/tag_script.asp) - [JavaScript 使用](http://www.w3school.com.cn/js/js_howto.asp) - [深入浅出JavaScript (五) 详解Document.write()方法](http://blog.csdn.net/jiaolong724/article/details/8532828) - [让我们再聊聊浏览器资源加载优化](http://kb.cnblogs.com/page/211942/) - [浏览器允许的并发请求资源数是什么意思?](https://www.zhihu.com/question/20474326) - [“不同浏览器对于同一域名的并发获取(加载)资源数是有限的” - 明明是悟空](http://www.tuicool.com/articles/maU7B3a) - [详解浏览器最大并发连接数](http://www.iefans.net/liulanqi-zuida-bingfa-lianjieshu/) - [现代浏览器的工作原理](http://blog.jobbole.com/12749/) - [前端必读:浏览器内部工作原理](http://kb.cnblogs.com/page/129756/) - [浏览器中Javascript的加载原理](http://blog.csdn.net/u012758088/article/details/54572673) - [浏览器加载和渲染原理分析 ](http://blog.chinaunix.net/uid-24603373-id-220310.html) - [html页面加载原理和浏览器应用程序交互原理](http://blog.csdn.net/ndcnb/article/details/51473667) - [JS模块加载器加载原理是怎么样的?](https://www.zhihu.com/question/21157540) - [关于浏览器处理新加入的JS,CSS方式原理](http://www.iteye.com/problems/7598) - [JS \<script\>标签详解](http://www.itxueyuan.org/view/6611.html) - [浏览器中JavaScript执行原理](http://www.cnblogs.com/inJS/p/4912843.html) - [动态加载script文件的两种方法](http://www.jb51.net/article/40623.htm) - [js在html中的加载执行顺序](http://www.cnblogs.com/lindaWei/archive/2012/04/05/2433454.html) - [前端必读:浏览器内部工作原理](http://kb.cnblogs.com/page/129756/) - [浅谈Html的内容加载及JS执行顺序](http://www.2cto.com/kf/201507/415672.html) - [网站性能优化-将Script放到HTML文件中尽量靠近尾部原理](http://blog.csdn.net/spring21st/article/details/6192831) - [浏览器加载、解析、渲染的过程](http://blog.csdn.net/xiaozhuxmen/article/details/52014901) - [浏览器渲染页面过程](http://www.cnblogs.com/chenlogin/p/5221562.html) - [浏览器~加载,解析,渲染](http://www.jianshu.com/p/e141d1543143) - [浅析浏览器渲染页面过程](http://blog.csdn.net/yuhk231/article/details/53581212) - [面试的时候人家问浏览器渲染过程 这个怎么答? ](https://zhidao.baidu.com/question/395315056469060285.html) - [我的前端进阶学习(一)—— 模块化开发](http://www.imooc.com/m/wap/article/detail.html?aid=6911) - [Javascript模块化编程(一):模块的写法](http://www.ruanyifeng.com/blog/2012/10/javascript_module.html) - [Javascript模块化编程(二):AMD规范](http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html) - [Javascript模块化编程(三):require.js的用法](http://www.ruanyifeng.com/blog/2012/11/require_js.html) - [浏览器加载 CommonJS 模块的原理与实现](http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html) - [SeaJS 所为何](http://cyj.me/why-seajs/zh/) - [RequireJS 其一](http://cyj.me/why-seajs/requirejs/) - [JS模块化](http://www.kancloud.cn/xiak/quanduan/254621) - [DNS 原理入门](http://www.ruanyifeng.com/blog/2016/06/dns.html) - [浏览器工作原理(浏览器内核 即渲染)](http://blog.sina.com.cn/s/blog_6fb22e5b010117s0.html) - [浏览器的工作原理:现代网络浏览器幕后揭秘](http://blog.sina.com.cn/s/blog_6deafdb2010146bt.html) - [事件DOMContentLoaded和load的区别](http://www.jianshu.com/p/d851db5f2f30) - [HTML 5 \<script\> async 属性](http://www.w3school.com.cn/tags/att_script_async.asp) > 如果既不使用 async 也不使用 defer:**在浏览器继续解析页面之前,立即读取并执行脚本** - [JS学习笔记(一)——JS的阻塞特性 - MeteorSeed - 博客园](http://www.cnblogs.com/MeteorSeed/articles/2283629.html) > JS具有阻塞特性,当浏览器在执行js代码时,不能同时做其它事情,即\<script\>每次出现都会让页面等待脚本的解析和执行(不论JS是内嵌的还是外链的),JS代码执行完成后,才继续渲染页面。 - [现代浏览器性能优化-JS篇 · Issue #2 · GeoffZhu/geoffzhu.github.io](https://github.com/GeoffZhu/geoffzhu.github.io/issues/2) - [现代浏览器性能优化-CSS篇 - 掘金](https://juejin.im/post/5a461f006fb9a0450408358f) - [前端工程师必备——浏览器渲染原理详解!](https://www.toutiao.com/a6506267555328426499/?tt_from=weixin&utm_campaign=client_share&timestamp=1514909819&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1) ### 其它 正因为“按照书写的script标签的顺序,依次加载并执行”,那么利用这个特性,可以实现,js脚本获取自身路径,[layer mobile](https://github.com/sentsin/layer/blob/2.x/src/mobile/layer.js)就是这么做的 里面有类似于这样的代码: ~~~javascript var js = document.scripts, script = js[js.length - 1], jsPath = script.src; var path = jsPath.substring(0, jsPath.lastIndexOf("/") + 1); //如果合并方式,则需要单独引入layer.css if(script.getAttribute('merge')) return; document.head.appendChild(function(){ var link = doc.createElement('link'); link.href = path + 'need/layer.css?2.0'; link.type = 'text/css'; link.rel = 'styleSheet' link.id = 'layermcss'; return link; }()); ~~~ 关键是这个`document.scripts`,巧妙的利用了“一次加载并执行”。 那么问题来了,如果使用了模块化后,sea.js或者require.js这样的加载方式之后,那么势必就会改变这种情况,这种获取当前脚本路径的方式就不行了。 来看看,pc版的layer,也是这种方式。 好吧。 require.js其实没什么问题,sea.js就不行了,因为他是,尽可能的懒啊,尽可能的懒解析(执行),所以就会存在问题。 从layer的暴露模块支持代码就可以看出来,它不支持sea.js哦: ~~~javascript //加载方式 window.layui && layui.define ? ( layer.ready() ,layui.define('jquery', function(exports){ //layui加载 layer.path = layui.cache.dir; ready.run(layui.jquery); //暴露模块 window.layer = layer; exports('layer', layer); }) ) : ( (typeof define === 'function' && define.amd) ? define(['jquery'], function(){ //requirejs加载 ready.run(window.jQuery); return layer; }) : function(){ //普通script标签加载 ready.run(window.jQuery); layer.ready(); }() ); ~~~ 它支持三种方式:layui、require.js,普通script标签,唯独不支持sea.js,我们去社区看看sea.js支持方面的问题,果不奇然。 [layui.all 用sea js 载入会提示 css 路径不对的错误](https://fly.layui.com/jie/11504/) [请问sea如何使用layer](http://fly.layui.com/jie/7458/) > 不建议采用seajs 如果使用了sea.js那么需要自己配置脚本所在路径了,这种方式就行不通了。 [加快构建 DOM: 使用预解析, async, defer 以及 preload](http://mp.weixin.qq.com/s/CCjhMqiNNvjo-46FykN0UA) > 在过去,为了执行一个脚本,HTML 的解析必须暂停。只有在 JavaScript 引擎执行完代码之后它才会重新开始解析。…… [Web图片资源的加载与渲染时机](https://segmentfault.com/a/1190000010032501?v=2017082201) [前端性能优化之 DOM 篇 - 关人潮的博客 | FSUX Blog](http://fsux.me/%E9%9A%8F%E7%AC%94/%E6%9E%B6%E6%9E%84/%E6%B5%85%E8%B0%88%E5%89%8D%E7%AB%AF/2017/04/13/Front-end-performance-optimization-dom.html) [从输入 URL 到页面加载完成的过程中都发生了什么事情?](http://fex.baidu.com/blog/2014/05/what-happen/) [HTTP 缓存机制一二三 - 知乎专栏](https://zhuanlan.zhihu.com/p/29750583?group_id=901822939576557568) [漫画揭秘一个超快的渲染引擎(上)](http://mp.weixin.qq.com/s/EAZUXq7RC6W9rWhRdE-7Ew) [漫画揭秘一个超快的渲染引擎(下)](http://mp.weixin.qq.com/s/u8xUnB8nXWv_cvnVScAwoQ) [前端开发工程师必须关注的几个性能指标](http://mp.weixin.qq.com/s/JV4yRQkkvsRxgwj3gw3zkQ) [这么多前端优化点你都记得住吗?](http://mp.weixin.qq.com/s/QSakBZH5i_CfkPz4RHeFKg) [如何打造一个全满分网站](http://mp.weixin.qq.com/s/NR_9y83sja-LjVzBs4viwA) [如何打造一个安全满分网站](http://mp.weixin.qq.com/s/5CdnIN3eeAkux7rGJHjiyQ) [浏览器「内核」都做了些什么?](https://mp.weixin.qq.com/s/JwUoIM2jcxbOd_5Mox4r-w) [Webpack HMR 原理解析](http://mp.weixin.qq.com/s/eHFAYzX9GnxcixIjeMqojQ) [Web静态资源缓存及优化](https://zhuanlan.zhihu.com/p/30780216) [从Chrome源码看浏览器如何加载资源 – 人人网FED博客](https://fed.renren.com/2017/10/29/chrome-fetch-resource/) [文字编码的那些事 – 人人网FED博客](https://fed.renren.com/2017/11/11/text-encode/) [浏览器:一个家族的奋斗](http://mp.weixin.qq.com/s/5mOPs9Vcl0VNq4NAjdRPAg) [火狐浏览器是如何又变快起来的?](https://mp.weixin.qq.com/s/ZTfqw_ECZCNqxifXGqfqwQ) [【年底补课】HTTP缓存机制](https://mp.weixin.qq.com/s/HlJKEXk7q-f1n8va2RLZdA) [曾是最强浏览器!Firefox是如何渐渐走向没落的_观点评论_太平洋电脑网PConline](http://pcedu.pconline.com.cn/938/9388346_all.html#content_page_2) > 作为一个非营利性组织,Mozilla的目的是让浏览器市场多姿多彩,避免一家独大。Firefox一路走来,已经出色地诠释了这个使命。 [从输入网址到浏览器呈现页面内容,中间发生了什么?](http://mp.weixin.qq.com/s/q9wDvplWysHCn_Bet8j5lA) [标准模式与怪异模式对渲染页面的影响](https://mp.weixin.qq.com/s/Y0l5YK68bi5A1KI2dhYf1w) [浏览器家族的安全反击战](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665514143&idx=1&sn=28ea209c00309e6b93d8d1f76032d7a4&chksm=80d67cdcb7a1f5ca81d8d454a98af56d58b22f6058f100e21ff30e70867ea6e3e922a4f000bf&scene=21#wechat_redirect) > 同源 & 安全:这些安全策略都是浏览器制定的,假如浏览器也不安全,我电脑的浏览器被黑客攻击了,那么怎么办? > > 没辙了,你的核心被攻占, 除非你把浏览器或者OS重装了 [HTML 文档之 Head 最佳实践](https://mp.weixin.qq.com/s/GF3HPhFrXll6eEI8crJCaw) [谈谈web前端缓存](https://www.toutiao.com/a6505280890388611597/?tt_from=weixin&utm_campaign=client_share&timestamp=1514707129&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1) [浅谈浏览器 http 的缓存机制](https://mp.weixin.qq.com/s/qSN5yaNmuMysG71229qC-Q) [让我们再聊聊浏览器资源加载优化 – 前端技术漫游指南](http://qingbob.com/let-us-talk-about-resource-load/) [Range/Content-Range与断点续传,了解一下? - 掘金](https://juejin.im/post/5acac9f5f265da239531463f) [web beacon - jvava - 博客园](https://www.cnblogs.com/jvava/p/3926539.html) * * * * * last update:2018-1-22 22:09:31