您的位置:新葡亰496net > 新葡亰官网 > 新葡亰496netJS相关概念,操作费用到底高在何地

新葡亰496netJS相关概念,操作费用到底高在何地

发布时间:2019-12-01 01:16编辑:新葡亰官网浏览(166)

    DOM 操作成本到底高在哪儿?

    2018/04/09 · 基础技术 · DOM

    原文出处: palmerye   

    从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM。尤其是React、vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQuery时代提供的强大便利地操作DOM的API在前端工程里用的越来越少。刨根问底,这里说的成本,到底高在哪儿呢?

     

    1:javascript的诞生

    • 1990年底,欧洲核能研究组织(CERN)科学家Tim Berners-Lee,在全世界最大的电脑网络——互联网的基础上,发明了万维网(World Wide Web),从此可以在网上浏览网页文件。最早的网页只能在操作系统的终端里浏览,也就是说只能使用命令行操作,网页都是在字符窗口中显示,这当然非常不方便。

    • 1992年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。

    • 1994年10月,NCSA的一个主要程序员Marc Andreessen联合风险投资家Jim Clark,成立了Mosaic通信公司(Mosaic Communications),不久后改名为Netscape。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。

    • 1994年12月,Navigator发布了1.0版,市场份额一举超过90%。

    • 1995年,Netscape公司雇佣了程序员Brendan Eich开发这种网页脚本语言。Brendan Eich有很强的函数式编程背景,希望以Scheme语言(函数式语言鼻祖LISP语言的一种方言)为蓝本,实现这种新语言。

    • 1995年5月,Brendan Eich只用了10天,就设计完成了这种语言的第一版。

    • 1995年12月4日,Netscape公司与Sun公司联合发布了JavaScript语言。

    • 1996年3月,Navigator 2.0浏览器正式内置了JavaScript脚本语言。

    1. CSS和JS在网页中的放置顺序是怎样的?

    1. css放在head标签内,防止渲染时出现白屏
    2. js放在最后body尾部,因为js一般会涉及到一些DOM操作,所以要等全部的dom元素都加载完再加载js,js方法执行时,有可能会阻塞页面加载,如果把js放在前面可能会造成白屏的现象。

    通过网络模块加载到HTML文件后渲染引擎渲染流程如下,这也通常被称作关键渲染路径(Critical Rendering Path):

    新葡亰496netJS相关概念,操作费用到底高在何地。简单介绍JavaScript的发展历史

    JavaScript因互联网而生,回顾它的历史要从浏览器的历史讲起。

    • 1990年底,欧洲核能研究组织科学家Tim Berners-Lee发明了万维网(World Wide Web),但只能在操作系统的终端里浏览和操作,非常不方便。
    • 1992年底,美国国家超级电脑应用中心开发了人类历史上第一个浏览器Mosaic,从此网页可以在图形界面的窗口浏览。
    • 1994年10月,NCSA的主要程序员Marc Andreessen联合风险投资家Jim Clark,成立了Mosaic通信公司,后改名为Netscape。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。
    • 1994年12月,Navigator发布了1.0版,该公司很快发现浏览器需要一种可嵌入网页的脚本语言来控制浏览器行为。
      管理层对这种浏览器脚本语言的设想是:功能够用,语法简单,容易学习。当时就职于Netscape的Brendan Eich着手计划于1995年2月发布的Netscape Navigator2开发LiveScript脚本语言,那年正逢Sun公司的Java语言问世,市场推广活动非常成功,为赶在发布日期前完成LiveScript开发,Netscape公司与Sun公司建立了开发联盟。
    • 1995年5月,Brendan Eich只用了10天,就设计完成了LiveScript第一版,所以这门语言比较随意。
    • 1995年12月,Netscape公司与Sun公司达成协议,后者允许将这种语言叫做JavaScript,于是Netscape在二代浏览器问世前顺利蹭到了java的热度,场面一度十分火爆。
    • 1996年3月,Navigator 2.0浏览器正式内置了JavaScript脚本语言。
    • 1996年8月,微软模仿JavaScript开发了一种相近的语言,取名为JScript。
    • 1996年11月,Netscape公司决定将JavaScript提交给国际标准化组织ECMA,希望JavaScript能够成为国际标准,以此抵抗微软。
    • 1997年7月,ECMA组织发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript。这个版本就是ECMAScript 1.0版。ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现。日常场合这两个词是可以互换的。
    • 1998年6月,ECMAScript 2.0版发布。
    • 1999年12月,ECMAScript 3.0版发布,成为JavaScript的通行标准,得到了广泛支持。
    • 2007年10月,ECMAScript 4.0版草案发布,对3.0版做了大幅升级,预计次年8月发布正式版本。
    • 2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激进,ECMA开会决定,中止ECMAScript 4.0的开发(即废除了这个版本),将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为ECMAScript 5。
    • 2009年12月,ECMAScript 5.0版正式发布。
    • 2011年6月,ECMAscript 5.1版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)。到了2012年底,所有主要浏览器都支持ECMAScript 5.1版的全部功能。
    • 2013年3月,ECMAScript 6草案冻结,不再添加新功能。
    • 2013年12月,ECMAScript 6草案发布。
    • 2015年6月,ECMAScript 6正式发布,并且更名为“ECMAScript 2015”。
    • 2016年6月,《ECMAScript 2016标准》发布。

    什么是DOM

    Document Object Model 文档对象模型

    什么是DOM?可能很多人第一反应就是div、p、span等html标签(至少我是),但要知道,DOM是Model,是Object Model,对象模型,是为HTML(and XML)提供的API。HTML(Hyper Text Markup Language)是一种标记语言,HTML在DOM的模型标准中被视为对象,DOM只提供编程接口,却无法实际操作HTML里面的内容。但在浏览器端,前端们可以用脚本语言(JavaScript)通过DOM去操作HTML内容。

    那么问题来了,只有JavaScript才能调用DOM这个API吗?

    答案是NO

    Python也可以访问DOM。所以DOM不是提供给Javascript的API,也不是Javascript里的API。

    PS: 实质上还存在CSSOM:CSS Object Model,浏览器将CSS代码解析成树形的数据结构,与DOM是两个独立的数据结构

    2:浏览器的渲染机制

    1:DOM:Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。
    2:CSSOM:CSS Object Model,浏览器将CSS解析成树形的数据结构,简称CSSOM。
    3:Render Tree: DOM和CSSOM合并后生成Render Tree
    4:Layout: 计算出Render Tree每个节点的具体位置。
    5:Painting:通过显卡,将Layout后的节点内容分别呈现到屏幕上。

    2. 解释白屏和FOUC

    白屏现象:

    1. 浏览器需要加载html文件和所有css文件后,构造了渲染树后,再把树节点渲染在屏幕上。如果暂时加载不到css文件,则会阻塞渲染过程,造成白屏现象。
    2. 如果把样式放在底部,对于IE浏览器,在某些场景下(新窗口打开,刷新等)页面会出现白屏,而不是内容逐步展现。
    3. 如果使用 @import 标签(使用@import标签引入样式文件的情况下,会等待html文件加载完成后才加载css文件),即使 CSS 放入 link, 并且放在头部,也可能出现白屏。

    FOUC(Flash of Unstyled Content) 无样式内容闪烁:

    1. 如果把样式放在底部,对于IE浏览器,在某些场景下(点击链接,输入URL,使用书签进入等),会出现 FOUC 现象(逐步加载无样式的内容,等CSS加载后页面突然展现样式).对于 Firefox 会一直表现出 FOUC .

    1. 构建DOM树(DOM tree):从上到下解析HTML文档生成DOM节点树(DOM tree),也叫内容树(content tree);
    2. 构建CSSOM(CSS Object Model)树:加载解析样式生成CSSOM树;
    3. 执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);
    4. 构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree);
    5. 布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;
    6. 绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成;

    简述网页的渲染机制

    1. 解析HTML标签,构建DOM树
    2. 解析CSS标签,构建CSSOM树
    3. 把DOM和CSSOM组合成渲染树
    4. 在渲染树的基础上布局,计算每个节点的几何结构
    5. 把每个节点绘制到屏幕上

    浏览器渲染过程

    讨论DOM操作成本,肯定要先了解该成本的来源,那么就离不开浏览器渲染。

    这里暂只讨论浏览器拿到HTML之后开始解析、渲染。(怎么拿到HTML资源的可能后续另开篇总结吧,什么握握握手啊挥挥挥挥手啊,万恶的flag…)

    1. 解析HTML,构建DOM树(这里遇到外链,此时会发起请求)
    2. 解析CSS,生成CSS规则树
    3. 合并DOM树和CSS规则,生成render树
    4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
    5. 绘制render树(paint),绘制页面像素信息
    6. 浏览器会将各层的信息发送给GPU,GPU将各层合成(composite),显示在屏幕上

    3:样式、JS 在 HTML 中如何放置?

    script标签最好放在</body>标签的前面,因为放在所有body中的标签后面就不会出现网页加载时出现空白的情况,可以持续的给用户提供视觉反馈,同时在有些情况下,会降低错误的发生。而css标签应该放在<head></head>标签之间,因为如果放在</body>标签的前面,那么当DOM树构建完成了,渲染树才构建,那么当渲染树构建完成,浏览器不得不再重新渲染整个页面,这样造成了资源的浪费。效率也不高。如果放在<head></head>之间,浏览器边构建边渲染,效率要高的多。

    3.async和defer的作用是什么?有什么区别

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

    没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

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

    有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

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

    有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

    在有 async 的情况下,JavaScript 脚本一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果 JavaScript 脚本前后有依赖性,使用 async 就很有可能出现错误。


    ![](https://upload-images.jianshu.io/upload_images/5979792-da9339f37371bfe8.jpg)
    
    image
    
    
    
    为了更友好的用户体验,浏览器会尽可能快的展现内容,而不会等到文档所有内容到达才开始解析和构建/布局渲染树,而是每次处理一部分,并展现在屏幕上,这也是为什么我们经常可以看到页面加载的时候内容是从上到下一点一点展现的。
    

    CSS和JS在网页中的放置顺序是怎样的?

    1. css放入head中,link引入样式表和写在head內的style标签里都可以,也可以直接在元素中写入样式,但注意要放在js脚本之前。

    2. js放置位置:

    • 放入body底部,</body>之前。
    • 放入head中同时使用defer或async来延迟或异步加载js。
    • 使用creatElement动态生成但要注意加载顺序。
    • 用ajax加载。

    原因:当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,不仅要等到文档中JS文件加载完毕还要等待解析执行完毕,才会继续HTML的渲染。原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因,即无论当前JS代码是内嵌还是在外部文件中,页面的下载和渲染都必须停下来等待脚本执行完成,JS执行过程越久,浏览器等待响应用户输入的时间就越长,所以尽量把JS放在底部、设置script标签的defer或async属性、合并脚本等方法可起到性能优化效果。

    1.构建DOM树

    <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
        <title>Critical Path</title>
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
      </body>
    </html>

    无论是DOM还是CSSOM,都是要经过Bytes → characters → tokens → nodes → object model这个过程。

    新葡亰496net 1

    新葡亰496netJS相关概念,操作费用到底高在何地。DOM树构建过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

    4:白屏和 FOUC

    1:白屏
    IE 会出现白屏现象,这是因为,其等待页面组件包括样式表全部加载完成后才呈现整个页面。若样式表放在页面底部,将会出现白屏。——样式表在页面中位置并不影响页面中组件的下载时间,但是会影响页面的呈现。
    在IE中,将样式表放在文档底部公导致白屏问题的情形有以下几种:
    一、在新窗口中打开时
    二、重新加载时
    三、作为主页打开时
    2:什么是FOUC(文档样式短暂失效)?
    如果使用import方法对CSS进行导入,会导致某些页面在Windows 下的Internet Explorer出现一些奇怪的现象:以无样式显示页面内容的瞬间闪烁,这种现象称之为文档样式短暂失效(Flash of Unstyled Content),简称为FOUC。
    原因大致为:
    1,使用import方法导入样式表。
    2,将样式表放在页面底部
    3,有几个样式表,放在html结构的不同位置。
    其实原理很清楚:当样式表晚于结构性html加载,当加载到此样式表时,页面将停止之前的渲染。此样式表被下载和解析后,将重新渲染页面,也就出现了短暂的花屏现象。
    为了很好的避免白屏和FOUC问题,请遵循以下规则:
    使用LINK标签将样式表放在文档的HEAD中

    4.简述网页的渲染机制

    1. 解析html构建DOM树
    2. 解析CSS构建CSSOM树
    3. 把DOM和CSSOM组合成渲染树(Render Tree)
    4. 在渲染树的基础上进行布局,计算每个节点的几何结构(Layout Tree)
    5. 把每个节点绘制到屏幕上(Painting)

    新葡亰496net 2

    浏览器渲染.png

    新葡亰496net 3

    关键渲染路径.png

    为了更友好的用户体验,浏览器会尽可能快的展现内容,而不会等到文档所有内容到达才开始解析和构建/布局渲染树,而是每次处理一部分,并展现在屏幕上,这也是为什么我们经常可以看到页面加载的时候内容是从上到下一点一点展现的。

    流程图:

    新葡亰496net 4

    Webkit渲染引擎流程

    新葡亰496net 5

    Gecko渲染引擎流程

    Webkit浏览器和Gecko浏览器渲染流程大致相同,不同的是:

    1. Webkit浏览器中的渲染树(render tree),在Gecko浏览器中对应的则是框架树(frame tree),渲染对象(render object)对应的是框架(frame);
    2. Webkit中的布局(Layout)过程,在Gecko中称为回流(Reflow),本质是一样的,后文会解释回流的另一层含义–重新布局;
    3. Gecko中HTML和DOM树中间多了一层内容池(Content sink),可以理解成生成DOM元素的工厂。

    解释白屏和FOUC

    1. 白屏:
    • 对IE来说,把样式放在底部时,在某些场景下(如打开新窗口/刷新页面等)页面会出现白屏,而不是内容逐步展现。
    • 如果使用@import标签,即使将CSS写入外部样式表由link引入并放在头部,也可能出现白屏。
    • 把js文件放入页面顶部而未使用defer或async延迟或异步加载js文件,从而阻塞html与css的加载也会导致白屏。
    • 另外:Chrome会同时加载html和css分别构建DOM树和CSSOM树,等到二者都构建完成后再绘制渲染树,然后显示出页面,当外部css样式表设置了延时或者加载的时间比较久时就会显示出白屏,与浏览器的渲染机制有关。
    1. FOUC:
      Flash of unsettled content:无样式内容闪烁。对IE来说,把样式放在底部时,在某些场景下(如点击链接、输入URL、使用书签进入等)页面会出现FOUC现象,具体表现为逐步加载无样式的内容,等CSS加载完成后页面突然展现样式;对Firefox来说会先显示已加载的html内容,再逐步加载无样式内容,等css全部加载完成后页面突然展现样式,所以Firefox会一直表现出FOUC。

    2.构建CSSOM树

    上述也提到了CSSOM的构建过程,也是树的结构,在最终计算各个节点的样式时,浏览器都会先从该节点的普遍属性(比如body里设置的全局样式)开始,再去应用该节点的具体属性。还有要注意的是,每个浏览器都有自己默认的样式表,因此很多时候这棵CSSOM树只是对这张默认样式表的部分替换。

    5:repaint和 reflow

    开发一个页面时,不可避免的需要进行repaint和reflow。也就只有古来的静态页面才会不存在repaint和reflow。repaint主要是针对某一个DOM元素进行的重绘,reflow则是回流,针对整个页面的重排。字面意思来说:repaint就是重绘,reflow就是回流。repaint和reflow的目的是:展示一个新的页面样貌。

    体现:
    repaint是某个DOM元素进行重绘;reflow是整个页面进行重排,也就是页面所有DOM元素渲染。

    如何触发:
    style变动造成repaint和reflow。

    不涉及任何DOM元素的排版问题的变动为repaint,例如元素的color/text-align/text-decoration等等属性的变动。

    除上面所提到的DOM元素style的修改基本为reflow。例如元素的任何涉及长、宽、行高、边框、display等style的修改。

    渲染引擎流程

    Webkit渲染引擎流程如下图:

    新葡亰496net 6

    Webkit.png

    Gecko渲染引擎流程如下图:

    新葡亰496net 7

    Gecko.png

    如上图,Webkit浏览器和Gecko浏览器渲染流程大致相同,不同的是:

    • Webkit浏览器中的渲染树(render tree),在Gecko浏览器中对应的则是框架树(frame tree),渲染对象(render object)对应的是框架(frame);
    • Webkit中的布局(Layout)过程,在Gecko中称为回流(Reflow),本质是一样的,后文会解释回流的另一层含义–重新布局;
    • Gecko中HTML和DOM树中间多了一层内容池(Content sink),可以理解成生成DOM元素的工厂。

    解析文档(parser HTML)

    解析顺序:浏览器按从上到下的顺序扫描解析文档;

    解析样式和脚本

    脚本:或许是由于通常会在JavaScript脚本中改变文档DOM结构,于是浏览器以同步方式解析,加载和执行脚本,浏览器在解析文档时,当解析到<script>标签时,会解析其中的脚本(对于外链的JavaScript文件,需要先加载该文件内容,再进行解析),然后立即执行,这整个过程都会阻塞文档解析,直到脚本执行完才会继续解析文档。就是说由于脚本是同步加载和执行的,它会阻塞文档解析,这也解释了为什么现在通常建议将<script>标签放在标签前面,而不是放在<head>标签里。现在HTML5提供defer和async两个属性支持延迟和异步加载JavaScript文件,如:<script defer src="script.js">

    改进:针对上文说的脚本阻塞文档解析,主流浏览器如Chrome和FireFox等都有一些优化,比如在执行脚本时,开启另一个进程解析剩余的文档以找出并加载其他的待下载外部资源(不改变主进程的DOM树,仅优化加载外部资源)。

    样式:不同于脚本,浏览器对样式的处理并不会阻塞文档解析,大概是因为样式表并不会改变DOM结构。

    样式表与脚本:你可能想问样式是否会阻塞脚本文件的加载执行呢?正常情况是不会的,但是存在一个问题是通常我们会在脚本中请求样式信息,但是在文档解析时,如果样式尚未加载或解析,将会得到错误信息,对于这一问题,FireFox浏览器和Webkit浏览器处理策略不同:
    当存在有样式文件未被加载和解析时,FireFox浏览器会阻塞所有脚本;
    而Webkit浏览器只会阻塞操作了改文件内声明的样式属性的脚本。

    async和defer的作用是什么?有什么区别

    async和defer用于异步或延迟加载脚本。
    没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是读到就加载并执行。也就是说脚本会阻塞其后内容的解析和执行。

    1. 作用:
    • async(异步):定义了async属性的脚本相对于页面的其他部分异步执行(同时执行),作用是不让页面等待两个或以上的脚本下载和执行,从而异步加载页面的其他内容。
    <!DOCTYPE html>
    <head>
        <title>Example HTML Page</title>
        <script type="text/javascript" async src="example1.js"></script> 
        <script type="text/javascript" async src="example2.js"></script>
    </head>
    <body>
        <!-- 这里放内容 --> 
    </body>
    </html>
    

    以上代码中evample2.js可能会在example1.js之前执行,所以确保二者之间互不依赖很重要。

    • defer(延迟):定义了defer属性的脚本会被延迟到整个页面都解析完毕后再执行。
    <!DOCTYPE html>
    <head>
        <title>Example HTML Page</title>
        <script type="text/javascript" defer src="example1.js"></script>
        <script type="text/javascript" defer src="example2.js"></script>
    </head>
    <body>
        <!-- 这里放内容 -->
    </body>
    </html>
    

    以上代码中虽然我们把<script>放在了<head>中,但其中包含的脚本文件将延迟到浏览器遇到</html>后再执行。HTML5规范要求脚本按照它们出现的先后顺序执行,即第一个延迟脚本先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行,但现实中延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded事件触发前执行,所以一个文档里最好只包含一个延迟脚本。

    新葡亰496net 8

    async-VS-defer-图片来自文章最底部参考资料

    上图的意思是:浏览器在解析HTML文件时,遇上没有设置defer或async属性的脚本,浏览器读到该脚本就加载并执行,脚本会阻塞其后内容的执行;
    遇上设有async属性的脚本,会在HTML解析过程中下载该脚本,并在完成下载后暂停HTML的解析来执行这个异步脚本,直到执行完成后再继续HTML的解析;
    遇上设有defer属性的脚本,会在HTML解析过程中下载该脚本,在HTML解析完成后才执行该文件。延迟脚本按照它们在文档中出现的顺序执行。

    1. 共同点:
    • 设置了async或defer属性的脚本不会阻塞页面渲染
    • async和defer属性决定了js脚本的执行方式,内联脚本会忽略这两个属性
    • 使用这两个属性的脚本中不能调用document.write
    • 所有defer和async脚本执行完毕后,DOMContentLoaded和load事件都将触发
    1. 区别:
    • 异步脚本一定会在页面的load事件前执行,但可能在DOMContentLoaded事件触发之前或之后执行,所以可能出现无顺序加载js的情况;延迟脚本在文档完成解析后,执行理论上是有序的但现实中并不能保证顺序,也不一定会在DOMContentLoaded事件触发前执行。
      (实际实验得到的结果是,在页面中有2个defer脚本,2个async脚本的情况下,这四个脚本的执行顺序是无序的,但单看defer或单看async,他们都是有序的,且这四个脚本文件都会在DOMContentLoaded事情之前执行完毕,load事件永远最后触发。)
    1. 关联:
    • async为true,脚本将在加载完成后立即执行
    • async为false,defer为true,脚本将在页面全部解析完成后执行
    • async和defer都为false,脚本将阻塞页面解析,即页面解析被挂起转而下载并立即执行脚本文件
    1. 注意事项:
      如果一个外部脚本依赖于另一外部脚本,请将它们标记为defer,并按它们被声明的顺序执行。
      浏览器发起资源请求request的时间都比较接近,除非DOM树非常长,时间可能会有明显差别。但资源响应时间responsive的时间差别较大,浏览器会根据情况作出优化,图片字体等优先级较低,一般等到最后才加载完成(比js还久)。

    3.生成render树

    DOM树和CSSOM树合并生成render树

    新葡亰496net 9

    简单描述这个过程:

    DOM树从根节点开始遍历可见节点,这里之所以强调了“可见”,是因为如果遇到设置了类似display: none;的不可见节点,在render过程中是会被跳过的(但visibility: hidden; opacity: 0这种仍旧占据空间的节点不会被跳过render),保存各个节点的样式信息及其余节点的从属关系。

    常见触发场景:

    单线程

    渲染引擎是单线程工作的,意味着渲染流程是一步一步渐进完成的。

    构建DOM树

    DOM树,即文档内所有节点构成的一个树形结构。
    假设浏览器获取返回的如下HTML文档:

     <!doctype html>
        <html>
        <head>
          <link rel="stylesheet" href="./theme.css"></link>
          <script src="./config.js"></script>
          <title>关键渲染路径</title>
        </head>
        <body>
          <h1 class="title">关键渲染路径</h1>
          <p>关键渲染路径介绍</p>
          <footer>@copyright2017</footer>
        </body>
        </html>
    

    首先浏览器从上到下依次解析文档构建DOM树,如下:

    新葡亰496net 10

    Paste_Image.png

    repaint 和 reflow

    • 什么是repaint和reflow:
      浏览器根据自己默认的或开发人员给出的样式表来计算DOM结构中的各个盒模型该出现的位置就是reflow;当各种盒子的位置、大小及其他属性,例如颜色、字体大小等都确定下来后,浏览器开始布局渲染树并将其绘制到屏幕上,这个过程就是repaint。

    浏览器解析和渲染页面过程中会涉及到reflow(回流)和repaint(重绘),就某些浏览器如Opera而言,大部分的reflow将导致页面重新渲染,其变化涉及到部分甚至是整个页面的布局,而repaint则导致浏览器必须验证DOM树上其他节点元素的可见性。reflow和repaint过程非常消耗性能,尤其在移动设备上,会破坏用户体验,造成页面卡顿,所以应尽可能减少reflow和repaint。

    • 导致回流的原因:

      • 调整窗口大小
      • 改变字体
      • 增加或移除样式表
      • 内容变化,如在input框中输入文字
      • 激活CSS伪类,如hover
      • 操作class属性
      • 脚本操作DOM
      • 计算offsetWidth和offsetHeight属性
      • 设置style属性的值
    • 如何避免回流或将它们对性能的影响降到最低:

      • 尽可能在DOM树最末端通过改变元素的class名的方式设定元素的样式:回流会顺行或逆行传递给周围的节点,在DOM树里改变class限制了回流的范围,使其影响尽可能少的节点。
      • 避免设置多项内联样式:因为每个内联样式都会造成回流,样式应合并在一个外部类,这样当该元素的class属性被操控时只会产生一个回流。
      • 应用元素的动画使用position属性的absolute或fixed值:这样设置不影响其他元素的布局,只会导致重绘而不是完整回流。
      • 牺牲平滑度换取速度:你可能想每次1px移动一个动画,但是此动画及随后的回流使用了100%的CPU,动画看上去就会是跳动的,因为浏览器正在与更新回流做斗争。而动画元素每次移动3px,在非常快的机器上看起来平滑度低了,但它不会导致CPU在较慢的机器和移动设备中抖动。
      • 避免使用table布局:table是个和罕见的可以影响在它们之前已经进入的DOM元素的显示的元素。想象一下,因为表格最后一个单元格的内容过宽而导致纵列大小完全改变。这就是为什么所有的浏览器都逐步地不支持table表格的渲染。另外一个原因说明表格布局很糟糕,根据Mozilla,一些小的变化将导致表格(table)中的所有其他节点回流。
      • 避免使用CSS的JS表达式:因为他们每次都重新计算全部或部分文档而导致每秒产生成千上万次回流。
      • 设置box-sizing: border-box;把标准盒模型转换为IE盒模型,告诉浏览器去理解你设置的边框和内边距的值是包含在width内的。设置后,即使padding或者border发生了改变,盒子宽高不会发生变化,只会重绘,不会回流。

    关于浏览器渲染更详细内容可参考我的博客第六节


    参考资料

    • async vs defer attributes

    4.Layout 布局

    有了各个节点的样式信息和属性,但不知道各个节点的确切位置和大小,所以要通过布局将样式信息和属性转换为实际可视窗口的相对大小和位置。

    1:触发repaint:

    • color的修改,如color=#ddd;
    • text-align的修改,如text-align=center;
    • a:hover也会造成重绘。
    • :hover引起的颜色等不导致页面回流的style变动。
    • 等等太多,一时间写出来也太难想了。

    解析文档(PARSER HTML)

    在详细介绍浏览器渲染文档之前,先应该理解浏览器如何解析文档:解析文档的顺序,对于CSS和JavaScript如何处理等。

    1. 解析顺序
      浏览器按从上到下的顺序扫描解析文档;

    2. 解析样式和脚本

    • 脚本

    由于通常会在JavaScript脚本中改变文档DOM结构,于是浏览器以同步方式解析,加载和执行脚本,浏览器在解析文档时,当解析到<script>标签时,会解析其中的脚本(对于外链的JavaScript文件,需要先加载该文件内容,再进行解析),然后立即执行,这整个过程都会阻塞文档解析,直到脚本执行完才会继续解析文档。就是说由于脚本是同步加载和执行的,它会阻塞文档解析,这也解释了为什么现在通常建议将<script>标签放在</body>标签前面,而不是放在<head>标签里。现在HTML5提供defer和async两个属性支持延迟和异步加载JavaScript文件,如:
    <script defer src="script.js">

    • 改进

    针对上文说的脚本阻塞文档解析,主流浏览器如Chrome和FireFox等都有一些优化,比如在执行脚本时,开启另一个线程解析剩余的文档以找出并加载其他的待下载外部资源(不改变主线程的DOM树,仅优化加载外部资源)。

    • 样式

    不同于脚本,浏览器对样式的处理并不会阻塞文档解析,大概是因为样式表并不会改变DOM结构。

    • 样式表与脚本

    你可能想问样式是否会阻塞脚本文件的加载执行呢?正常情况是不会的,但是存在一个问题是通常我们会在脚本中请求样式信息,但是在文档解析时,如果样式尚未加载或解析,将会得到错误信息,对于这一问题,FireFox浏览器和Webkit浏览器处理策略不同:

    • 当存在有样式文件未被加载和解析时,FireFox浏览器会阻塞所有脚本;
    • 而Webkit浏览器只会阻塞操作了改文件内声明的样式属性的脚本。

    构建CSSOM树

    CSSOM树,与DOM树结构相似,只是另外为每一个节点关联了样式信息。
    theme.css样式内容如下:

    html, body {
        width: 100%;
        height: 100%;
        background-color: #fcfcfc;
        }
        .title {
        font-size: 20px;
        }
        .footer {
        font-size: 12px;
        color: #aaa;
        }
    

    构建CSSOM树如图:

    新葡亰496net 11

    Paste_Image.png

    5.Paint 绘制

    万事俱备,最后只要将确定好位置大小的各节点,通过GPU渲染到屏幕的实际像素。

    2:触发reflow:

    • width/height/border/margin/padding的修改,如width=778px;
    • 动画,:hover等伪类引起的元素表现改动,display=none等造成页面回流;
    • appendChild等DOM元素操作;
    • font类style的修改;
    • background的修改,注意着字面上可能以为是重绘,但是浏览器确实回流了,经过浏览器厂家的优化,部分background的修改只触发repaint,当然IE不用考虑;

    构建DOM树

    DOM树,即文档内所有节点构成的一个树形结构。
    假设浏览器获取返回的如下HTML文档:

    <!doctype html>
    <html>
    <head>
      <link rel="stylesheet" href="./theme.css"></link>
      <script src="./config.js"></script>
      <title>关键渲染路径</title>
    </head>
    <body>
      <h1 class="title">关键渲染路径</h1>
      <p>关键渲染路径介绍</p>
      <footer>@copyright2017</footer>
    </body>
    </html>
    

    首先浏览器从上到下依次解析文档构建DOM树,如下:

    新葡亰496net 12

    Dom构建.png

    执行JavaScript

    上文已经阐述了文档解析时对脚本的处理,我们得知脚本加载,解析和执行会阻塞文档解析,而在特殊情况下样式的加载和解析也会阻塞脚本,所以现在推荐的实践是<script>标签放在</body>标签前面。

    Tips

    • 在上述渲染过程中,前3点可能要多次执行,比如js脚本去操作dom、更改css样式时,浏览器又要重新构建DOM、CSSOM树,重新render,重新layout、paint;
    • Layout在Paint之前,因此每次Layout重新布局(reflow 回流)后都要重新出发Paint渲染,这时又要去消耗GPU;
    • Paint不一定会触发Layout,比如改个颜色改个背景;(repaint 重绘)
    • 图片下载完也会重新出发Layout和Paint;

    新葡亰496net 13

    6:如何异步加载脚本

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

    没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

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

    有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

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

    有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

    defer:脚本延迟到文档解析和显示后执行,有顺序。
    async:不保证顺序。

    构建CSSOM树

    CSSOM树,与DOM树结构相似,只是另外为每一个节点关联了样式信息。

    theme.css样式内容如下:

     html, body {
        width: 100%;
        height: 100%;
        background-color: #fcfcfc;
    }
    .title {
    font-size: 20px;
    }
    .footer {
    font-size: 12px;
    color: #aaa;
    }
    

    构建CSSOM树如图:

    新葡亰496net 14

    构建CSSOM树.png

    构建渲染树(render tree)

    渲染树,代表一个文档的视觉展示,浏览器通过它将文档内容绘制在浏览器窗口,展示给用户,它由按顺序展示在屏幕上的一系列矩形对象组成,这些矩形对象都带有字体,颜色和尺寸,位置等视觉样式属性。对于这些矩对象,FireFox称之为框架(frame),Webkit浏览器称之为渲染对象(render object, renderer),后文统称为渲染对象。
    渲染树及其对应DOM树如图:

    新葡亰496net 15

    Paste_Image.png

    何时触发reflow和repaint

    reflow(回流): 根据Render Tree布局(几何属性),意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树;
    repaint(重绘): 意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了;
    reflow回流的成本开销要高于repaint重绘,一个节点的回流往往回导致子节点以及同级节点的回流;

    GoogleChromeLabs 里面有一个csstriggers,列出了各个CSS属性对浏览器执行Layout、Paint、Composite的影响。

    执行JAVASCRIPT

    上文已经阐述了文档解析时对脚本的处理,我们得知脚本加载,解析和执行会阻塞文档解析,而在特殊情况下样式的加载和解析也会阻塞脚本,所以现在推荐的实践是<script>标签放在</body>标签前面。

    布局(Layout)或回流(reflow,relayout)

    创建渲染树后,下一步就是布局(Layout),或者叫回流(reflow,relayout),这个过程就是通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置,而有些时候我们会在文档布局完成后对DOM进行修改,这时候可能需要重新进行布局,也可称其为回流,本质上还是一个布局的过程,每一个渲染对象都有一个布局或者回流方法,实现其布局或回流。

    引起reflow回流

    现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。

    1. 页面第一次渲染(初始化)
    2. DOM树变化(如:增删节点)
    3. Render树变化(如:padding改变)
    4. 浏览器窗口resize
    5. 获取元素的某些属性:
      浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle

    构建渲染树(RENDER TREE)

    DOM树和CSSOM树都构建完了,接着浏览器会构建渲染树:

    渲染树,代表一个文档的视觉展示,浏览器通过它将文档内容绘制在浏览器窗口,展示给用户,它由按顺序展示在屏幕上的一系列矩形对象组成,这些矩形对象都带有字体,颜色和尺寸,位置等视觉样式属性。对于这些矩对象,FireFox称之为框架(frame),Webkit浏览器称之为渲染对象(render object, renderer),后文统称为渲染对象。

    这里把渲染树节点称为矩形对象,是因为,每一个渲染对象都代表着其对应DOM节点的CSS盒子,该盒子包含了尺寸,位置等几何信息,同时它指向一个样式对象包含其他视觉样式信息。

    绘制(painting)

    最后是绘制(paint)阶段或重绘(repaint)阶段,浏览器UI组件将遍历渲染树并调用渲染对象的绘制(paint)方法,将内容展现在屏幕上,也有可能在之后对DOM进行修改,需要重新绘制渲染对象,也就是重绘,绘制和重绘的关系可以参考布局和回流的关系。

    引起repaint重绘

    1. reflow回流必定引起repaint重绘,重绘可以单独触发
    2. 背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)

    渲染树与DOM树

    每一个渲染对象都对应着DOM节点,但是非视觉(隐藏,不占位)DOM元素不会插入渲染树,如<head>元素或声明display: none;的元素,渲染对象与DOM节点不是简单的一对一的关系,一个DOM可以对应一个渲染对象,但一个DOM元素也可能对应多个渲染对象,因为有很多元素不止包含一个CSS盒子,如当文本被折行时,会产生多个行盒,这些行会生成多个渲染对象;又如行内元素同时包含块元素和行内元素,则会创建一个匿名块级盒包含内部行内元素,此时一个DOM对应多个矩形对象(渲染对象)。渲染树及其对应DOM树如图:

    新葡亰496net 16

    渲染树与DOM树

    • 图中渲染树viewport即视口,是文档的初始包含块,scroll代表滚动区域
    • 渲染树并不会包含显式或隐式地display:none;的标签元素。

    页面渲染优化

    浏览器对上文介绍的关键渲染路径进行了很多优化,针对每一次变化产生尽量少的操作,还有优化判断重新绘制或布局的方式等等。

    在改变文档根元素的字体颜色等视觉性信息时,会触发整个文档的重绘,而改变某元素的字体颜色则只触发特定元素的重绘;改变元素的位置信息会同时触发此元素(可能还包括其兄弟元素或子级元素)的布局和重绘。某些重大改变,如更改文档根元素<html>的字体尺寸,则会触发整个文档的重新布局和重绘,据此及上文所述,推荐以下优化和实践:

    1.HTML文档结构层次尽量少,最好不深于六层;
    2.脚本尽量后放,放在</body>前即可;
    3.少量首屏样式内联放在<head>标签内;
    4.样式结构层次尽量简单;
    5.在脚本中尽量减少DOM操作,尽量缓存访问DOM的样式信息,避免过度触发回流;
    6.减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;
    7.动画尽量使用在绝对定位或固定定位的元素上;
    8.隐藏在屏幕外,或在页面滚动时,尽量停止动画;
    9.尽量缓存DOM查找,查找器尽量简洁;
    10.涉及多域名的网站,可以开启域名预解析

    浅析浏览器页面渲染机制

    优化reflow、repaint触发次数

    • 避免逐个修改节点样式,尽量一次性修改
    • 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
    • 可以将需要多次修改的DOM元素设置display: none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
    • 避免多次读取某些属性(见上)
    • 将复杂的节点元素脱离文档流,降低回流成本

    布局过程

    布局是一个从上到下,从外到内进行的递归过程,从根渲染对象,即对应着HTML文档根元素<html>,然后下一级渲染对象,如对应着<body>元素,如此层层递归,依次计算每一个渲染对象的几何信息(位置和尺寸)。

    几何信息-位置和尺寸,即相对于窗口的坐标和尺寸,如根渲染对象,其坐标为(0, 0),尺寸即是视口
    尺寸(浏览器窗口的可视区域)。

    ** 每一个渲染对象的布局流程基本如:**

    1. 计算此渲染对象的宽度(width);
    2. 遍历此渲染对象的所有子级,依次:
      2.1 设置子级渲染对象的坐标
      2.2 判断是否需要触发子渲染对象的布局或回流方法,计算子渲染对象的高度(height)
    3. 设置此渲染对象的高度:根据子渲染对象的累积高,margin和padding的高度设置其高度;
    4. 设置此渲染对象脏位值为false。

    为什么一再强调将css放在头部,将js文件放在尾部

    绘制(PAINTING)

    最后是绘制(paint)阶段或重绘(repaint)阶段,浏览器UI组件将遍历渲染树并调用渲染对象的绘制(paint)方法,将内容展现在屏幕上,也有可能在之后对DOM进行修改,需要重新绘制渲染对象,也就是重绘,绘制和重绘的关系可以参考布局和回流的关系。

    DOMContentLoaded 和 load

    • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片…
    • load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成

    一个重要的概念reflow和repaint

    • Repaint
      屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
    • Reflow
      意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow)reflow 会从<html>这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必需被包装起来。
      Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个节点的reflow很有可能导致子子点,甚至父节点以及同级节点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。
      所以,下面这些动作有很大可能会是成本比较高的。
    1. 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint
    2. 当你移动DOM的位置,或是搞个动画的时候。
    3. 当你修改CSS样式的时候。
    4. 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
    5. 当你修改网页的默认字体时。
      注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

    减少reflow/repaint

    1. 不要一条一条地修改DOM的样式。与其这样,还不如预先定义好css的class,然后修改DOM的className。
    2. 不要把DOM结点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。
    3. 尽可能的修改层级比较低的DOM。当然,改变层级比较低的DOM有可能会造成大面积的reflow,但是也可能影响范围很小。
      为动画的HTML元件使用fixed或absoult的position,那么修改他们的CSS是不会reflow的。
      千万不要使用table布局。因为可能很小的一个小改动会造成整个table的重新布局。

    CSS 资源阻塞渲染

    构建Render树需要DOM和CSSOM,所以HTML和CSS都会阻塞渲染。所以需要让CSS尽早加载(如:放在头部),以缩短首次渲染的时间。

    JS 资源

    • 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML
      • 这和之前文章提到的浏览器线程有关,浏览器中js引擎线程和渲染线程是互斥的,详见《从setTimeout-setInterval看JS线程》
    • 普通的脚本会阻塞浏览器解析,加上defer或async属性,脚本就变成异步,可等到解析完毕再执行
      • async异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在onload前,但不确定在DOMContentLoaded事件的前后
      • defer延迟执行,相对于放在body最后(理论上在DOMContentLoaded事件前)

    举个栗子

    <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js"></script> </body> </html>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="style.css" rel="stylesheet">
      </head>
      <body>
        <p>Hello <span>web performance</span> students!</p>
        <div><img src="awesome-photo.jpg"></div>
        <script src="app.js"></script>
      </body>
    </html>

    新葡亰496net 17

    • 浏览器拿到HTML后,从上到下顺序解析文档
    • 此时遇到css、js外链,则同时发起请求
    • 开始构建DOM树
    • 这里要特别注意,由于有CSS资源,CSSOM还未构建前,会阻塞js(如果有的话)
    • 无论JavaScript是内联还是外链,只要浏览器遇到 script 标记,唤醒JavaScript解析器,就会进行暂停 blocked 浏览器解析HTML,并等到 CSSOM 构建完毕,才执行js脚本
    • 渲染首屏(DOMContentLoaded 触发,其实不一定是首屏,可能在js脚本执行前DOM树和CSSOM已经构建完render树,已经paint)

    首屏优化Tips

    说了这么多,其实可以总结几点浏览器首屏渲染优化的方向

    • 减少资源请求数量(内联亦或是延迟动态加载)
    • 使CSS样式表尽早加载,减少@import的使用,因为需要解析完样式表中所有import的资源才会算CSS资源下载完
    • 异步js:阻塞解析器的 JavaScript 会强制浏览器等待 CSSOM 并暂停 DOM 的构建,导致首次渲染的时间延迟
    • so on…

    知道操作DOM成本多高了吗?

    其实写了这么多,感觉偏题了,大量的资料参考的是chrome开发者文档。感觉js脚本资源那块还是有点乱,包括和DOMContentLoaded的关系,希望大家能多多指点,多多批评,谢谢大佬们。

    操作DOM具体的成本,说到底是造成浏览器回流reflow和重绘reflow,从而消耗GPU资源。

    参考文献:

    已同步至个人博客-软硬皆施
    Github 欢迎star :)

    2 赞 4 收藏 评论

    新葡亰496net 18

    本文由新葡亰496net发布于新葡亰官网,转载请注明出处:新葡亰496netJS相关概念,操作费用到底高在何地

    关键词:

上一篇:新葡亰496netjs_脚本之家

下一篇:没有了