从无阻塞并行脚本加载(Lab.js)到浏览器消息模型

2,930 views

Published on

web标准化交流会第十八期上海站分享。
Jackson Tian:从无阻塞并行脚本加载(LAB.js)到浏览器消息模型

Published in: Technology, Education
1 Comment
8 Likes
Statistics
Notes
No Downloads
Views
Total views
2,930
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
39
Comments
1
Likes
8
Embeds 0
No embeds

No notes for slide

从无阻塞并行脚本加载(Lab.js)到浏览器消息模型

  1. 1. Jackson Tian/田永强 @朴灵 at SAP<br />从无阻塞脚本加载(LAB.js)到浏览器线程模型<br />5/7/2011<br />1<br />
  2. 2. Agenda<br /><ul><li>为什么script会阻塞页面解析
  3. 3. 如何实现无阻塞脚本加载
  4. 4. 依赖管理与无阻塞并行加载与顺序执行(LAB.js实现机制)
  5. 5. 无阻塞脚本加载潜在问题
  6. 6. 为什么要谈谈JavaScript线程
  7. 7. 为什么要谈谈同步与异步
  8. 8. 浏览器线程消息模型
  9. 9. 从Web Worker看浏览器的线程
  10. 10. NodeJS的单线程与多线程</li></ul>5/7/2011<br />2<br />
  11. 11. 说到无阻塞脚本加载<br /><ul><li>你不得不知道的大牛是
  12. 12. Steve Souders
  13. 13. At Google on web performance and open source initiatives.
  14. 14. http://www.stevesouders.com/</li></ul>5/7/2011<br />3<br />
  15. 15. 为什么script标签会阻塞页面解析<br /><ul><li>从Document.write说起
  16. 16. Document.write() 是魔鬼。
  17. 17. <p>task 1.</p><script>document.write(“<p>task5</p>”);</script><p>task 2.</p><p>task 3.</p><p>task 4.</p>
  18. 18. 浏览器在UI渲染上,采用队列的方式实现</li></ul>5/7/2011<br />4<br />
  19. 19. 5/7/2011<br />5<br />
  20. 20. Document.write是魔鬼<br /><ul><li>队列的基本原则
  21. 21. 先进先出原则(FIFO, First-In-First-Out)http://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97
  22. 22. Document.write破坏了原始的UI更新队列
  23. 23. 从document.write来看,JavaScript设计之初只是用来做Web页面辅助的</li></ul>5/7/2011<br />6<br />
  24. 24. 5/7/2011<br />7<br />
  25. 25. Alert也是魔鬼<br /><ul><li>alert(“I am evil, too.”);
  26. 26. 是否有人通过alert来debug JavaScript程序呢?
  27. 27. for(vari=0; i< 1000; i++) { alert(“Hi, ” + i);}
  28. 28. alert最坏的地方在于它中断整个UI线程
  29. 29. 用户体验的敌人(桌面浏览器)
  30. 30. confirm,prompt方法也是类似的</li></ul>5/7/2011<br />8<br />
  31. 31. 为什么script标签会阻塞页面解析<br /><ul><li>基于以上的历史原因,JavaScript线程与UI共用同一进程,而且JS任务优先于UI任务
  32. 32. 浏览器会在遇到Script标签的时候进行解析和执行
  33. 33. 如果Script是外链的话,还要等待下载
  34. 34. 如果页面存在多个外链脚本,下载是串行的
  35. 35. 在IE8, Firefox3.5, Safari4和Chrome2开始允许并行下载JavaScript文件,但是依然会阻塞页面的其他资源的下载</li></ul>5/7/2011<br />9<br />
  36. 36. 5/7/2011<br />Chromium解析流程<br />10<br />此图由Tapir(貘)提供,感谢他<br />
  37. 37. YSlow的35条军规<br /><ul><li>Put Scripts at the Bottom
  38. 38. http://developer.yahoo.com/performance/rules.html#js_bottom
  39. 39. 如果脚本的下载+解析+执行的时间太久,UI队列没有得到执行,页面会出现空白
  40. 40. Yahoo!建议将所有的脚本都放在</body>之前,让UI队列优先执行和显示。
  41. 41. Minimize HTTP Requests
  42. 42. http://developer.yahoo.com/performance/rules.html#num_http
  43. 43. 页面脚本过多的情况下,通过combo和compress减少请求数
  44. 44. 问题?
  45. 45. 在</body>之前存在一个较大的脚本文件需要下载和执行
  46. 46. UI在ready之后,需要较长时间等待脚本的下载和执行,在脚本ready之前,UI是出于无事件响应状态的。</li></ul>5/7/2011<br />11<br />
  47. 47. 无阻塞脚本加载<br /><ul><li>我们需要预先加载脚本,而且不阻塞UI的渲染
  48. 48. 对YSlow规则说No
  49. 49. Put Scripts at the Bottom
  50. 50. 为过往的犯错买单,看看标准和浏览器厂商都做了什么方案来补救</li></ul>5/7/2011<br />12<br />
  51. 51. Defer属性<br /><ul><li>HTML4标准为<script>标签定义了defer属性,以此声明告诉浏览器内容中不包含document.write之类破坏DOM的脚本
  52. 52. 浏览器会延迟(无阻塞)下载脚本,并按<script>脚本顺序执行。
  53. 53. 在onload事件前执行
  54. 54. 实现/支持情况
  55. 55. IE4.0
  56. 56. Firefox3.5</li></ul>5/7/2011<br />13<br />
  57. 57. Async属性<br /><ul><li>HTML5标准为<script>标签定义了async属性
  58. 58. 与defer属性相同的是脚本会无阻塞加载
  59. 59. 与defer属性不同的是脚本在加载完了立即执行,并非按照<script>标签顺序执行
  60. 60. 实现/支持情况
  61. 61. Firefox3.6
  62. 62. Opera10.5
  63. 63. Safari
  64. 64. Chrome
  65. 65. IE9.0(defer的别名?)</li></ul>5/7/2011<br />14<br />
  66. 66. 标准与实现之间的差距<br /><ul><li>动态脚本元素
  67. 67. 利用一小段脚本去创建<script>标签,实现无阻塞加载
  68. 68. var script = document.createElement('script'); script.src = 'myscript.js';var head = document.getElementsByTagName('head')[0];head.appendChild(script);</li></ul>5/7/2011<br />15<br />
  69. 69. Script标签的onload事件<br /> function loadScript(url, callback){<br />    var script = document.createElement(”script”)    script.type = “text/javascript”;<br />    if (script.readyState){  //IE        script.onreadystatechange = function(){            if (script.readyState == “loaded” ||                    script.readyState == “complete”){                script.onreadystatechange = null;                callback();            }        };    } else {  //Others        script.onload = function(){            callback();        };    }<br />    script.src = url;    document.body.appendChild(script);}<br />5/7/2011<br />16<br />
  70. 70. 无阻塞脚本加载实例<br /><ul><li>Google Analytics代码
  71. 71. http://code.google.com/intl/zh-CN/apis/analytics/docs/tracking/asyncTracking.html</li></ul>5/7/2011<br />17<br />
  72. 72. XMLHttprequest脚本注入<br /><ul><li>$.get(“script.js”, function (responseText) { var script = document.createElement(“script”); script.type=“text/javascript”; script.text = “responseText”; document.body.appendChild(script);});
  73. 73. 可以控制下载和执行
  74. 74. 该方法基于Ajax,受同源策略影响,无法使用CDN。</li></ul>5/7/2011<br />18<br />
  75. 75. XMLHttpRequestEval<br /><ul><li>$.get(“script.js”, function (responseText) {eval(responseText);});
  76. 76. 与XMLHttpRequest具有相同的优点和缺点
  77. 77. 但是,Eval is Evil。</li></ul>5/7/2011<br />19<br />
  78. 78. document.write Script Tag<br /><ul><li> document.write(“<script src=‘script.js’> </script>”);
  79. 79. 只有IE生效
  80. 80. 如果在文档流关闭之后再调用document.write(),整个DOM会被毁坏</li></ul>5/7/2011<br />20<br />
  81. 81. Script in Iframe<br /><ul><li>我也不知道怎么搞。
  82. 82. 很复杂。
  83. 83. Google都Google不到。
  84. 84. 也许是<iframe src=“#”></iframe></li></ul>5/7/2011<br />21<br />
  85. 85. 问题总结<br /><ul><li>同源策略限制(XHR inject, XHR eval,Iframe)
  86. 86. 顺序执行(动态script元素, async)
  87. 87. 浏览器支持(async, defer)
  88. 88. 并行下载与顺序执行</li></ul>5/7/2011<br />22<br />
  89. 89. 各种方法支持一览<br />5/7/2011<br />23<br />
  90. 90. 无顺序执行的要求情况下<br />5/7/2011<br />24<br />
  91. 91. 5/7/2011<br />25<br />
  92. 92. 如何控制依赖加载<br /><ul><li>通过onload事件控制?loadScript(“file1.js”, function() {loadScript(“file2.js”, function() {loadScript(“file3.js”, function() { alert(“All ready.”); }); }); });
  93. 93. 这是串行加载的</li></ul>5/7/2011<br />26<br />
  94. 94. 如何控制依赖加载<br /><ul><li>或者采用defer?<script src=“script1.js” defer></script> <script src=“script2.js” defer></script> <script src=“script3.js” defer></script>
  95. 95. 显然浏览器支持不够</li></ul>5/7/2011<br />27<br />
  96. 96. 如何控制依赖加载<br /><ul><li>更或者通过XHR动态加载,顺序执行
  97. 97. 同源策略怎么办?</li></ul>5/7/2011<br />28<br />
  98. 98. 如何控制依赖加载<br /><ul><li>在Server端组织好脚本顺序,并且combo成一个文件
  99. 99. 这是一个好主意!!!
  100. 100. 但是总有你不能combo的文件,比如本地化的资源文件</li></ul>5/7/2011<br />29<br />
  101. 101. 依赖加载≠必须顺序加载 ≠串行加载<br />5/7/2011<br />30<br />
  102. 102. 顺序执行≠立即执行<br />5/7/2011<br />31<br />
  103. 103. 有没有一种方法<br /><ul><li>能实现并行下载
  104. 104. 能实现顺序执行
  105. 105. 能没有副作用</li></ul>5/7/2011<br />32<br />
  106. 106. LAB.js<br /><ul><li>@getify
  107. 107. Kyle Simpson
  108. 108. 公司:Getify Solutions
  109. 109. LABjs被很多大网站所采用,包括新版的Twitter, Zappos, 以及Vimeo.
  110. 110. Kyle Simpson在Steve Sounders的启示下写下了LABjs这个库,用于管理并行加载和顺序执行</li></ul>5/7/2011<br />33<br />
  111. 111. LABjs的诀窍<br /><ul><li>对于Firefox/Opera,采用动态Script DOM element可以完美地实现并行下载和顺序执行
  112. 112. 对于Safari/Chrome,无法保证顺序执行,但是LABjs通过插入一个<script type=“text/cache” src=“#”>来实现。IE/Safari/Chrome浏览器会下载文件到缓存并触发onload事件,但不会执行</li></ul>5/7/2011<br />34<br />
  113. 113. <ul><li>在需要的文件下载完成之后,再次通过插入正确的type=“text/javascript”而且监听onload来完成对于顺序执行的控制
  114. 114. LABjs判断script文件是否同域文件,优先选择XHR Injection实现并行下载,顺序执行
  115. 115. Text/cache严重依赖浏览器的非标准特性</li></ul>LABjs的诀窍<br />5/7/2011<br />35<br />
  116. 116. LABjs示例<br /><ul><li>Sample Code
  117. 117. <script src="LAB.js"></script> <script>$LAB .script("http://remote/jquery.js”).wait() .script("/local/plugin1.jquery.js") .script("/local/plugin2.jquery.js").wait() .script("/local/init.js").wait(function(){ initMyPage(); }); </script></li></ul>5/7/2011<br />36<br />
  118. 118. LABjs方法<br /><ul><li>主要方法
  119. 119. Script()
  120. 120. 可以多次调用script方法来并行加载脚本
  121. 121. Wait()
  122. 122. 通过调用wait方法来保证序列中的脚本执行顺序</li></ul>5/7/2011<br />37<br />
  123. 123. LABjs API示意<br />5/7/2011<br />38<br />
  124. 124. 题外话<br /><ul><li>曾经Firefox4试图改掉动态脚本元素按顺序执行的特性,使得像Chrome/Safari一样
  125. 125. 那么,在面对CDN脚本,text/cache失效的情况下,LABjs会无可奈何地进行串行下载
  126. 126. 有链接为证:
  127. 127. http://blog.getify.com/2010/10/ff4-script-loaders-and-order-preservation/
  128. 128. http://blog.getify.com/2010/10/mozilla-labjs-the-story-unfolds/
  129. 129. http://blog.getify.com/2010/10/mozilla-labjs-part-2/</li></ul>5/7/2011<br />39<br />
  130. 130. 题外话<br /><ul><li>后来,getify赢了</li></ul>5/7/2011<br />40<br />
  131. 131. 无阻塞脚本与DOM<br /><ul><li>在脚本加载完成的时候,DOM可能
  132. 132. DOM还没有DOMContentLoaded(加载速度快或者来自cache)
  133. 133. 访问DOM是危险的
  134. 134. DOM可能已经DOMContentLoaded了
  135. 135. 绑定DOMContentLoaded事件永远也不会被触发
  136. 136. 如何判断DOM是否ready
  137. 137. DOM甚至已经Onload了(IE)</li></ul>5/7/2011<br />41<br />
  138. 138. 如何解决以上问题?<br /><ul><li>DOM还没有DOMContentLoaded(加载速度快或者来自cache)
  139. 139. 保证DOM操作注册在DOMContentLoaded事件中
  140. 140. DOM可能已经DOMContentLoaded了
  141. 141. 绑定DOMContentLoaded事件永远也不会被触发
  142. 142. 通过document.readyState == “complete”判断DOMready
  143. 143. DOM甚至已经Onload了(IE)
  144. 144. document.readyState 依然有效</li></ul>5/7/2011<br />42<br />
  145. 145. 回顾<br /><ul><li>Combo脚本文件,以减少链接数
  146. 146. 优先加载和无阻塞加载,实现界面快速响应
  147. 147. 采用LABjs做依赖管理,实现并行下载和顺序执行
  148. 148. 控制好访问DOM的代码,确保在DOMContentLoaded后执行</li></ul>5/7/2011<br />43<br />
  149. 149. 为什么要啰嗦浏览器线程<br /><ul><li>无阻塞加载讲得又不深入,还要啰嗦浏览器线程
  150. 150. 因为扯谈了这么多,目的只是将脚本的下载线程脱离UI线程而已
  151. 151. 设计JavaScript执行和UI渲染共用UI线程的人上辈子是折翼的天使,伤不起</li></ul>5/7/2011<br />44<br />
  152. 152. <ul><li>为什么JavaScript作为一门编程语言,却没有多线程?
  153. 153. 抱怨JavaScript没有多线程的程序员不是好程序员
  154. 154. 有多少人用setTimeout模拟过多线程</li></ul>JavaScript线程怨念知多少<br />5/7/2011<br />45<br />
  155. 155. JavaScript线程<br /><ul><li>JavaScript的多线程
  156. 156. 找到约 1,280,000 条结果 (用时 0.13 秒) 
  157. 157. http://www.google.com.hk/search?sourceid=chrome&ie=UTF-8&q=JavaScript+%E5%A4%9A%E7%BA%BF%E7%A8%8B</li></ul>5/7/2011<br />46<br />
  158. 158. 可是<br /><ul><li>你知道的
  159. 159. 浏览器并不是单线程
  160. 160. JavaScript是单线程 ≠ 浏览器是单线程
  161. 161. WebApp ≠ JavaScript App
  162. 162. WebApp = 浏览器 + JavaScript + CSS + HTML + Images + …. + API
  163. 163. 所以
  164. 164. JavaScript的单线程是有原因的</li></ul>5/7/2011<br />47<br />
  165. 165. 浏览器线程知多少<br /><ul><li>UI渲染线程/JavaScript执行线程
  166. 166. 资源下载线程(JavaScript, CSS, Image, Object)
  167. 167. Ajax线程
  168. 168. Web Worker线程
  169. 169. 还有?欢迎补充</li></ul>5/7/2011<br />48<br />
  170. 170. 前端,你伤不起<br /><ul><li>在所有下载线程中
  171. 171. 为什么CSS文件加载不会block页面?
  172. 172. 为什么图片加载不会block页面?
  173. 173. 为什么flash加载不会block页面?
  174. 174. 为什么ajax还有同步和异步之分?
  175. 175. 为什么JavaScript文件会block UI?</li></ul>5/7/2011<br />49<br />
  176. 176. 关于AJAX的同步异步<br /><ul><li>同步与异步的差别来自于AJAX的盛行
  177. 177. 同步Ajax会锁住整个浏览器,直到请求完成
  178. 178. 而异步请求则无影响,但是接收结果必须通过回调函数</li></ul>5/7/2011<br />50<br />
  179. 179. 被各种ajax库宠坏的孩子们<br /><ul><li>看看最原始的Ajax吧
  180. 180. varhttpRequest;if (window.XMLHttpRequest) { // Mozilla, Safari, ... httpRequest = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE httpRequest= new ActiveXObject("Microsoft.XMLHTTP"); }httpRequest.onreadystatechange = function(){ if (httpRequest.readyState === 4) {</li></ul> if (httpRequest.status === 200) { // perfect! } else { // there was a problem with the request, // for example the response may be a 404 (Not Found) // or 500 (Internal Server Error) response codes}<br /> } else {<br />     // still not ready<br /> }};httpRequest.open('GET', ‘http://url', true);httpRequest.send(null);<br />5/7/2011<br />51<br />
  181. 181. 5/7/2011<br />异步Ajax模型<br />52<br />
  182. 182. 5/7/2011<br />同步Ajax模型<br />53<br />
  183. 183. 消息模型与事件驱动<br /><ul><li>在所有的浏览器线程之间的交互,是通过消息来传递的
  184. 184. 我们唯一能控制的线程是UI线程
  185. 185. 其余的线程我们只能通过侦听事件消息才能得到数据
  186. 186. 所有的事件handler都由浏览器来做事件驱动</li></ul>5/7/2011<br />54<br />
  187. 187. 消息编程模型 Vs. 多线程编程<br />消息模型是构建在多线程的基础之上的<br />在JavaScript的领域里(包括NodeJS), 多线程的细节被隔离<br />应用里的多线程操作通过事件驱动和消息传递暴露给程序员<br />鼓吹消息模型而鄙视多线程模型的程序员不是好的工程师<br />5/7/2011<br />55<br />
  188. 188. 从Web Worker看浏览器的线程<br /><ul><li>将JavaScript执行线程脱离UI线程
  189. 189. Web Worker的线程也可以控制,但是交互依然是消息</li></ul>5/7/2011<br />56<br />
  190. 190. 5/7/2011<br />浏览器是最成熟的消息模型应用<br />异步Ajax<br />各种资源的下载消息通知<br />浏览器原生事件驱动<br />UI事件驱动<br />网络事件驱动<br />57<br />
  191. 191. 5/7/2011<br />消息模型的优势<br />编程模型简化<br />多线程框架由底层实现和托管,我们只负责调用API启动线程,发送消息和侦听消息<br />对编程人员隔离多线程的烦恼<br />程序耦合性低<br />程序代码属于被trigger的状况,不耦合于目标代码<br />我讨厌异步特有的callback编程,但是我喜欢消息模型<br />原生的AOP编程<br />58<br />
  192. 192. 5/7/2011<br />消息模型的弱势<br />无法手动构造多线程实现并行和异步<br />严重依赖应用的支持<br />以web worker和websocket为例,只有浏览器支持的情况下,才能享受到消息模型的好处<br />事件驱动依赖应用实现<br />可以手动触发自定义事件,但是对于特殊事件无法触发<br />59<br />
  193. 193. NodeJS是一个跑在Server端的浏览器<br />NodeJS架构<br />浏览器架构(Chrome)<br />5/7/2011<br />60<br />
  194. 194. 5/7/2011<br />未来世界也许会是这样的<br />它也许是浏览器<br />它也许是NodeJS<br />它也许什么应用都不是,但是应用都跑在这上面<br />61<br />
  195. 195. 5/7/2011<br />为何JScript在服务端会死掉?<br />var objConn = Server.CreateObject("ADODB.Connection");<br /> // open our ADO connection and Execute the SQL command<br /> objConn.Open("dsn=helpdesk", "sa", "");<br /> objConn.Execute(strCommandText);<br /> var returnErrCode = <br /> objConn.Execute("SELECT @@ERROR as errorCode").GetString();<br /> objConn.Close();<br /> objConn = null;<br />62<br />
  196. 196. 5/7/2011<br />JScript Vs. NodeJS<br />JScript之死<br />摒弃了JavaScript在浏览器中的异步机制<br />完全无视消息模型<br />无多线程支持<br />在Server端,表现与PHP和ASP无异<br />NodeJS之生<br />延续JavaScript在浏览器端赖以生存的特性<br />异步,消息模型,事件驱动<br />向程序员屏蔽多线程<br />63<br />
  197. 197. 5/7/2011<br />结语<br />Block UI的JavaScript不是好JavaScript<br />无消息模型,不JavaScript<br />无事件驱动,不JavaScript<br />浏览器是多线程的,JavaScript是单线程<br />上一条适用于NodeJS<br />64<br />
  198. 198. References<br /><ul><li>http://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/
  199. 199. http://www.blog.highub.com/mobile-2/mobile-safari-scripts-loading-without-blocking/
  200. 200. http://blogs.sitepoint.com/non-blocking-async-defer/</li></ul>5/7/2011<br />65<br />
  201. 201. 5/7/2011<br />Thanks<br />66<br />

×