浏览器页面加载过程

当用户输入网址之后浏览器究竟干了什么,当然先是域名解析,然后建立链接......网络层面不是我想说的重点,好多前辈大牛都讲过,就一笔带过,着重说的是浏览器在拿到HTML、CSS和JS还有image等等素材之后怎么渲染成我们要看的页面的(google,百度了好多关键词,很多都把细节讲穿了,讲得太细了脑海里没有整体的概念不太容易整明白,所以抛砖引玉的写写)。

1.DNS域名解析

我们在浏览器输入网址,其实就是要向服务器请求我们想要的页面内容,所有浏览器首先要确认的是域名所对应的服务器在哪里。将域名解析成对应的服务器IP地址这项工作,是由DNS服务器来完成的。

打个不太恰当的比方,现在注册账号都要绑定手机号,网站不一定要输入用户名,只要有手机号就能注册然后登录。有的人说我新手机号记不住,可以,你自己搞个记得住的昵称也可以登录。域名就是你的昵称,ip地址就是你的手机号,网站必须要有ip地址才能访问,DNS解析就是通过昵称找到ip地址然后访问网站。

客户端收到你输入的域名地址后,它首先去找本地的hosts文件,检查在该文件中是否有相应的域名、IP对应关系,如果有,则向其IP地址发送请求,如果没有,再去找DNS服务器。一般用户很少去编辑修改hosts文件。

之前流行过一种科学上网方式,就是修改这个文件的ip对应关系来实现的,不过现在GFW再次加固了,能用的ip越来越少,这种武功秘籍已经绝迹江湖了。

2.建立TCP连接

费了一顿周折终于拿到服务器IP了,与服务器建立TCP连接

3.发送HTTP请求

与服务器建立了TCP连接后,就可以向服务器发起请求了。

4.服务器处理请求

服务器端收到请求后的由web服务器处理请求,诸如Apache、Ngnix、IIS等。web服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过web服务器返回给浏览器客户端。

5.返回响应结果
6.关闭TCP连接

为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。

网络阶段,完


7.浏览器解析HTML
1.构建DOM Tree

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM Tree。

遇到<script>标签的时候,会立即解析脚本,停止解析文档(因为JS可能会改动DOM和CSS,所以继续解析会造成浪费)。如果脚本是外部的,会等待脚本下载完毕,再继续解析文档。现在可以在script标签上增加属性 defer或者async,强行跳过html渲染完成之后再加载。脚本解析会将脚本中改变DOM和CSS的地方分别解析出来,追加到DOM Tree和Style Rules上。 我们可以看到这回有两个问题,一个是如果代码太多,加载编译运行时间太长,页面就卡住了,而是代码中操作dom元素或者css,如果页面没有加载完,可能有些dom获取不到,刚开始工作时就有一条金科玉律,<script>放在页面底部。

2.构建CSSOM

浏览器的CSS Parser将CSS解析成Style Rules,Style Rules也叫CSSOM(CSS Object Model)。 StyleRules也是一个树形结构,根据CSS文件整理出来的类似DOM Tree的树形结构。CSS的渲染和HTML渲染是同步进行的,所以越快加载出来越好,上面的金科玉律后一半就是css放在页面顶部。

3.构建Render Tree

DOM Tree和CSSOM我们便可以构建Render Tree。浏览器会先从DOM Tree的根节点开始遍历每个可见节点。对每个可见节点,找到其适配的CSS样式规则并应用。这中间包括了两个步骤,样式计算和样式覆盖

样式计算

Render Tree上每个节点都绑定好了,包括浏览器默认样式表,自定义样式表,inline样式元素,HTML可视化属性如:width=100。后者将转化以匹配CSS样式,需要加以计算。

样式覆盖 同样的标签可能被重复定义,也可能被泛式的定义所影响,也有可能根本没有被定义。权重高的定义方式覆盖权重低的,得到最终呈现的样式,这一个过程可以通过控制台看到一些细节。

4.布局(Layout)

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

通俗的讲就是开会排座位,了解到有哪些领导要来,把这些领导的位置排排好。

5.绘制(Painting)

在绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。

上一步是把领导位置订好了,这一步就是领导入座(要是把局长排在处长的后面,你这浏览器可以不用干了)。

8.JS的加载

准确说JS的加载是HTML的加载的一部分,一开始网页加载只有HTML,后来发展出了JS。但是现在JS的发展显然使其灵活性、可塑性比HTML要高(个人想法),所以想单独拿出来讲。

我们口中的JS其实分为三部分,ECMAScript、DOM、BOM。

习惯把ECMAScript和JS说在一起,其实它是JS最核心的一部分。浏览器是ECMAScript的宿主环境之一(nodeJS也是宿主环境)。

DOM是浏览器这个宿主环境针对程序开发的扩展,也有其他语言可以操作DOM,但是JS的发展最完善,其他方法也就被沉了。

BOM这个可能有些人不了解,DOM是文章对象模型,直白点就是浏览器HTML这一部分。但是浏览器就是一个承载HTML的容器,针对这个容器本身的操作就是BOM浏览器对象模型,包括了信息提示(指突破沙盒的操作系统层面的信息提示)、获取显示器分辨率等等功能。当然,BOM这个概念也是H5出来之后才清晰起来,本身这些标准就受制于浏览器厂商的实现,BOM目前还没个谱儿。

讲了这么多铺垫,开始讲JS的加载。

1.创建window对象

window对象也叫全局执行环境,当页面产生时就会被创建,所有的全局变量和函数都属于window的属性和方法,而DOM Tree也会映射在window的doucment对象上,我们在JS中针对dom的操作都是通过这个document对象(这个对象的执行效率比较低,这也是促使react和vue诞生的原因)。当关闭网页或者关闭浏览器时,全局执行环境会被销毁,包括其内部所有成员都被销毁。

2.加载文件

没啥好讲的,加载文件,完了js引擎分析它的语法与词法是否合法,如果合法进入预编译

3.预编译

解释名词,预编译。

先要说解释型语言和编译型语言,二者区分没有制定标准,看看wiki上的解释:

编译型语言是代码在运行前编译器将人类可以理解的语言(编程语言)转换成机器可以理解的语言。用编译语言写成的程序,在运行期的运行速度,通常比用解释型语言写的程序快。因为程序在编译期,已经被预先编译成机器代码,可以直接运行,不用像解释型语言一样,还要多一道直译程序。

解释型语言会将代码一句一句直接运行,不需要像编译语言一样,经过编译器先行编译为机器代码,之后再运行。这种编程语言需要利用解释器,在运行期,动态将代码逐句解释为机器代码,或是已经预先编译为机器代码的的子程序,之后再运行。

许多人认为解释型语言意味着当遇到程序中行号为xyz时直接将其传给CPU就能运行;但是事实不是这样。所有的编程语言都是为人类创建的。他们是人类能够理解的。你必须将编程语言转换为机器语言。而什么时候通过什么方式转换,就是二者的区别。

举个典型的例子,考虑下面这一段代码。

for(i=0; i>1000; i++){
    sum += i;
}
复制代码

在编译型语言中sum += i部分在循环运行时已经编译成了机器码,机器码将直接运行一千次。

但是在解释型语言中,他会在执行时将sum += i解释一千次。所以因为对相同的代码进行一千次转换会造成非常大的性能损耗。

所以两者区别大概就清楚了,编译语言编译时间长,但是运行快,而且不需要浏览器这样的容器就能跑。解释型语言不要编译直接跑,但是没法优化,跑起来慢,而且需要载体。因为现在各种设备种类太多的问题,反而需要容器的成为优点了,因为浏览器替我们解决了程序兼容,减少开发时间。

扯远了,搞编译语言的大佬们为了改善编译语言的效率而发展出的即时编译技术,减少编译时间(我觉得后端的本地运行就是这样的,我不懂,纯属yy的)

浏览器也不傻,你搞了快速编译那我也搞个预编译,要是真的像上面那个例子一样1000次的循环转换1000次那不是比IE还慢。

在预编译的过程中,浏览器会寻找全局变量声明,把它作为window的属性加入到window对象中,并给变量赋值为'undefined';寻找全局函数声明,把它作为window的方法加入到window对象中,并将函数体赋值给他(匿名函数是不参与预编译的,因为它是变量)。

这个过程涉及到很多优化,这两点是明显的能被我们感知到的,我的理解是这个过程中,浏览器替变量和函数开辟好了内存空间,等着后续的操作。而变量提升作为不合理的地方在ES6中已经解决了,函数提升还存在。

4.解释执行

执行到变量就赋值,如果变量没有被定义,也就没有被预编译直接赋值,在ES5非严格模式下这个变量会成为window的一个属性,也就是成为全局变量。string、int这样的值就是直接把值放在变量的存储空间里,object对象就是把指针指向变量的存储空间。函数执行,就将函数的环境推入一个环境的栈中,执行完成后再弹出,控制权交还给之前的环境。仔细脑补这个过程,JS作用域其实就是这样的执行流机制实现的。

完事,可能有一些不太严谨的说法,我只是抛砖引玉,给大家面试吹牛的时候被问到这个问题可以有一个大概的认知。大牛们看到了错漏之处不要笑,请指出,感谢。

参考

JavaScript高级程序设计

浏览器渲染页面过程与页面优化

wiki百科

MDN

转载于:https://juejin.im/post/5cbef7f1f265da03981fc709

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值