浏览器原理与实践系列文章内容来自极客时间李兵老师《浏览器工作原理与实践》,主要是记录自己的学习过程,基于自己的理解对内容做的一些总结,包括《宏观视角下的浏览器》《浏览器中的JS执行机制》《V8引擎工作原理》《事件循环系统》《浏览器中的页面》《网络协议》《浏览器安全》共七篇,此为第五篇
DOM树
从网络传给渲染引擎的HTML文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是DOM。DOM提供了对HTML文档结构化的表述。在渲染引擎中,DOM有三个层面的作用。
- 从页面的视角来看,DOM是生成页面的基础数据结构。
- 从JavaScript脚本视角来看,DOM提供给JavaScript脚本操作的接口,通过这套接口,JavaScript可以对DOM结构进行访问,从而改变文档的结构、样式和内容。
- 从安全视角来看,DOM是一道安全防护线,一些不安全的内容在DOM解析阶段就被拒之门外了。
简言之,DOM是表述HTML的内部数据结构,它会将Web页面和JavaScript脚本连接起来,并过滤一些不安全的内容。
在渲染引擎内部,有一个叫HTML解析器(HTMLParser) 的模块,它的职责就是负责将HTML字节流转换为DOM结构。
HTML解析器并不是等整个文档加载完成之后再解析的,而是网络进程加载了多少数据,HTML解析器便解析多少数据 :网络进程接收到响应头之后,会根据响应头中的content-type字段来判断文件的类型,比如content-type的值是"text/html",浏览器就会判断这是一个HTML类型的文件,然后为该请求选择或者创建一个渲染进程。渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据传递给HTML解析器,它会动态接收字节流,并将其解析为DOM。
JavaScript会影响DOM生成
当执行到JavaScript标签时,解析器会暂停整个DOM的解析,执行JavaScript代码,不过执行JavaScript时,需要先下载JavaScript代码。JavaScript文件的下载过程会阻塞DOM解析,而通常下载又是非常耗时的,会受到网络环境、JavaScript文件大小等因素的影响。
不过Chrome浏览器做了很多优化,其中一个主要的优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析HTML文件中包含的JavaScript、CSS等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。
有一些相关的策略来规避JavaScript线程阻塞DOM,比如使用CDN来加速JavaScript文件的加载,压缩JavaScript文件的体积。另外,如果JavaScript文件中没有操作DOM相关代码,就可以将该JavaScript脚本设置为异步加载,通过async 或defer来标记代码。async和defer虽然都是异步的,不过还有一些差异,使用async标志的脚本文件一旦加载完成,会立即执行,与在页面中出现的顺序无关 ;而使用了defer标记的脚本文件,需要在DOMContentLoaded事件之前执行,与出现顺序有关,dom加载完毕后按出现顺序依次执行。
如果JavaScript代码出现了类似 div.style.color = 'red' 的语句,在执行JavaScript之前,就需要先解析JavaScript语句之上所有的CSS样式,因为它是用来操纵CSSOM的。所以如果代码里引用了外部的CSS文件,在执行JavaScript之前,还需要等待外部的CSS文件下载完成,并解析生成CSSOM对象之后,才能执行JavaScript脚本。
而JavaScript引擎在解析JavaScript之前,是不知道JavaScript是否操纵了CSSOM的,所以渲染引擎在遇到JavaScript脚本时,不管该脚本是否操纵了CSSOM,都会执行CSS文件下载,解析操作,再执行JavaScript脚本。
所以说JavaScript脚本是依赖样式表的,这又多了一个阻塞过程。JavaScript会阻塞DOM生成,而样式文件又会阻塞JavaScript的执行,所以在实际的工程中需要重点关注JavaScript文件和样式表文件,使用不当会影响到页面性能的。
渲染流水线:CSS如何影响首次加载时的白屏时间?
xml
<link href="theme.css" rel="stylesheet">
<div>geekbang com</div>
<script src="foo.js"></script>
<div>geekbang com</div>
以上是页面HTML代码以及渲染流程图,借此分析渲染流水线:
首先是发起主页面的请求,这个发起请求方可能是渲染进程,也有可能是浏览器进程,发起的请求被送到网络进程中去执行。网络进程接收到返回的HTML数据之后,将其发送给渲染进程,渲染进程会解析HTML数据并构建DOM。需要特别注意下,请求HTML数据和构建DOM中间有一段空闲时间,这个空闲时间有可能成为页面渲染的瓶颈。当渲染进程接收HTML文件字节流时,会先开启一个预解析线程,如果遇到JavaScript文件或者CSS文件,预解析线程会提前下载这些数据。对于上面的代码,HTML预解析器识别出来了有CSS文件和JavaScript文件需要下载,然后就同时发起这两个文件的下载请求,这两个文件的下载过程是重叠的,所以下载时间按照最久的那个文件来算。不管CSS文件和JavaScript文件谁先到达,都要先等到CSS文件下载完成并生成CSSOM,然后再执行JavaScript脚本,最后再继续构建DOM,构建布局树,绘制页面。还有一个空闲时间需要注意,就是在DOM构建结束之后、theme.css文件还未下载完成的这段时间内,渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要CSSOM和DOM,所以这里需要等待CSS加载结束并解析成CSSOM。
和HTML一样,渲染引擎也是无法直接理解CSS文件内容的,所以需要将其解析成渲染引擎能够理解的结构,这个结构就是CSSOM。和DOM一样,CSSOM也具有两个作用,第一个是提供给JavaScript操作样式表的能力,第二个是为布局树的合成提供基础的样式信息。 因此渲染流水线也需要等待CSSOM。等DOM和CSSOM都构建好之后,渲染引擎就会构造布局树。布局树的结构基本上就是复制DOM树的结构,不同之处在于DOM树中那些不需要显示的元素会被过滤掉,如display:none属性的元素、head标签、script标签等。复制好基本的布局树结构之后,渲染引擎会为对应的DOM元素选择对应的样式信息,这个过程就是样式计算 。样式计算完成之后,渲染引擎还需要计算布局树中每个元素对应的几何位置,这个过程就是计算布局。通过样式计算和计算布局就完成了最终布局树的构建。
影响页面展示的因素以及优化策略
渲染流水线影响到了首次页面展示的速度,而首次页面展示的速度又直接影响到了用户体验,所以分析渲染流水线的目的就是为了找出一些影响到首屏展示的因素,然后再基于这些因素做一些针对性的调整。
从发起URL请求开始,到首次显示页面的内容,在视觉上经历三个阶段。
- 等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容。
- 提交数据之后渲染进程会创建一个空白页面,这段时间称为解析白屏,并等待CSS文件和JavaScript文件的加载完成,生成CSSOM和DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染。
- 第三个阶段,等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。
影响第一个阶段的因素主要是网络或者是服务器处理。第二个阶段主要问题是白屏时间,如果白屏时间过久,就会影响到用户体验。为了缩短白屏时间,需要挨个分析这个阶段的主要任务,包括了解析HTML、下载CSS、下载JavaScript、生成CSSOM、执行JavaScript、生成布局树、绘制页面一系列操作。
通常情况下的瓶颈主要体现在下载CSS文件、下载JavaScript文件和执行JavaScript。
所以要想缩短白屏时长,可以有以下策略:
- 通过内联JavaScript、内联CSS来移除这两种类型的文件下载,这样获取到HTML文件之后就可以直接开始渲染流程了。
- 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过webpack等工具移除一些不必要的注释,并压缩JavaScript文件。
- 还可以将一些不需要在解析HTML阶段使用的JavaScript标记上sync或者defer。
- 对于大的CSS文件,可以通过媒体查询属性,将其拆分为多个不同用途的CSS文件,这样只有在特定的场景下才会加载特定的CSS文件。
分层和合成机制:为什么CSS动画比JavaScript高效?
显示器是怎么显示图像的
每个显示器都有固定的刷新频率,通常是60HZ,也就是每秒更新60张图片,更新的图片都来自于显卡中的前缓冲区 ,显示器所做的任务很简单,就是每秒固定读取60次前缓冲区中的图像,并将读取的图像显示到显示器上。那么这里显卡做什么呢? 显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。通常情况下,显卡的更新频率和显示器的刷新频率是一致的。但有时候,在一些复杂的场景中,显卡处理一张图片的速度会变慢,这样就会造成视觉上的卡顿。
帧 VS 帧率
大多数设备屏幕的更新频率是60次/秒,这也就意味着正常情况下要实现流畅的动画效果,渲染引擎需要每秒更新60张图片到显卡的后缓冲区。
渲染流水线生成的每一副图片称为一帧,渲染流水线每秒更新了多少帧称为帧率,比如滚动过程中1秒更新了60帧,那么帧率就是60Hz(或者60FPS)。
如何生成一帧图像
任意一帧的生成方式,有重排、重绘和合成三种方式。
这三种方式的渲染路径是不同的,通常渲染路径越长,生成图像花费的时间就越多。
- 重排,它需要重新根据CSSOM和DOM来计算布局树,这样生成一幅图片时,会让整个渲染流水线的每个阶段都执行一遍,如果布局复杂的话,就很难保证渲染的效率了。
- 重绘,因为没有了重新布局的阶段,操作效率稍微高点,但是依然需要重新计算绘制信息,并触发绘制操作之后的一系列操作。
- 合成,相较于重排和重绘,合成操作的路径就显得非常短了,并不需要触发布局和绘制两个阶段,如果采用了GPU,合成的效率会非常高。
Chrome浏览器是怎么实现合成操作的,可以用三个词来概括总结:分层、分块和合成:
分层和合成
如果没有采用分层机制,从布局树直接生成目标图片的话,那么每次页面有很小的变化时,都会触发重排或者重绘机制,这种"牵一发而动全身"的绘制策略会严重影响页面的渲染效率。和PhotoShop的图层类似,PhotoShop中一个项目是由很多图层构成的,每个图层都可以是一张单独图片,可以设置透明度、边框阴影,可以旋转或者设置图层的上下位置,将这些图层叠加在一起后,就能呈现出最终的图片了。将素材分解为多个图层的操作就称为分层 ,最后将这些图层合并到一起的操作就称为合成 。合成操作是在合成线程上完成的,这就意味着在执行合成操作时,是不会影响到主线程执行的。 所以有时候主线程卡住了,但是CSS动画依然能执行。
分块
如果说分层是从宏观上提升了渲染效率,那么分块则是从微观层面提升了渲染效率。
通常情况下,页面的内容都要比屏幕大得多,显示一个页面时,如果等待所有的图层都生成完毕,再进行合成的话,会产生一些不必要的开销,也会让合成图片的时间变得更久。
因此,合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块,这样就可以大大加速页面的显示速度。不过有时候, 即使只绘制那些优先级最高的图块,也要耗费不少的时间,因为涉及到一个很关键的因素------纹理上传,这是因为从计算机内存上传到GPU内存的操作会比较慢。
如何利用分层技术优化代码
开发中经常需要对某个元素做几何形状变换、透明度变换或者一些缩放操作,如果使用JavaScript来写这些效果,会牵涉到整个渲染流水线,所以JavaScript的绘制效率会非常低下。
这时可以使用 will-change来告诉渲染引擎会对该元素做一些特效变换,CSS代码如下:
css
.box {
will-change: transform, opacity;
}
以上代码就是提前告诉渲染引擎box元素将要做几何变换和透明度变换操作,这时候渲染引擎会将该元素单独实现一帧,等这些变换发生时,渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就大大提升了渲染的效率。这也是CSS动画比JavaScript动画高效的原因。
所以,如果涉及到一些可以使用合成线程来处理CSS特效或者动画的情况,就尽量使用will-change来提前告诉渲染引擎,让它为该元素准备独立的层。但是凡事都有两面性,每当渲染引擎为一个元素准备一个独立层的时候,它占用的内存也会大大增加,因为从层树开始,后续每个阶段都会多一个层结构,这些都需要额外的内存,所以需要恰当地使用 will-change。
页面性能,如何系统的优化页面?
所谓页面优化,其实就是指如何让页面更快地显示和响应。一个页面在它不同的阶段,所侧重的关注点是不一样的,所以页面优化,就要分析一个页面生存周期的不同阶段。
通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。
- 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和JavaScript脚本。
- 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是JavaScript脚本。
- 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。
重点关注加载阶段和交互阶段,因为影响到体验的因素主要都在这两个阶段
加载阶段
上图是一个典型的渲染流水线。并非所有的资源都会阻塞页面的首次绘制,比如图片、音频、视频等文件就不会阻塞页面的首次渲染;而JavaScript、首次请求的HTML资源文件、CSS文件是会阻塞首次渲染,因为在构建DOM的过程中需要HTML和JavaScript文件,在构造渲染树的过程中需要用到CSS文件。
能阻塞网页首次渲染的资源称为关键资源。基于关键资源,可以细化出来三个影响页面首次渲染的核心因素。
第一个是关键资源个数。关键资源个数越多,首次页面的加载时间就会越长。比如上图中的关键资源个数就是3个,1个HTML文件、1个JavaScript和1个CSS文件。
第二个是关键资源大小。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短。上图中关键资源的大小分别是6KB、8KB和9KB,那么整个关键资源大小就是23KB。
第三个是请求关键资源需要多少个RTT(Round Trip Time) 。当使用TCP协议传输一个文件时,比如文件大小是0.1M,由于TCP的特性,这个数据并不是一次传输到服务端的,而是需要拆分成一个个数据包来回多次进行传输的。RTT就是这里的往返时延,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。通常1个HTTP的数据包在14KB左右,所以1个0.1M的页面就需要拆分成8个包来传输,也就是说需要8个RTT。
结合上图来看看它的关键资源请求需要多少个RTT。首先是请求HTML资源,大小是6KB,小于14KB,所以1个RTT就可以解决了。至于JavaScript和CSS文件,这里需要注意的是,由于渲染引擎有一个预解析的线程,在接收到HTML数据之后,预解析线程会快速扫描HTML数据中的关键资源,一旦扫描到了,会立马发起请求,可以认为JavaScript和CSS是同时发起请求的,所以它们的请求是重叠的,那么计算它们的RTT时,只需要计算体积最大的那个数据就可以了。这里最大的是CSS文件(9KB),所以按照9KB来计算,同样由于9KB小于14KB,所以JavaScript和CSS资源也就可以算成1个RTT。也就是说,上图中关键资源请求共花费了2个RTT。
了解了影响加载过程中的几个核心因素之后,就可以系统性地考虑优化方案了。总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的RTT次数。
- 如何减少关键资源的个数?一种方式是可以将JavaScript和CSS改成内联的形式,比如上图的JavaScript和CSS,若都改成内联模式,那么关键资源的个数就由3个减少到了1个。另一种方式,如果JavaScript代码没有DOM或者CSSOM的操作,则可以改成async或者defer属性;同样对于CSS,如果不是在构建页面之前加载的,则可以添加媒体取消阻止显现的标志。当JavaScript标签加上了async或者defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了。
- 如何减少关键资源的大小?可以压缩CSS和JavaScript资源,移除HTML、CSS、JavaScript文件中一些注释内容,也可以通过取消CSS或者JavaScript中关键资源的方式。
- 如何减少关键资源RTT的次数?可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用CDN来减少每次RTT时长。
交互阶段
上图为交互渲染流水线示意图。交互阶段的优化,其实就是渲染进程渲染帧的速度,因为在交互阶段,帧的渲染速度决定了交互的流畅度。
大部分情况下,生成一个新的帧都是由JavaScript通过修改DOM或者CSSOM来触发的。还有另外一部分帧是由CSS来触发的。
如果在计算样式阶段发现有布局信息的修改,就会触发重排操作,然后触发后续渲染流水线的一系列操作,这个代价是非常大的。
若在计算样式阶段没有发现有布局信息的修改,只是修改了颜色一类的信息,就不会涉及到布局相关的调整,则跳过布局阶段,直接进入绘制阶段,这个过程叫重绘。不过重绘阶段的代价也是不小的。
还有另外一种情况,通过CSS实现一些变形、渐变、动画等特效,这是由CSS触发的,并且是在合成线程上执行的,这个过程称为合成。它不会触发重排或重绘,而且合成操作本身的速度就非常快,所以执行合成是效率最高的方式。
交互阶段渲染流水线中有哪些因素影响了帧的生成速度以及如何优化?
1. 减少JavaScript脚本执行时间
有时JavaScript函数的一次执行时间可能有几百毫秒,这就严重霸占了主线程执行其他渲染任务的时间。针对这种情况可以采用以下两种策略:
- 一种是将一次执行的函数分解为多个任务,使得每次的执行时间不要过久。
- 另一种是采用Web Workers。Web Workers是主线程之外的一个线程,在Web Workers中是可以执行JavaScript脚本的,不过Web Workers中没有DOM、CSSOM环境,这意味着其中是无法通过JavaScript来访问DOM的,所以可以把一些和DOM操作无关且耗时的任务放到Web Workers中去执行。
总之,在交互阶段,对JavaScript脚本总的原则就是不要一次霸占太久主线程。
2. 避免强制同步布局
正常情况下的布局操作,通过DOM接口执行添加元素或者删除元素等操作后,是需要重新计算样式和布局的,这些操作都是在另外的任务中异步完成的,这样做是为了避免当前的任务占用太长的主线程时间。而强制同步布局,是指JavaScript强制将计算样式和布局操作提前到当前的任务中。 如下代码所示就会造成强制同步布局:
javascript
function foo() {
let main_div = document.getElementById("mian_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("time.geekbang")
new_node.appendChild(textnode);
document.getElementById("mian_div").appendChild(new_node);
//由于要获取到offsetHeight,
//但是此时的offsetHeight还是老的数据,
//所以需要立即执行布局操作
console.log(main_div.offsetHeight)
}
将新的元素添加到DOM之后,又调用了main_div.offsetHeight来获取新main_div的高度信息。如果要获取到main_div的高度,就需要重新布局,所以这里在获取到main_div的高度之前,JavaScript还需要强制让渲染引擎默认执行一次布局操作。
3. 避免布局抖动
布局抖动,是指在一次JavaScript执行过程中,多次执行强制布局和抖动操作。如下代码所示,在一个for循环语句里面不断读取属性值,每次读取属性值之前都要进行计算样式和布局,这会大大影响当前函数的执行效率。这种情况的避免方式和强制同步布局一样,都是尽量不要在修改DOM结构时再去查询一些相关值。
ini
function foo() {
let time_li = document.getElementById("time_li")
for (let i = 0; i < 100; i++) {
let main_div = document.getElementById("mian_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("time.geekbang")
new_node.appendChild(textnode);
new_node.offsetHeight = time_li.offsetHeight;
document.getElementById("mian_div").appendChild(new_node);
}
}
4. 合理利用CSS合成动画
合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同,如果主线程被JavaScript或者一些布局任务占用,CSS动画依然能继续执行。所以要尽量利用好CSS合成动画,如果能让CSS处理动画,就尽量交给CSS来操作。另外,如果能提前知道对某个元素执行动画操作,那就最好将其标记为will-change,这是告诉渲染引擎需要将该元素单独生成一个图层。
5. 避免频繁的垃圾回收
JavaScript使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。所以要尽量避免产生那些临时垃圾数据,可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。
为什么会有虚拟DOM
DOM的缺陷
DOM提供了一组JavaScript接口用来遍历或者修改节点,这套接口包含了getElementById、removeChild、appendChild等方法。通过JavaScript操纵DOM是会影响到整个渲染流水线的,比如常见的重绘、重排、合成等,对于DOM的不当操作还有可能引发强制同步布局 和布局抖动的问题,这些操作都会大大降低渲染效率。
react中的虚拟DOM
以react中的虚拟DOM有两个执行阶段:
- 创建阶段。首先依据JSX和基础数据创建出来虚拟DOM,它反映了真实的DOM树的结构。然后由虚拟DOM树创建出真实DOM树,真实的DOM树生成完后,再触发渲染流水线往屏幕输出页面。
- 更新阶段。如果数据发生了改变,那么就需要根据新的数据创建一个新的虚拟DOM树;然后React比较两个树,找出变化的地方,并把变化的地方一次性更新到真实的DOM树上;最后渲染引擎更新渲染流水线,并生成新的页面。
双缓存
在开发游戏或者处理其他图像的过程中,屏幕从前缓冲区读取数据然后显示。但是很多图形操作都很复杂且需要大量的运算,比如一幅完整的画面,可能需要计算多次才能完成,如果每次计算完一部分图像,就将其写入缓冲区,那么显示一个稍微复杂点的图像的过程中,看到的页面效果可能是一部分一部分地显示出来,因此在刷新页面的过程中,会让用户感受到界面的闪烁。
而使用双缓存,可以让你先将计算的中间结果存放在另一个缓冲区中,等全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据一次性复制到显示缓冲区,这样就使得整个图像的输出非常稳定。在这里,可以把虚拟DOM看成是DOM的一个buffer,和图形显示一样,它会在完成一次完整的操作之后,再把结果应用到DOM上,这样就能减少一些不必要的更新,同时还能保证DOM的稳定输出。
MVC模式
MVC的整体结构比较简单,由模型、视图和控制器组成,其核心思想就是将数据和视图分离,视图和模型之间是不允许直接通信的,它们之间的通信都是通过控制器来完成的。通常情况下的通信路径是视图发生了改变,然后通知控制器,控制器再根据情况判断是否需要更新模型数据。当然还可以根据不同的通信路径和控制器不同的实现方式,基于MVC又能衍生出很多其他的模式,如MVP、MVVM等,不过万变不离其宗,它们的基础骨架都是基于MVC而来。
上图为基于React和Redux构建MVC模型。在该图中,可以把虚拟DOM看成是MVC的视图部分,其控制器和模型都是由Redux提供的。其具体实现过程如下:
- 图中的控制器是用来监控DOM的变化,一旦DOM发生变化,控制器便会通知模型,让其更新数据;
- 模型数据更新好之后,控制器会通知视图,告诉它模型的数据发生了变化;
- 视图接收到更新消息之后,会根据模型所提供的数据来生成新的虚拟DOM;
- 新的虚拟DOM生成好之后,就需要与之前的虚拟DOM进行比较,找出变化的节点;
- 比较出变化的节点之后,React将变化的虚拟节点应用到DOM上,这样就会触发DOM节点的更新;
- DOM节点的变化又会触发后续一系列渲染流水线的变化,从而实现页面的更新。