前端面试常考的有关浏览器方面的知识

1,392 阅读15分钟

浏览器渲染机制/浏览器渲染过程

1. 创建/更新DOM树和请求css/image/js: 浏览器请求HTML代码后 在生成DOM树最开始的阶段(字节 -> 字符) 并行发起css/image/js请求

2. 创建/更新 CSSOM树: CSS文件下载 开始构建CSSOM树

3. 创建/更新 渲染树: 所有CSS文件下载完成后 CSSOM树构建结束和DOM树一起生成渲染树

4. 布局: 通过渲染树 浏览器已经知道网页有哪些节点 各个节点CSS定义以及它们的从属关系 计算出每个节点在屏幕中的位置

5. 绘画: 根据布局 按照算出来的规则 通过显卡 把内容画到屏幕上


以上五个步骤 前三步都有创建/更新是因为DOM/CSSOM/渲染树可能在第一次绘画后多次更新 比如JS修改了DOM/CSS 布局和绘画也会被重复执行 除了DOM/CSSOM更新原因外 图片下载完成后也需要调用布局和绘画来更新网页

解析HTML文件的过程

1. 创建Document对象, 开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段的document.readyState = 'loading';

2. 遇到link外部的css,创建线程加载,并继续解析文档。

3. 遇到script外部的js,并且没有设置async/defere,浏览器加载,并阻塞, 等待js加载完成并执行该脚本,然后继续解析文档。

4. 遇到script外部的js,并且设置async/defere,浏览器创建线程加载,并继续解析文档。对于async属性的脚本,脚本加载完成后立即执行。(异步禁止使用document.write())

5. 遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。

6. 当文档解析完成后,document.readyState = 'interactive' // domTree刚建立

7. 文档解析完成后,所以设置有defere的脚本会按照顺序执行(注意与async的不同,但同样禁止使用document.write())。

8. document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段 // 可以监听用户的输入事件/监听事件。

9. 当所有的async的脚本加载完成并执行后,img等加载完成后 document.readyState = 'complete',window对象触发Load事件。

10. 从此,以异步响应方式处理用户输入,网络事件等 

分三步
1.创建Document对象
2.解析文档
3.文档加载完并执行完

思考解析HTML文件中引发的问题?

1. 加载JS和CSS会阻塞浏览器的渲染吗 下载JS和CSS会阻塞吗
2. js加载会阻塞dom的解析吗,js加载会阻塞dom的渲染吗 ?
3. css加载会阻塞dom的解析吗,css加载会阻塞dom的渲染吗 ?
4. 为什么生成了CSSOM树这样的结构?

加载JS和CSS会阻塞浏览器的渲染吗 下载JS和CSS会阻塞吗

加载JS会阻塞浏览器渲染 加载CSS不会阻塞浏览器渲染 因为CSS加载是异步的

下载JS和CSS不会阻塞浏览器渲染 因为加载有个下载和解析的过程 而下载只是下载这个文件 不会读取里面的内容

js加载会阻塞dom的解析吗,js加载会阻塞dom的渲染吗 ?

js会阻塞dom的解析 但js不会阻塞dom的渲染 因为此时js代码已经全部执行完成了

css加载会阻塞dom的解析吗,css加载会阻塞dom的渲染吗 ?

1. 整个页面只引入一个外部css文件 css是不阻塞dom解析的 因为DOM解析和CSS解析是两个并行的过程 但是css会阻塞dom渲染的 因为渲染树是依赖于DOM树和CSSOM树 它必须等待到CSSOM树构建完成 也就是CSS资源加载完成或者CSS资源加载失败后 才能开始渲染 

2. css页面后面还有外部js文件 css是阻塞了dom的解析的 这是主要是因为css后面还有js 考虑到js可能会改变css属性 所有必须等到它前面的css执行完毕

img会阻塞dom的解析吗,img会阻塞dom的渲染吗 ?

1、没有外部css文件 img对dom的解析没有阻塞 但是会阻塞dom的渲染(而所有通过src属性导入的元素,都会导致页面渲染的堵塞。)

2、有外部css文件

为什么生成了CSSOM树这样的结构?

浏览器的优化方式/性能优化?

前端优化的途径有很多,大致可以分为两类:页面级优化和代码级优化。

页面级优化有减少HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等等。

代码级优化有DOM操作优化、CSS选择符优化、图片优化以及HTML结构优化等等。

一丶页面级优化

1. 减少HTTP请求数(最重要最有效)       

 一个完整的请求都需要DNS寻址丶与服务器建立连接丶发送数据丶等待服务器响应丶接受数据这样一个漫长而复杂的过程 由于浏览器进行并发请求的请求数都是有上限的 因此请求数多了以后 浏览器需要分批进行请求 因此会增加用户的等待时间 会给用户造成网站速度慢这样的一个印象 即使可能用户能看到的第一屏的资源都已经请求完了 但是浏览器的进度条一直在加载

(1)从设计实现层面简化页面

如果你的页面和百度搜索的页面一样简单,那么也就不需要什么优化操作了。因此保持页面简洁、减少资源的使用是最直接的。

(2)合理设置HTTP缓存

缓存的力量是强大的,恰当的设置缓存可以大大减少HTTP请求

怎样才算是合理的设置?原则很简单:能缓存越多越久越好。

例如:很少变化的图片资源就可以直接通过HTTP Header中的Expires设置一个很长的过期头;变化不频繁而又可能会变的资源可以使用Last-Modified来做请求验证。尽可能的让资源能够在缓存中待的更久。

(3)资源合并与压缩

如果可以的话,尽可能的将外部脚本、样式进行合并,尽可能地合并为一个。另外,CSS、JS、Image都可以用相应的工具进行压缩,压缩后往往能节省不少空间。或者使用Webpack等前端工程化工具来进行代码的压缩和去重。

(4)使用雪碧图

雪碧图又叫做精灵图,我们可以把网站中需要用到的一些icon,全部放到一个图片资源中,然后通过改变位置来获取需要的图片,这样合并CSS图片,就可以大幅度减少HTTP请求数了。 

(5)内联图片使用 

data: URL scheme的方式将图片嵌入到页面或 CSS中,如果不考虑资源管理上的问题的话,不失为一个好办法。如果是嵌入页面的话换来的是增大了页面的体积,而且无法利用浏览器缓存。使用在 CSS中的图片则更为理想一些。

(6) 懒加载

这条策略实际上并不一定能减少 HTTP请求数,但是却能在某些条件下或者页面刚加载时减少 HTTP请求数。对于图片而言,在页面刚加载的时候可以只加载第一屏,当用户继续往后滚屏的时候才加载后续的图片。这样一来,假如用户只对第一屏的内容感兴趣时,那剩余的图片请求就都节省了。

首页 曾经的做法是在加载的时候把第一屏之后的图片地址缓存在 Textarea标签中,待用户往下滚屏的时候才 “惰性” 加载。

(6)瀑布流

其实懒加载并不能减少HTTP请求数,他只是可以减少页面刚加载的时候的HTTP请求数,总数是不变的。对于图片而言,在页面刚加载的时候可能只加载第一屏的图片,随着用户的滚动才会继续加载后面的图片资源,这种瀑布流的加载方式就可以有效提高性能。

2. 将外部脚本放在底部

前文有谈到,浏览器是可以并发请求的,这一特点使得其能够更快的加载资源,然而外链脚本在加载时却会阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。解决这一问题的方法有很多,而最简单可依赖的方法就是将脚本尽可能的往后挪,减少对并发下载的影响。

3. 并发执行内联脚本

使用 script元素的defer 属性(存在兼容性问题和其他一些问题,例如不能使用document.write()、使用setTimeout ,此外,在HTML5中引入了 Web Workers的机制,恰恰可以解决此类问题。 

4. 懒加载

只需要在需要资源的时候才加载资源,不需要的时候就不加载资源。

5. 将CSS放在 HEAD中

如果将 CSS放在其他地方比如 BODY中,则浏览器有可能还未下载和解析到 CSS就已经开始渲染页面了,这就导致页面由无 CSS状态跳转到 CSS状态,用户体验比较糟糕。除此之外,有些浏览器会在 CSS下载完成后才开始渲染页面,如果 CSS放在靠下的位置则会导致浏览器将渲染时间推迟。

6. 把js放到整个页面的底部,另一个方法是在script 标签上加一个 defer属性 保证让浏览器把脚本下载出来后,然后等到页面渲染完毕再执行。(参考权威指南 js时间线)

7. CSS文件用link标签加载 是阻塞状态的,我们可以使用loadCss小工具库来进行异步css文件加载,但是有一个问题是,全部异步加载css,页面最开始呈现出的只是单纯的html ,不是很好看 所以我们要选定一个关键的css文件,用critical工具来自动提取压缩关键的css. (百度自行查看使用方法)

8. 减少不必要的HTTP跳转

对于以目录形式访问的HTTP链接,很多人都会忽略链接最后是否带’/’,假如你的服务器对此区别对待的话,那么你也需要注意了,这其中很可能隐藏了301跳转,增加了多余请求。

 9. 避免重复的资源请求

这种情况主要是由于在模块化开发时,我们的不同模块之间可能有相同的部分,导致资源的重复请求

二丶 代码级优化

1. DOM 

 DOM操作应该是脚本中最耗性能的一类操作

(1)HTMLCollection(HTML收集器,返回的是一个数组的内容信息)因为是这个集合并不是一个静态的集合,它表示的仅仅是一个特定的查询,每次访问该集合时都会重新执行这个 查询从而更新查询结果。所谓的“访问集合”包括读取集合的length属性、访问集合中的元素

(2)Reflow&Repaint 

 减少页面的重绘和重排。

2. 慎用with

with会改变作用域链,有可能导致我们的作用域链变长,导致查询性能下降。

3. 避免使用eval和Function

每次 eval 或 Function 构造函数作用于字符串表示的源代码时,脚本引擎都需要将源代码转换成可执行代码。这是很消耗资源的操作—— 通常比简单的函数调用慢 100倍以上。

4. 减少作用域链查找

如果在循环中需要访问非本作用域下的变量的时候,请遍历之前用局部变量缓存的变量,并在遍历结束之后重写这个缓存变量

5. 数据访问

js中对直接量和局部变量的访问时最快的,对对象属性以及数组的访问需要更大的开销,当出现下面的情况的时候,建议将数据放入局部变量:

(1)对任何对象属性的访问超过1次

(2)对任何数组成员的访问次数超过1次

另外,要尽可能的减少对对象以及数组的深度查找。

6. 字符串拼接

字符串的拼接尽可能少的使用“+”,这种方式的效率是十分低下的,因为每次运行都会开辟新的内存并生成新的字符串变量,然后将拼接的结果赋值给新变量。

建议使用的是先转化为数组,然后通过数组的join方法来连接成字符串。不过由于数组也有一定的开销,因此就需要权衡一下,当拼接的字符串比较少的时候,可以考虑用“+”的方式,比较多的时候就需要考虑用数组的join方法了。


重绘和重排:页面有三个树:DOMTree、CSSTree、renderTree。(实际上多于三个),renderTree上有两个规则:repaint和reflow,重绘和重排。

重绘(repaint)是元素自身的位置和宽高不变,只改变颜色的之类的属性而不会导致后面的元素位置的变化的时候,renderTree发生的动作。

重排(reflow)是元素自身的位置或者宽高改变了从而导致的整个页面的大范围移动的时候,renderTree发生的动作。所以我们在DOM操作的时候,要尽量避免重排。

浏览器缓存

1. cookie

什么是cookie?

cookie是由服务端生成的 发送给User-Agent(一般是浏览器) (服务器告诉浏览器设置一下cookie) 浏览器会将cookie以key/value形式保存到某个目录下的文本文件内 下次请求同一网站时就发送该cookie给服务器(前提是浏览器设置为启动cookie)

cookie就是一个小型文件(浏览器对cookie的内存大小是有限制的 --- 用来设置一些信息)

为什么会有cookie?

Web应用程序是使用HTTP协议传输数据的 HTTP协议是无状态的协议 一旦数据交换完毕 客户端和服务端的连接就会关闭 再次交换数据需要建立新的连接 这就意味着服务器无法从连接上跟踪会话

cookie的特点?

cookie有保质期

满足同源策略 (不同域的话可以在服务端设置document.domain或path来实现共享)

cookie内存大小受限制(cookie有个数和大小的限制 大小一般是4K)

注意?

cookie在本地 可以被更改的文件 敏感数据不要放在cookiel里

// 自己封装设置cookie的代码
var manageCookie = {    setCookie: function(name, value, time) {        document.cookie = name + '=' + value + ';max-age' + time;        return this;    },    removeCookie: function(name) {        return this.setCookie(name, '', -1);    },    getCookie: function(name, callback) {        var allCookieArr = document.cookie.split('; ');        for(var i = 0; i < allCookieArr.length; i++) {            var itemCookieArr = allCookieArr[i].split('=');            if(itemCookieArr[0] === name) {                callback(itemCookieArr[1]);                return this;            }        }        callback(undefined);        return this;    }}

2. http缓存

http缓存是基于HTTP协议的浏览器文件级缓存机制

即针对文件的重复请求情况下 浏览器可以根据协议头判断从服务器端请求文件还是从本地读取文件 chrome控制台下的Frames即展示的是浏览器的http文件级缓存 

以下是浏览器缓存的这个机制流程 主要针对重复的http请求 在有缓存的情况下判断过程主要分为3步

判断expires 如果未过期 直接读取http缓存文件 不发http请求 否则进入下一步

判断是否含有etag 有则带上if-none-match发送请求 未修改返回304 修改返回200 否则进入下一步 

判断是否含有last-modified 有则带上if-modifined-since发送请求 无效返回200 有效返回304 否则直接向服务器请求

如果通过etag和last-modifined判断 即使返回304有至少有一次http请求 只不过返回的是304的返回内容 而不是文件内容 所以合理设置实现expires参数可以减少较多的浏览器请求

3. localstorage

localStorage是HTML5的一种新的本地缓存方案 目前用的比较多 一般用来存储Ajax返回的数据 加快下次页面打开时的渲染速度

常用API

localStorage.setItem(key, value); // 设置记录

localStorage.getItem(key); // 获得记录

localStorage.removeItem(key); // 删除该域名下单条记录

localStorage.clear(); // 删除该域名下所有记录

注意: localStorage大小有限制 不适合存放过多的数据 如果数据存放超过最大限制会报错 并移除最先保存的数据

4. sesstionStorage

sesssionStorage和localStorage类似 但是浏览器关闭则会全部关闭 api和localStorage相同


.......未完待续

你的点赞是我持续输出的动力 希望能帮助到大家 互相学习 有任何问题下面留言 一定回复