凭setTimeout在渲染出dom之后执行达到了优化效果吗?

为了更好地理解渲染过程的优化,我们首先需要了解网页的渲染流程。

在了解网页的渲染流程之前,我们先来看看浏览器中的进程与线程。

现代浏览器是多进程架构,页面的加载、渲染和交互是由多个进程配合完成。以 Chrome 为例,其主要的进程架构如下:

负责界面显示、用户交互、子进程管理,同时提供存储等功能。 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。 默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。 出于安全考虑,渲染进程都是运行在沙箱模式下,无法访问系统资源。 Chrome 刚开始发布的时候是没有 GPU 进程的。 GPU 的使用初衷是为了实现 3D CSS 的效果。 随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。 最后,Chrome 在其多进程架构上也引入了 GPU 进程。 主要负责页面的网络资源加载。 之前是作为一个模块运行在浏览器主进程里面的,直至最近才独立出来,成为一个单独的进程。 主要是负责插件的运行。 因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

我们用 Chrome 浏览器打开百度和微博两个页面,点击 Chrome 浏览器右上角的三个点的“选项”菜单,选择 更多工具--任务管理器,可以看到如下图的 Chrome 任务管理器窗口:

现代浏览器是多进程架构有如下优点:

  • 由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面,提高了浏览器稳定性。
  • Chrome 把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限,提高了浏览器安全性。
  • 此外,多进程还能充分利用多核优势。

当然,内存等资源消耗也会更大,相当于空间换时间。

在浏览器的所有进程中,与前端开发最相关就是渲染进程,也就是我们常说的浏览器内核。

渲染进程的主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。在该过程中渲染进程会开启多个线程协作完成,主要的线程以及作用如下:

负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。

也称为JS内核,负责解析Javascript脚本,运行代码。(例如V8引擎)

JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个渲染进程中无论什么时候都有一个JS线程在运行JS程序。

需要注意的是: GUI渲染线程与JS引擎线程是相互排斥的。因为JS引擎线程在执行的过程中可能会发生回流和重绘,所以GUI渲染线程执行时候,JS引擎线程会被挂起,等待GUI渲染线程执行完毕之后。同理,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存起来等到JS引擎空闲时立即被执行。所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染阻塞。

归属于浏览器内核而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)。

当 JS 引擎执行代码块如 setTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX 异步请求等),会将对应任务添加到事件线程中。

当对应的事件符合触发条件被触发时,该线程会把事件回调函数添加到任务队列的队尾,等待JS引擎的处理。

注意,由于JS的单线程关系,所以这些任务队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)。

浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)。

因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

在XMLHttpRequest在连接后是通过浏览器新开一个线程请求。

将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入任务队列中。再由JavaScript引擎执行。

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局、绘制和合成。

浏览器无法直接理解和使用 HTML 文本,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。并且 JavaScript 查询或修改页面结构也是通过 DOM 树。

构建 DOM 树、样式计算都是由GUI渲染线程控制完成,但为了提高效率,具体的字节流解析工作,GUI渲染线程会另起子线程完成。比如 HTMLParser线程负责将 HTML 文本转换为 DOM 结构;CSSParser线程将 CSS 文本转换为浏览器可以理解的结构——styleSheets。

样式计算的目的是为了计算出 DOM 树中每个元素的具体样式。主要有以下几个步骤:

1、把 CSS 转换为浏览器能够理解的结构styleSheets(CSSOM)。该结构同时具备查询和修改功能,这为后面的样式操作提供基础。在控制台中输入 document.styleSheets, 就可以查看其结构。

2、转换样式表中的属性值,使其标准化。CSS文本中有一些属性值,如 2em、red、bold等,不容易被渲染引擎理解,需要将其为渲染引擎容易理解的、标准化的计算值。em --> px,red --> rgb(255,0,0),bold --> 700.

3、计算出 DOM 树中每个节点的具体样式。在计算过程中需要遵守 CSS 的继承和层叠两个规则。DOM 元素最终的计算样式,可以通过浏览器的Element->Computed可以查看。

有了 DOM 树及其对应的样式,接下来就是 创建布局树 和 布局计算 。

DOM 树包含了很多不可见的元素,比如head 标签、使用了 display:none 属性的元素。也有一些不存在 DOM 树中但需要显示在页面上的元素,比如伪类。因此,我们需要在 DOM 树的基础上额外构建一棵布局树(Render Tree),只包含需要显示的可见元素。

有了布局树,下一步就是,根据 DOM 元素的文档结构和样式,计算出每个元素在页面中的具体坐标位置、尺寸等信息,并且,又将这些信息保存在布局树中。

CSS采用了盒子模型来表示每个元素与其他元素之间的距离,盒子模型包括外边距(Margin),内边距(Padding),边框(Border),内容(Content)。页面中的每个元素都是一个个盒子,布局阶段会从布局树的根节点开始遍历,然后确定每个元素盒子在页面中的确切大小与坐标位置的绝对像素值。

浏览器中的页面通常被分成了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。

为什么需要分层?主要是为了处理页面中的一些复杂的效果,比如 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,这些效果使得与之对应的 DOM 节点尺寸和坐标等不断更新。如果不分层,则需要重新计算整个布局树中每个元素的位置,而分层后,就只需要计算变换层中的元素位置信息。

因此,为了更方便地实现上述效果,渲染引擎为特定的节点生成专用的图层,最后生成一棵图层树(LayerTree)。

查看一个页面有哪些图层,可以打开开发者工具,选择“Layers”标签,左边是图层列表,对应右边的渲染结果中,每一个黑色线框就是一个图层,选中图层后,右下角可以看到图层的详细信息。

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。

那么元素要满足什么条件,渲染引擎才会为它创建新的图层呢?答案是:元素拥有层叠上下文属性,或者需要被剪裁。

裁剪,假如有一个200*200的div元素,其中文字内容超出了div面积(overflow:auto或scroll),就产生了裁剪,渲染引擎会裁剪文字内容的一部分显示在 div 区域,此时,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。这样,滚动内容就不会重新计算整个文档的布局信息。

图层树的构建完成后,渲染引擎会对图层树中的每个图层进行绘制。对于每一个图层,渲染引擎会把对它的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。打开“开发者工具”的“Layers”标签,选择“document”层,来实际体验下绘制列表:

在图层绘制阶段,输出的内容就是这些待绘制列表,用于记录绘制顺序和绘制指令。此时,并没有真正的绘制出页面,实际的绘制操作由合成线程来完成。

进行到这一步的一些关键数据结构如下图所示:

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。

前置知识: 浏览器显示页面内容的屏幕区域,叫做视口(viewport)。

一个页面可能很长,需要滚动显示,每次显示在视口中的只是页面的一小部分。对于这种情况,绘制出一整个长页,会产生太大的开销,而且也没必要。

合成线程会将图层划分为图块(这些图块的大小通常是 256x256 或者 512x512),然后按照视口附近的图块来优先生成位图。

实际生成位图的操作是由栅格化线程来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位

通常,栅格化过程都会使用 GPU 来加速生成。GPU 操作是运行在 GPU 进程中,这就涉及到了跨进程操作。

渲染进程把图块生成位图的指令发送给 GPU 进程,然后在 GPU 中生成图块的位图,并保存在 GPU 的内存中。

一旦所有图块都被生成位图,合成线程就会生成一个绘制图块位图的命令—— DrawQuad,然后将该命令提交给浏览器主进程。

浏览器主进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,浏览器就会显示出页面的内容了。整个渲染流程如下图所示:

CSS选择器的匹配是从右向左进行的,这一策略导致了不同的选择器之间的性能也存在差异。

相比于.content-title-span,使用.content .title span时,浏览器计算样式所要花费的时间更多。使用后面一种规则,浏览器必须遍历页面上所有 span 元素,先过滤掉祖先元素不是.title的,再过滤掉.title的祖先不是.content的。嵌套的层级更多,匹配所要花费的时间代价自然更高。

因此,在日常开发中,合理使用css选择器,也是优化页面的一个方面。

  • 避免使用通配符,只对需要用到的元素进行选择。
  • 关注可以通过继承实现的属性,避免重复匹配重复定义。
  • 减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最好不要超过三层),可以使用BEM风格的class来关联元素。
  • 减少用标签选择器和属性选择器。
  • 不要使用class选择器和id选择器修饰元素标签,如h3.title,这样多此一举,还会降低效率,直接使用.title

现代浏览器在这一方面做了很多优化,不同选择器的性能差别不是特别明显,不过知道总比不知道强。

JS 引擎和渲染引擎是两个独立的线程。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”,交流依赖了桥接接口作为“桥梁”。

我们每操作一次 DOM(不管是为了修改还是仅仅为了访问其值),都要过一次桥,每次过桥都有一定开销。因此,过桥的次数一多,就会产生比较明显的性能问题。

所以,使用js修改页面时,应尽量减少 DOM 操作。

上面这样写有两个明显的可优化点:

我们每一次循环都调用 DOM 接口重新获取了一次 container 元素,相当于每次循环都有过桥开销。其实,可以通过缓存访问过的元素,来减少后面9999次调用 DOM 接口的操作。

  • 避免频繁修改DOM,尽量一次更新。

上面虽然缓存了访问元素,但在每一次循环里都修改了 DOM 。对 DOM 的修改会引发渲染树的改变,进而导致回流。我们可以将对 DOM 的修改累计起来,然后一次性地应用到 DOM 上。

// 累计对DOM的修改操作 // 将累计的修改操作一次性地应用到 DOM

对于页面中多个元素节点的修改,可以借助 DocumentFragment 完成,具体参考

前端框架,Vue、React等,是通过虚拟节点(Virtual DOM)来搜集更新,然后一次性更新 DOM。

此外,日常开发中比较常见的减少DOM操作的方法还有事件委托。假如有一个很长的列表,点击每个列表项的时候需要响应事件;

如果给每个列表项一一绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;

比较好的方法就是用事件委托,把这个点击事件绑定到他的父层,也就是 ul 上,然后在执行事件的时候再去匹配判断目标元素;

// 给父层元素绑定事件
 // 判断是否匹配目标元素
 

1、只绑定一个事件函数,可以减少大量的内存消耗,并且减少了dom操作,提高性能。

2、动态元素绑定事件。通过 AJAX 或者 用户交互 动态的增加或者去除li元素,、在每一次改变的时候都需要重新给新增的元素绑定事件,给将要删去的元素解绑事件;使用了事件委托可以省去这些麻烦,因为事件是绑定在ul的,和li的增减没有关系。

当网页交互过程中,有一些操作常常会频繁触发,如滚动页面触发的scroll事件,页面缩放触发resize事件、鼠标移动的mousemove\mouseover事件等。

频繁触发这些事件会导致相应回调函数的大量计算,从而引发页面抖动甚至卡顿,为了控制事件回调的触发频率,就需要用事件节流和事件防抖。

简单来说,就是从一个时间点开始,在某段时间,无论触发了多少次回调,我都只认第一次,并在计时结束时给予响应。

以scroll事件为例,当用户滚动页面触发了一次scroll事件后,就为这个触发操作开启一个固定时间的计时器。在这个计时器持续时间内,限制后续发生的所有scroll事件对回调函数的触发,当计时器计时结束后,响应执行第一次触发scroll事件的回调函数。

// interval是时间间隔的阈值, fn是我们需要包装的事件回调
 //上次触发回调的时间
 
 //事件节流操作的闭包返回
 
 //记录本次回调触发的时间
 
 //判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值,若不小于,则执行回调
//通过事件节流优化事件回调函数
 

只响应最后一次触发事件。在一定时间内,不管事件触发了多少次,都只认最后一次。假设设置时间为 1000ms,第一次触发这个事件时会启动一个定时器,后面每次触发这个事件都会清除已有定时器,并重新设置新的定时器,起点为最近一次触发的这个时间。

所以,如果在1000ms内,你再次触发这个事件,之前的定时器被清除,又从此次触发事件的这一刻开始倒计时。

//事件防抖的闭包操作 //每次事件被触发时,都去清除之前的旧定时器 //通过事件防抖优化事件回调函数

回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化,比如修改元素的宽、高或隐藏元素等,浏览器需要重新计算元素的几何属性进行布局,然后再将计算的结果绘制出来。这个过程就是回流(也叫重排或页面布局)。

重绘:当 DOM 需要更新属性,而这些属性只是影响其外观,风格,并不会影响布局,比如只修改了颜色或背景色,浏览器不需重新计算元素的几何属性,直接为该元素绘制新的样式。这个过程叫做重绘。

重绘不一定导致回流,回流一定会导致重绘。回流比重绘的代价要更高。那么哪些操作可能触发回流呢?

2、 改变 DOM 树的结构。节点的增减、移动等操作。

这都可以在写样式的时候显式地通过代码效果看出来。对于上面触发回流的操作,浏览器自身也做了优化。浏览器会维护一个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作达到一个阈值或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流/重绘变成一次回流/重绘。但是当你获取某些特定地属性值或者调用某些方法时,浏览器会立刻清空队列。

3、获取下面这些属性值或者调用方法。这些属性和方法有一个共性,就是需要通过即时计算得到,所以浏览器需要立刻重新进行布局计算。

对回流、重绘最直观的优化,就是可以减少它的发生次数。

1、避免频繁改变样式。使用class或者cssText合并多次对样式的修改,然后一次更新。

2、批量修改DOM。在上面的减少DOM操作中已经写过,这里不再赘述。

3、 避免频繁读取“敏感”属性。上文我们说过,当我们访问元素的一些属性的时候,会导致浏览器强制清空队列。

举个例子,比如说我们想将一组元素的宽度赋值为某一个元素的宽度,我们可能写出这样的代码:

每一次循环的时候,都读取了div的offsetWidth属性值,这就导致了,每一次循环都会强制浏览器刷新队列。作为优化,我们可以将敏感属性通过变量的形式缓存起来。

对于一些复杂的动画效果,比如常见的点击菜单时弹出收回菜单的动画特效,还有一些炫酷的 3D 动画特效,如果没有采用分层机制,会经常引起回流重绘。

关于分层的机制,我们在上面的页面渲染流程中有讲到过。一个网页可以分解很多个图层(分层),每个图层都可以单独地设置大小、位置、透明度、旋转角度等,图层的上下位置可以调整,最后将这些图层叠加在一起后(合成),就能呈现出最终的效果。

在 Chrome 的渲染流水线中,分层体现在生成布局树之后,渲染引擎会根据布局树的特点将其转换为图层树(Layer Tree),合成则是当图层的绘制列表准备好了之后,交由合成线程来完成。

对于需要动画效果的元素,可以将其创建为单独图层,比如使用绝对定位,当它发生改变,不会引起其他元素的频繁回流。

使用css3的一些属性,可以使得动画效果不会引起回流重绘。在了解了渲染流程各部分的功能和作用后,我们知道如果一个动画的实现不经过页面布局和重绘环节,仅在合成阶段就能完成,将会大大提升性能。

符合这一要求的动画属性有:transform和opacity。它们能实现的动画效果:平移、缩放、旋转、倾斜、矩阵变换、透明度。

在使用transform和opacity实现动画效果时,尽量用 will-change 来提前告诉渲染引擎,让它为元素创建独立的层。

极客时间:浏览器工作原理与实践

}

Google在Udacity上发布了《[Android Performance][]》的在线课程,介绍了如何通过工具识别和和修复性能问题。课程分为四节:渲染,计算,内存,耗电。

}

6.Web服务器接收请求,并查找HTML页面。如果该页面存在,该Web服务器准备响应并把它发回给你的浏览器。如果服务器找不到你请求的页面,它将发送一个404错误消息,404表示“页面未找到”

7.浏览器接收到的HTML页面从头到尾扫描一遍,并去寻找其它相关的资源,如图片、CSS文件、JavaScript脚本文件等等,通过相同方式获取到资源

8.一旦浏览器加载完HTML涉及到的所有资源,页面最终会加载在浏览器窗体里,并关闭与服务器的连接。

12. 强缓存和协商缓存

协商缓存。在HTTP1.0中第一次请求资源时通过服务器设置Last-Modified响应头,填入最后修改时间。在之后的每次请求中都会在请求头中带上If-Modified-Since字段,如果未更新就返回304,指导浏览器从本地缓存中读取。

强缓存和协商缓存的区别总结:
1.强缓存只有首次请求会跟服务端通信,读取缓存资源时不用发送请求。返回200。
2.协商缓存总会与服务器交互,第一次是拿数据和E-tag的过程,之后每次凭E-tag询问是否更新。命中缓存返回304。
3.二者之间最大的区别就是:强缓存只通信一次;协商缓存每次都通信询问。

在数据发生变化,vue是先根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode ,来实现更新节点。

(1)先去同级比较,然后再去比较子节点

(2)先去判断一方有子节点一方没有子节点的情况

(3)比较都有子节点的情况

虚拟 DOM 的实现原理主要包括以下 3 部分:
(2)diff 算法 — 比较两棵虚拟 DOM 树的差异;
(3)pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作promise,帮助我们处理队列

首先,Promise 是一个构造函数,对回调函数的一种封装,对异步编程的一种改进,用同步的方式表达出来。可以说Promise是ajax的执行状态管理工具,它还应用到Vue里的fetch等方面。该构造函数身上有两个方法:Promise.all(),和Promise.race()。
其实,我们应用的重点是:new出来的promise对象。它有三种状态:pending(进行中),resolved(已完成),rejected(已失败),状态一旦发生,就不能改变。执行new的时候状态就开始变化。promise对象身上有两个方法:then(),和catch()。

所以,我们在遇到回调函数层层嵌套时,就可以将函数返回一个promise对象。利用 then方法+catch方法 来处理。

二、使用promise的好处是:
1.代码结构更加扁平且更可读,清晰明了。

2.能解决回调地狱问题。

3.可将数据请求和业务逻辑分离开来。

5.能更好的捕获错误。

都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

2、await可以是Promise同步,即不会立即执行Proimse外面(或者说下面)的代码,而await只能用在async标记的函数中,这样async函数本身就是异步的,而async函数里面的代码是同步的

3、async本身是异步的,可以这样理解,async函数执行到await的时候就立即返回,开始执行async下面的代码,与await等待执行的代码同时执行

Vue 项目的编译优化

前端优化的途径有很多,大致可以分为两类,第一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等 ;第二类则是代码级别的优化,例如 Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。

1、减少 HTTP 请求:浏览器一般同时响应请求为4个(PC 一般为4个,Android 支持4个,IOS 5后可支持6个)
减少 HTTP请求数的主要途径包括:
合理设置 HTTP缓存、资源合并与压缩
3、使用首屏加载:首屏的快速显示,可以大大提升用户对页面速度的感知,因此应尽量针对首屏的快速显示做优化按需加载:将不影响首屏的资源和当前屏幕资源不用的资源放到用户需要时才加载,可以大大提升重要资源的显示速度和降低总体流量。但是这也会导致大量重绘,影响渲染性能
5、使用其他方式代替图片(CSS3,SVG,IconFont)、使用 Srcset (主要移动端)、选择合适的图片(webP优于JPG,PNG8优于GIF)、选择合适的大小(首次加载不大于1014KB,不宽于640(基于手机的一般宽度))
6、CSS 写在头部(阻塞 DOM 渲染,不阻塞加载,内联会阻塞加载)
如果将 CSS放在其他地方比如 BODY中,则浏览器有可能还未下载和解析CSS,就已经开始渲染页面了,这就导致页面由无 CSS状态跳转到 CSS状态,用户体验比较糟糕。除此之外,有些浏览器会在 CSS下载完成后才开始渲染页面,如果 CSS放在靠下的位置则会导致浏览器将渲染时间推迟。),JavaScript 写在尾部或异步(js文件默认阻塞加载和渲染)

1、减少dom的重绘和回流
2、避免不必要的 DOM 操作
5、缓存 DOM 选择与计算:每次 DOM 选择都要计算,用一个变量保存这个值
6、尽量使用事件代理,避免批量绑定事件
7、尽量使用 ID 选择器:ID选择器是最快的

Vue3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:①只能监测属性,不能监测对象;②检测属性的添加和删除;③检测数组索引和长度的变更;④支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下特性:
用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。

模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
3.对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其它的更改:
支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
支持 Fragment(多个根节点)和 Protal(在 dom 其它部分渲染组建内容)组件,针对一些特殊的场景做了处理。
基于 treeshaking 优化,提供了更多的内置功能。

21. 防抖和节流区别

函数防抖:将多次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

函数节流:使得一定时间内只触发一次函数。原理是通过判断是否有延迟调用函数未执行。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

同协议,同端口,同域名才属于同一域。

ajax出于安全考虑禁止跨域,但是src可以跨域。

此方案仅限主域相同,子域不同的跨域应用场景。

它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递

2、带cookie跨域请求:前后端都需要进行设置

1、主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;

2、主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);

3、异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;

4、主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);

5、重复执行step2、3、4;称为事件循环。

1、每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的属性和方法,即这个原型对象是用来给实例共享属性和方法的。
而每个实例内部都有一个指向原型对象的指针。

2、每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象内部的指针。我们让原型对象的实例(1)等于另一个原型对象(2),
此时原型对象(2)将包含一个指向原型对象(1)的指针,
再让原型对象(2)的实例等于原型对象(3),如此层层递进就构成了实例和原型的链条,这就是原型链的概念

我们可以对于webpack配置做以下扩展和优化:

3、Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件

4、不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。

用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。

2、利用加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径

3、删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现

}

我要回帖

更多关于 dom渲染在vue的哪个生命周期 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信