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

3,756 views

Published on

Published in: Technology, Design
1 Comment
9 Likes
Statistics
Notes
  • Thanks for sharing.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
3,756
On SlideShare
0
From Embeds
0
Number of Embeds
898
Actions
Shares
0
Downloads
45
Comments
1
Likes
9
Embeds 0
No embeds

No notes for slide

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

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

×