浏览器
一般考察架构师的岗位时,不管是前端还是后端,浏览器相关的话题都极有可能会聊到。
浏览器的主要构成
- 用户界面:显示请求页面的主窗口、地址栏、后退/前进按钮、书签目录等;
- 浏览器引擎:用来查询及操作渲染引擎的接口;
- 渲染引擎:用来显示请求的内容;
- JS引擎:用来解释执行JS代码。
- 网络:用来完成http请求等网络调用;
- UI后端:用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
- 数据存储:属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据;
谈谈浏览器的 js引擎
页面的呈现主要靠浏览器内核,而浏览器内核主要说的是渲染排版引擎,但页面要想实现人机交互,还需要JS 引擎来解析和执行JS语言来实现网页的动态效果。
同样,对于不同的浏览器,也有不同的js引擎:
- chrome:使用v8引擎;
- safari:使用JavaScriptCore引擎;
- Firefox:使用SpiderMonkey引擎;
- Edge和IE: 使用的Chakra(forJScript)引擎。
浏览器渲染主流程
浏览器获取到html代码后,内核会做以下工作:
- 构建DOM树(Parse html)
- 构建CSSOM树(Recaculate Style)
- 合并DOM树与CSSOM树为Render树
- 布局(Layout)
- 绘制(Paint)
- 复合图层化(Composite)
事件的触发过程?
事件触发有三个阶段:
- window 往事件触发处传播,遇到注册的捕获事件会触发
- 传播到事件触发处时触发注册的事件
- 从事件触发处往 window 传播,遇到注册的冒泡事件会触发
谈谈跨域?
跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。
浏览器出于安全考虑(主要是用来防止 CSRF 攻击的),有同源策略,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。
解决跨域的方式有:
- JSONP: 利用
<script>
标签没有跨域限制的漏洞,使用简单且兼容性不错,但是只限于 get 请求; - CORS 需要浏览器和后端同时支持, 浏览器会自动进行 CORS 通信,只要后端(设置 Access-Control-Allow-Origin)实现了 CORS,就实现了跨域;
- document.domain: 只能用于二级域名相同的情况;
- postMessage: 用于获取嵌入页面中的第三方页面数据。
cookies,sessionStorage和localStorage的区别?
- 共同点:都是保存在浏览器端,且是同源的。
- 区别:
- cookies是为了标识用户身份而存储在用户本地终端上的数据,始终在同源http请求中携带,即cookies在浏览器和服务器间来回传递,而sessionstorage和localstorage不会自动把数据发给服务器,仅在本地保存。
- 存储大小的限制不同。cookie保存的数据很小,不能超过4k,而sessionstorage和localstorage保存的数据大,可达到5M。
- 数据的有效期不同。cookie在设置的cookie过期时间之前一直有效,即使窗口或者浏览器关闭。sessionstorage仅在浏览器窗口关闭之前有效。localstorage始终有效,窗口和浏览器关闭也一直保存,用作长久数据保存。
- 作用域不同。cookie在所有的同源窗口都是共享;sessionstorage不在不同的浏览器共享,即使同一页面;localstorage在所有同源窗口都是共享
谈谈JS阻塞
JS 引擎是独立于渲染引擎存在的。我们的 JS 代码在文档的何处插入,就在何处执行。
当 HTML 解析器遇到一个 script 标签时,它会暂停渲染过程,将控制权交给 JS 引擎。JS 引擎对内联的 JS 代码会直接执行,对外部 JS 文件还要先获取到脚本、再进行执行。
等 JS 引擎运行完毕,浏览器又会把控制权还给渲染引擎,继续 CSSOM 和 DOM 的构建。
因此与其说是 JS 把 CSS 和 HTML 阻塞了,不如说是 JS 引擎抢走了渲染引擎的控制权。
浏览器之所以让 JS 阻塞其它的活动,是因为它不知道 JS 会做什么改变,担心如果不阻止后续的操作,会造成混乱。
一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。
通过审时度势地向 script 标签添加 async/defer,我们就可以告诉浏览器在等待脚本可用期间不阻止其它的工作,这样可以显著提升性能。
谈谈回流和重绘?
回流(也叫重排):当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来的过程。
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式的过程。
重绘不一定导致回流,回流一定会导致重绘。回流比重绘做的事情更多,带来的开销也更大。
要避免回流与重绘的发生,最直接的做法是避免掉可能会引发回流与重绘的 DOM 操作。
如何设计一个JS引擎?
JavaScript引擎是执行 JavaScript 代码的程序或解释器,一些实现JavaScript引擎的流行项目的列表:
- V8 — 开源,由 Google 开发,用 C ++ 编写
- Rhino — 由 Mozilla 基金会管理,开源,完全用 Java 开发
- SpiderMonkey — 是第一个支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用
- JavaScriptCore — 开源,由苹果为Safari开发
- KJS — KDE 的引擎,最初由 Harri Porten 为 KDE 项目中的 Konqueror 网页浏览器开发
- Chakra (JScript9) — Internet Explorer
- Chakra (JavaScript) — Microsoft Edge
- Nashorn, 作为 OpenJDK 的一部分,由 Oracle Java 语言和工具组编写
- JerryScript — 物联网的轻量级引擎
- QuickJS-- Fabrice Bellard 大神开源的一个轻量且可嵌入的 JavaScript 引擎
简单地说,JavaScript引擎能够“读懂”JavaScript代码,并准确地给出代码运行结果的一段程序。
学过编译原理的人都知道,对于静态语言来说(如Java、C++、C),处理上述这些事情的叫编译器(Compiler),相应地对于JavaScript这样的动态语言则叫解释器(Interpreter)。两者的区别:
- 编译器是将源代码编译为另外一种代码(比如机器码,或者字节码);
- 解释器是直接解析并将代码运行结果输出。
JavaScript引擎组成
- 编译器:将源代码编译成抽象语法树,在某些引擎中还包含将抽象语法树转换成字节码;
- 解释器:接受字节码,解释执行这个字节码;
- JIT工具:将字节码或者抽象语法树转换成本地代码;
- 垃圾回收器和分析工具(profiler):负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。
所以,设计一个JS引擎,在掌握基本原理后,基于优秀的开源引擎进行二次开发时最好的方式。