ECMAScript+ 面试宝典

浏览器

  • localStorage、sessionStorage、cookie、Session
  • Web Worker,Web Storage
  • http 长链接
  • 浏览器渲染流程
  • 浏览器多进程与 JavaScript 单线程
  • 浏览器抓包
  • 三次握手、四次挥手
  • 服务器代理、document.domain、postMessage
  • 存储:cookie、sessionStorage、localStorage、indexDB、ServiceWorker
  • 渲染、重绘

基础§

浏览器有什么特点?§

  • 可用于使用万维网;
  • 提供丰富多彩的用户界面:其中包括上下页、刷新、地址栏、书签、显示源码等功能;
  • 支持解析多种网页标准:HTML、HTML5、CSS、SVG、XHTML、WebGL、JavaScript 和 MathML 等;
  • 支持多种文件格式及协议:可以通过浏览器打开特定格式的文件进行查询、编辑等操作,且提供 HTTPS、FTP 等网络协议的支持;
  • 可通过多个窗口或多个标签页同时打开多种由统一资源标识符标志的信息资源:网络、图片、影音等;
  • 可通过开放开发的浏览器插件来拓展浏览器功能。

浏览器两轮大战背景?§

  • 第一轮概要,Netscape 对抗新 IE
    • 围绕在二十世纪末期——人们开始注意到万维网
    • 当时市场及网页浏览标准以 Netscape 主导。Netscape 曾改进了史前浏览器 Mosaic 的实用性和稳定性,并在网络上提供免费试用版。
    • 用户界面友好的微软公司争取到 Mosaic 授权,IE 浏览器登场。
    • IE 浏览器从技术层面通过最先支持 CSS、新增网页动态加载及图片位置改变等优势,逐步提高了市场占有率。
    • 一场要求网页无论使用 IE 或 Netscape 均能正常浏览的 “可用任何浏览器浏览”(Viewable With Any Browser)运动悄然崛起。
    • 微软公司使用诸多商业手段与公司规模较小的 Netscape 公司争抢浏览器用户。
    • Netscape 浏览器市场占用率从 90% 跌至 IE 占有率之下。
    • Netscape 公司落败,被美国在线公司收购。
  • 第二轮概要,旧 IE 对抗各大新浏览器
    • IE 浏览器寡占市场,暴露问题:使用专属格式,不尊重网页公开标准;由于高占有率,成为电脑蠕虫病毒攻击的主要目标之一。
  • Netscape 浏览器衰落之时开发浏览器源码,酝酿出安全性较高的 Firefox 浏览器。
  • 2012 年报告,Chrome 市场占有率达 33%,超过 IE 浏览器,成为全球第一大。
  • 总结,使用尽可能新的浏览器
    • 第一,旧版浏览器通常无法更新最新的安全补丁和功能,因而极易受到攻击。
    • 第二,网络的发展十分迅速。旧版浏览器可能无法支持当今网站和网络应用程序中的许多最 新功能。
    • 第三,旧版浏览器阻碍了网络创新的步伐。如果大量的互联网用户都墨守旧版浏览器,网络 开发人员就不得不在设计网站时兼顾新旧技术。

什么是浏览器的沙盒模型?§

内容安全策略 (CSP, Content Security Policy) 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入等攻击。 这些攻击可用于实现从数据窃取到网站破坏或作为恶意软件分发版本等用途。

例如,“Google Chrome 浏览器”采用了“安全浏览”技术,这种技术也应用于其他几种现代浏览器。当您浏览网页时,浏览器会将每个网页与疑似存在网上诱骗和恶意软件的网站列表进行快速比对。这个列表会在您的本地计算机上进行存储和维护,从而帮助您保护浏览隐私。如果从本地列表中找到了匹配项,浏览器就会向 Google 发送一条请求,以获取更多信息。(这条请求是完全加密的,不会以纯文本形式发送。)如果 Google 通过验证确定是匹配项,“Chrome 浏览器”就会显示一个红色警告页面,提醒您尝试访问的网页可能存在风险。浏览器获取和载入网站时,如何通过扩展验证证书验证网站的身份。

浏览器兼容性有什么需要注意的?§

  • 在确定问题原因和有问题的浏览器后,使用单独的样式表,仅供出现问题的浏览器加载。这种方法需要使用服务器端渲染。

  • 使用已经处理好此类问题的库,比如 BootStrap。

  • 使用 Reset CSS 或 Normalize.css。

  • 优雅的降级/渐进式增强,构建基于用户体验的应用,同时确保它在旧版浏览器中正常运行。

  • 利用 caniuse.com 检查特性支持。

  • 使用 autoprefixer 自动生成 CSS 属性前缀。

  • 使用 Modernizr 进行特性检测。Modernizr 是一套 JavaScript 库,用来侦测浏览器是否支持 HTML5 与 CSS3 等规格。如果浏览器不支持,Modernizr 会使用其他的解决方法来进行模拟。

内核§

常见浏览器内核及其组成?§

浏览器 RunTime 内核 / 渲染引擎 JavaScript 引擎
Chrome Blink(b28+), Webkit(v27) V8
FireFox Gecko SpiderMonkey
Safari Webkit JavaScriptCore
Edge EdgeHTML Chakra(for JavaScript)
IE Trident Chakra(for JScript)
PhantomJS Webkit JavaScriptCore
Node - V8
Deno - V8
  • 常见的浏览器内核:
    • Trident 内核:IE、360、搜狗浏览器、MaxThon、TT、The World 等。[又称 MSHTML]。
      • Trident 内核运行在 IE 上,又称 IE 内核,是 IE 的排版引擎的名称。Trident 曾因其市场占有量庞大而不思进取,一度与 W3C 标准脱节(05 年),给了运行在 Safari、Chrome、Firefox 和 Opera 等浏览器 上的内核提供了很大的发展空间。随着微软逐步放弃 IE 浏览器品牌,Trident 内核版本也不再更新。
    • EdgeHTML 内核:运行在 Microsoft Edge 上
      • EdgeHTML 内核开启了 Trident 内核的分支,成为了替代 IE 浏览器的 Microsoft Edge 浏览器的主要排版引擎。EdgeHTML 移除所有旧版 IE 浏览器遗留下来的代码,并通过尊重网页标准、重写主要的代码以和其他现代浏览器的设计精神互通有无。
    • KHTML 内核,早期内核,Webkit 前身
      • KHTML 是由 KDE 自由软件社区所开发的 HTML 排版引擎,由 C++ 语言编写。这里提到 KHTML 是因为其是早起 Safari 的内核最终选型。Safari 开发团队因对 KHTML 作出大量的改动,逐步从 KHTML 中脱离出来,KHTML 逐渐淡出主流浏览器中。
    • Gecko 内核:火狐、FF、MozillaSuite、SeaMonkey 等。
      • Gecko 是 Netscape6 和 Firefox 的内核。Gecko 代码公开,使用该内核浏览器很多。其诞生来源于 IE 的不思进取。微软内部人员不满,与一停止更新 Netscape 的员工一起在创办 了 Mozila 后开发,常被称为 Firefox 内核,跨平台使用。
    • Presto 内核:Opera7 及以上。[Opera 内核原为:Presto,现为:Blink]。
      • Opera 浏览器早期使用的内核,Opera 在 Blink 引擎推出之后转用 Blink,其中原因包括毫无推广上的优势(主要原因)和使用 Webkit 内核的 Opera 可以兼容谷歌 Chrome 浏览器等。但换内核代价惨痛,从快速轻量化与稳定到异常卡顿与不稳定,书签同步都困难,很多用户流失。Presto 内核最终停留在了 12.17。
    • Webkit 内核:Safari、Chrome 等。 [ Chrome 的:Blink(WebKit 的分支)]。
      • Webkit 的前身是 KHTML 引擎,属于其一个开源分支,是 Safari 及早期 Chromium 、Amazon Kindle 等浏览器的默认内核。通常所说的 Webkit 不仅仅是排版引擎,其包括用来渲染 HTML 和 CSS 的 Webcore 引擎和用来解析 JS 的 JSCore。Webcore 便用来处理排版。
    • Chromium/Blink:运行在 Chrome 上
      • Chrome 浏览器的内核来源于 Webkit 的 Webcore,最终用谷歌公司自主开发的开源排版引擎 Blink 所代替;同时 Chrome 浏览器对于 JavaScript 代码的解析也使用了自己的 V8 引擎。
      • Blink 同样来自于 Webkit,据说 Blink 删除了 880w 行 webkit 代码。Blink 引擎问世后,国产各种 chrome 系的浏览器也纷纷投入 Blink 的怀抱,可以在浏览器地址栏输入 chrome://version 进行查看。
  • 浏览器内核的组成:
    • 渲染引擎。
    • JavaScript 引擎。

现代浏览器内部揭秘?§

  • 多进程架构:两个进程之间使用 IPC 进行通信
    • 优点:独立、安全
    • 缺点:不共享而冗余、最大进程树
    • 进程服务化:根据情况合并进程或拆分进程(浏览器进程有时包括 UI 线程、存储线程、网络线程)
    • 站点隔离:
      • 同一个 tab 下的跨站 iframe 使用单独的进程来渲染
      • 改变了 iframe 的通信方法
      • 提高了同源策略的安全性
  • 多进程架构及其多线程的分类:
    • 浏览器进程:顶层、地址栏、书签栏、前进后退等按钮
      • 主线程、工作线程、排版线程、光栅线程
    • 网络进程:底层网络请求和网络访问
    • GPU 进程:GPU 相关任务
    • 插件进程:控制一个网页用到的所有插件
    • 渲染进程:一个 tab 内关于网页呈现的所有事;控制所有 JS 代码
      • 构建 DOM -> 子资源加载 -> 提示浏览器如何加载资源 -> 计算样式 -> 布局树(遍历 DOM 树和 CSS 树) -> 绘制 -> 更新渲染流(动画) -> 排版(不涉及主线程,分层光栅化)
    • 存储进程
    • 工具进程

浏览器的多进程?§

  • 浏览器是多进程的
    • 主控进程,Browser 进程:浏览器的主进程(负责协调、主控),只有一个
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将 Renderer 进程得到的内存中的 Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
    • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
    • GPU 进程:最多一个,用于 3D 绘制
    • 浏览器渲染进程(内核):默认每个页面一个该进程,互不影响,该进程控制页面渲染,脚本执行,事件处理等
  • 浏览器多进程的优势
    • 避免单个 Page Crash 影响整个浏览器
    • 避免第三方插件 Crash 影响整个浏览器
    • 多进程充分利用多核优势
    • 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
  • Browser 进程和 Renderer 进程的通信过程
    • Browser 进程收到用户请求,首先需要获取页面内容,譬如通过网络下载资源,随后将该任务通过 RendererHost 接口传递给 Render 进程
    • Renderer 进程的 Renderer 接口收到消息,简单解释后,交给渲染线程,然后开始渲染
      • 渲染线程接收请求,加载网页并渲染网页,这其中可能需要 Browser 进程获取资源和需要 GPU 进程来帮助渲染
      • 可能会有 JavaScript 线程操作 DOM,这样可能会造成回流并重绘
      • 最后 Render 进程将结果传递给 Browser 进程
    • Browser 进程接收到结果并将结果绘制出来

内核的多线程机制?§

  • GUI 线程
    • 负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。
    • 当界面需要重绘 Repaint 或由于某种操作引发回流 Reflow 时,该线程就会执行
    • 注意,GUI 渲染线程与 JavaScript 引擎线程是互斥的,当 JS 引擎执行时GUI线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JavaScript 引擎空闲时立即被执行。
  • JavaScript 引擎线程
    • 也称为JS内核,负责处理 JavaScript 脚本程序。例如 V8 引擎。
    • JavaScript 引擎线程负责解析 JavaScript 脚本,运行代码。
    • JavaScript 引擎一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页(Renderer 进程)中无论什么时候都只有一个 JavaScript 线程在运行 JavaScript 程序
    • 同样注意,GUI 渲染线程与 JavaScript 引擎线程是互斥的,所以如果 JavaScript 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
  • 事件触发线程
    • 归属于浏览器而不是 JavaScript 引擎,用来控制事件循环(可以理解,JavaScript引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当 JavaScript 引擎执行代码块如 SetTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中。
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理
    • 注意,由于 JavaScript 的单线程关系,所以这些待处理队列中的事件都得排队等待 JavaScript 引擎处理(当 JavaScript 引擎空闲时才会去执行)
  • 定时触发器线程
    • 传说中的 SetInterval 与 SetTimeout 所在的线程
    • 浏览器定时计数器并不是由 JavaScript 引擎计数的,因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确
    • 因此通过单独线程来计时并触发定时,计时完毕后,添加到事件队列中,等待 JavaScript 引擎空闲后执行)
    • 注意,W3C 在 HTML 标准中规定,规定要求 SetTimeout 中低于 4ms 的时间间隔算为 4ms。
  • 网络请求线程/异步 HTTP 请求线程
    • 每次网络请求时都需要开辟单独的线程,开启网络线程到发出一个完整的 HTTP 请求过程中包括:dns 查询、TCP/IP 请求构建、五层因特网协议栈等
    • 在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由 JavaScript 引擎执行。
  • 浏览器内核中线程之间的关系:
    • GUI 渲染线程与 JavaScript 引擎线程互斥
      • 由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
      • 因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系,当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新则会被保存在一个队列中等到 JavaScript 引擎线程空闲时立即被执行。
    • JavaScript 阻塞页面加载
      • JavaScript 如果执行时间过长就会阻塞页面。譬如,假设 JavaScript 引擎正在进行巨量的计算,此时就算 GUI 有更新,也会被保存到队列中,等待 JavaScript 引擎空闲后执行。然后,由于巨量计算,所以 JavaScript 引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。
  • WebWorker,JavaScript 的多线程
    • WebWorker 的 MDN 官方解释如下:
      • Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。
      • 一个 Worker 是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的 JavaScript 文件
      • 这个文件包含将在工作线程中运行的代码 Workers 运行在另一个全局上下文中,不同于当前的 Window
      • 因此,使用 Window 快捷方式获取当前全局的范围(而不是 self) 在一个 Worker 内将返回错误
    • JavaScript 引擎是单线程的,这一点的本质仍然未改变。如果有非常耗时的工作,请单独开一个 Worker 线程。
      • 创建 Worker 时,JavaScript 引擎向浏览器申请开一个子线程。子线程是浏览器开的,完全受主线程控制,而且不能操作DOM
      • JavaScript 引擎线程与 Worker 线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
    • WebWorker 与 SharedWorked
      • WebWorker 只属于某个页面,不会和其他页面的 Render 进程(浏览器内核进程)共享
        • 所以 Chrome 在 Render 进程中创建一个新的线程来运行 Worker 中的 JavaScript 程序
      • SharedWorker 是浏览器所有页面共享的,不能采用与 Worker 同样的方式实现,因为它不隶属于某个 Render 进程,可以为多个 Render 进程共享使用
        • 所以 Chrome 浏览器为 SharedWorker 单独创建一个进程来运行 JavaScript 程序,在浏览器中每个相同的 JavaScript 只存在一个 SharedWorker 进程,不管它被创建多少次。
  • 网络线程静态资源下载:这里将遇到的静态资源分为一下几大类(未列举所有):CSS样式资源、JS脚本资源、img图片类资源。
    • 遇到 CSS 样式资源
      • CSS 下载时异步,不会阻塞浏览器构建 DOM 树
      • 但是会阻塞渲染,也就是在构建 render 时,会等到 CSS 下载解析完毕后才进行(这点与浏览器优化有关,防止 CSS 规则不断改变,避免了重复的构建)
      • 有例外,media query 声明的 CSS 是不会阻塞渲染的
    • 遇到 JS 脚本资源
      • 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析 HTML
      • 浏览器的优化,一般现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限),但是虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已
      • defer 与 async,普通的脚本是会阻塞浏览器解析的,但是可以加上 defer 或 async 属性,这样脚本就变成异步了,可以等到解析完毕后再执行。注意,defer 和 async 是有区别的:
        • async 是异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在 onload 前,但不确定在 DOMContentLoaded 事件的前或后
        • defer 是延迟执行,在浏览器看起来的效果像是将脚本放在了 body 后面一样(虽然按规范应该是在 DOMContentLoaded 事件前,但实际上不同浏览器的优化效果不一样,也有可能在它后面)
    • 遇到 img 图片类资源:遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有 src 的地方。
  • loaded 和 domcontentloaded
    • DOMContentLoaded 事件触发时,仅当 DOM 加载完成,不包括样式表,图片(譬如如果有 async 加载的脚本就不一定完成)
    • load 事件触发时,页面上所有 DOM,样式表,脚本,图片都已经加载完成了。

浏览器多页面之间如何通信?§

  • 标签页之间的通信
    • WebSocket
    • LocalStorage:事件监听,无关跨域
    • SharedWorker:但必须是同源的

渲染§

浏览器从输入网址到显示页面都发生了什么?§

  • 关键字
    • 浏览器:
      • 浏览器的进程、浏览器内核的多线程、JavaScript 的单线程
      • 文档树:layout、paint、复合层与简单层、CSS 可视化格式模型
      • V8 & JS:变量提升、执行上下文、VO、AO、作用域链、回收机制
    • 网络:TCP/IP 模型、HTTP 协议、缓存机制
    • 硬件:
      • 键盘输入、操作系统交互、屏幕显示原理、网卡等硬件交互
      • 集线器、路由器、接入网、电话局、网络运营商、电话局、接入网
      • 防火墙、缓存服务器、DNS 服务器、CDN 服务器、网卡驱动
    • 安全:跨域、拦截、抓包
    • 应用:
      • 前端应用
      • 后端应用
      • 服务器 & 数据库:负载均衡

浏览器的渲染原理与关键渲染路径?§

  • 渲染过程
    • 创建 DOM 树:独立树
    • 创建 CSS 树:独立树
    • 生成渲染树(文档流):<head> 内部及 display: none 都不在,visibility: hidden在树中。
    • 执行脚本(就是JavaScript):
      • 脚本会阻塞 DOM 的解析,但在脚本修改 CSSOM 的地方,会在创建好 CSSOM 后执行。
      • 放在紧跟 </body> 之前,可以保证 DOM 树的解析。
      • async:异步下载,下载好后立刻执行。
      • defer:异步下载,下载好后等页面解析完再执行。
    • 布局:
    • 绘制:
  • 优化关键渲染路径:
    • 减少文件大小:最小化文件;压缩文件;以及缓存文件。
    • 减少关键资源的数量:CSS 和 JS 文件放在 bundle 当中;在 link 上使用 media query。
    • 减少关键渲染路径的长度:

同源策略的机制是什么?§

同源策略可防止 JavaScript 发起跨域请求。源被定义为 URI、主机名和端口号的组合。此策略可防止页面上的恶意脚本通过该页面的文档对象模型,访问另一个网页上的敏感数据。

浏览器内核的渲染机制?§

  • 解析 HTML,构建 DOM 树
    • Bytes → characters → tokens → nodes → DOM
    • Conversion 转换:浏览器将获得的 HTML 内容(Bytes)基于他的编码转换为单个字符
    • Tokenizing 分词:浏览器按照 HTML 规范标准将这些字符转换为不同的标记 token。每个 token 都有自己独特的含义以及规则集
    • Lexing 词法分析:分词的结果是得到一堆的 token,此时把他们转换为对象,这些对象分别定义他们的属性和规则
    • DOM 构建:因为 HTML 标记定义的就是不同标签之间的关系,这个关系就像是一个树形结构一样。 例如:body 对象的父节点就是 HTML 对象,然后段略 p 对象的父节点就是 body 对象
  • 解析 CSS,生成 CSS 规则树
    • Bytes → characters → tokens → nodes → CSSOM
  • 合并 DOM 树和 CSS 规则,生成 render 树
    • 一般来说,渲染树和 DOM 树相对应的,但不是严格意义上的一一对应。因为有一些不可见的DOM元素不会插入到渲染树中,如 head 这种不可见的标签或者 display: none 等。
  • 渲染过程:
    • 计算CSS样式
    • 构建渲染树
    • 布局,主要定位坐标和大小,是否换行,各种 position overflow z-index 属性【布局 render 树(Layout/reflow),负责各元素尺寸、位置的计算】
    • 绘制,将图像绘制出来【绘制 render 树(paint),绘制页面像素信息】
    • 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成(composite),显示在屏幕上。
  • 回流和重绘
    • Layout,也称为 Reflow,即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树
    • Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了
    • 回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流,所以优化方案中一般都包括,尽量避免回流。回流一定伴随着重绘,重绘却可以单独出现。
    • 什么引起回流
      • 页面渲染初始化
      • DOM 结构改变,比如删除了某个节点
      • render 树变化,比如减少了 padding
      • 窗口 resize
      • 改变字体大小会引发回流
      • 最复杂的一种:获取某些属性,引发回流, 很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流, 但是除了 render 树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括 offset(Top/Left/Width/Height); scroll(Top/Left/Width/Height); cilent(Top/Left/Width/Height); width,height; 调用了 getComputedStyle() 或者 IE 的 currentStyle;
    • 回流优化方案:
      • 减少逐项更改样式,最好一次性更改 style,或者将样式定义为 class 并一次性更新
      • 避免循环操作 DOM,创建一个 documentFragment 或 div,在它上面应用所有 DOM 操作,最后再把它添加到window.document
      • 避免多次读取 offset 等属性。无法避免则将它们缓存到变量
      • 将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高
  • 普通图层和混合图层
    • 可以认为默认只有一个复合图层,所有的 DOM 节点都是在这个复合图层下的
    • 如果开启了硬件加速功能,可以将某个节点变成复合图层
    • 复合图层之间的绘制互不干扰,由 GPU 直接控制
    • 而简单图层中,就算是 absolute 等布局,变化时不影响整体的回流,但是由于在同一个图层中,仍然是会影响绘制的,因此做动画时性能仍然很低。而复合层是独立的,所以一般做动画推荐使用硬件加速。
  • 硬件加速变成复合图层:经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层。
    • 最常用的方式:translate3d、translateZ
    • opacity 属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
    • will-chang 属性,一般配合 opacity 与 translate 使用
  • absolute 和硬件加速的区别
    • absolute 虽然可以脱离普通文档流,但是无法脱离默认复合层。所以,就算 absolute 中信息改变时不会改变普通文档流中 render 树,但浏览器最终绘制时,是整个复合层绘制的,absolute 中信息的改变仍然会影响整个复合层的绘制。
    • 硬件加速直接就是在另一个复合层了,所以它的信息改变不会影响默认复合层。
  • 复合层的作用?:一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。
  • 硬件加速时请使用 index。使用硬件加速时,尽可能的使用index,防止浏览器默认给后续的元素创建复合层渲染。如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意。

重绘和重排的机制?§

  • 基础
    • 重排:部分(或整个)渲染树需要重新分析并且节点尺寸需要重新计算,表现为重新生成布局,重新排列元素。
    • 重绘:由于节点的几何属性发生改变活着由于样式发生改变,例如改变元素背景色时,屏幕上的部分内容需要更新,表现为某些元素的外观被改变。
    • 重排和重绘代价是高昂的,它们会破坏用户体验,并且让UI展示非常迟缓,而相比之下重排的性能影响更大,在两者无 法避免的情况下,一般我们宁可选择代价更小的重绘。
    • 『重绘』不一定会出现『重排』,『重排』必然会出现『重绘』。
  • 如何触发?
  • 任何改变用来构建渲染树的信息都会导致一次重排或重绘:
    • 添加、删除、更新DOM节点
    • 通过display: none隐藏一个DOM节点-触发重排和重绘
    • 通过visibility: hidden隐藏一个DOM节点-只触发重绘,因为没有几何变化 移动或者给⻚面中的DOM节点添加动画
    • 添加一个样式表,调整样式属性 用户行为,例如调整窗口大小,改变字号,或者滚动。
  • 如何避免?
    • 集中改变样式:如更换一次 class
    • 使用 DocumentFragment:使用 createDocumentFragment 创建一个游离于 DOM 树之外的节点,然后在此节点上批量操作,最后插入到 DOM 树中,此时只触发了一次重排。
    • 将元素提升为合成层:优点:
      • 合成层的位图,会交由 GPU 合成,比 CPU 处理更快。
      • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层。
      • 对于 transform 和 opactiy 效果,不会触发 layout 和 paint。
      • 提升合成层最好的方式是使用 CSS 的 will-change 属性。

如何理解 GPU 硬件加速?§

  • 硬件加速主要就是利用GPU提供的一些功能,使GPU为CPU分担一些任务。
  • 浏览器不会在动画的每一帧都绘制一次,而是生成DOM元素的快照,并作为GPU纹理(也被叫做层)存储起来。之后浏览器只需要告诉GPU去转换指定的纹理来实现DOM元素的动画效果。这就叫做GPU合成,也经常被称这种借助于显卡的优势改变渲染操作:通常被笼统的称为“硬件加速(hardware acceleration)”。
  • 在硬件加速条件下,GPU接受来自渲染进程的命令,并将其输入OpenGL 或 Direct3D 进行处理,Chrome在此一直落后于其他对手,原因是Google考虑到安全因素,将浏览器的渲染工作安排在一个独立的进程内,导致无法与操作系统的硬件接口直接通信。
  • CSS 与 GPU 硬件加速
    • CSS animations, transforms 以及 transitions 不会自动开启GPU加速,而是由浏览器的缓慢的软件渲染引擎来执行。
    • 现在,像Chrome, FireFox, Safari, IE9+和最新版本的Opera都支持硬件加速,当它们检测到页面中某个DOM元素应用了某些CSS规则时就会开启,最显著的特征的元素的3D变换。
      • transform、opacity、filter
  • 创建标准 - 什么情况下能使元素获得自己的层?目前来说,满足以下任意情况便会创建层。
    • 3D 或透视变换(perspective transform) CSS 属性
    • 使用加速视频解码的 元素
    • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
    • 混合插件(如 Flash)
    • 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
    • 拥有加速 CSS 过滤器的元素
    • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
    • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
  • 缺点:
    • 内存。如果GPU加载了大量的纹理,那么很容易就会发生内容问题,这一点在移动端浏览器上尤为明显,所以,一定要牢记不要让页面的每个元素都使用硬件加速。
    • 使用GPU渲染会影响字体的抗锯齿效果。这是因为GPU和CPU具有不同的渲染机制。即使最终硬件加速停止了,文本还是会在动画期间显示得很模糊。

进阶§

客户端存储机制是怎样的?§

  • Web SQL:2010年被W3C废弃的本地数据库数据存储方案,但是主流浏览器(火狐除外)都已经有了相关的实 现,web sql类似于SQLite,是真正意义上的关系型数据库,用sql进行操作,当我们用JavaScript时要进行转换, 较为繁琐。
  • IndexedDB: 是被正式纳入HTML5标准的数据库储存方案,它是NoSQL数据库,用键值对进行储存,可以进行快 速读取操作,非常适合web场景,同时用JavaScript进行操作会非常方便。
  • localStorage、sessionStorage、Storage 的方法及属性:clear()、getItem()、key()、removeItem()、setItem()、constructor()、length
  • Cookie、LocalStorage、SessionStorage 都是客户端以键值对存储的存储机制,并且只能将值存储为字符串。
Cookie LocalStorage SessionStorage
由谁初始化 服务器(Set-Cookie 请求头)或客户端 客户端 客户端
过期时间 手动设置 永不过期 当前页面关闭时
在当前浏览器会话中是否保持不变 取决于是否设置过期时间
是否与域名相关联
是否随着每个 HTTP 请求发送给服务器 是,Cookie 会自动设置 Cookie 请求头
每个域名容量 4kb 5mb 5mb
访问权限 任一窗口 任一窗口 当前页面窗口

同源策略与跨域?§

  • 无视跨域问题的标签:<script>、<link>、<img>、<video>、<audio>、<iframe>
  • 解决方法:
    • JSONP(JSON with Padding):只能处理 GET 请求。
    • CORS(Cross-Origin Resource Sharing):新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
      • 简单请求:
      • 预检请求:预检,查看自己是否可以跨域
    • 代理

如何高效使用浏览器开发者工具?§

JS 引擎§

JS 的引擎有哪些及其特点?§

  • KJS 引擎,与早期 KHTML 引擎相配

    • 与早期 KHTML 排版引擎相配的,还有用来解析 JavaScript 的 KJS 引擎。KJS 同样由 KDE 社区开发。其后因为 Webkit 作为分支的诞生,逐步被可以直接将 JS 代码编译为原生机器码的 JavaScriptCore 引擎替代。JavaScriptCore 引擎成为了 Webkit 中的一个重要组件。
  • Chakra,运行在 IE9+ 上

    • Chakra 引擎(又称 JScript 引擎)是由微软为 IE9+ 版本开发的 JavaScript 引擎,在一个独立的 CPU 核心上即时编译脚本,与浏览器并行。在 2009 年 11 月 18 日举行的 SunSpider 测试展示了 IE9 的 PDC 版本对脚本的执行远快于 IE8,但是仍然慢于 Firefox 3.5、Google Chrome 4 和 Safari 4。
  • ChakraCore,运行在 Microsoft Edge 上

    • Chakra是由微软为其Microsoft Edge网页浏览器开发的JavaScript引擎。它是Internet Explorer中使用的JScript引擎的一个分支。
  • V8 引擎,运行在 Chrome、Node.JS 上

    • V8 由 Google 公司开发,是开源的 JavaScript 引擎。V8 在运行之前将 JavaScript 编译成了机器码,而非字节码或是解释执行它,以此提升性能。基于 V8 引擎对 JavaScript 的高性能解析,Node.js 也选择了 V8 引擎作为其在服务端解析 JavaScript 的首选引擎,促进了前端的蓬勃发展。
  • JavaScriptCore 引擎

    • 浏览器下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的。
    • 在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素都已经下载完,比如图片)。
    • 如果遇到语义解释性的标签嵌入文件(JS脚本,CSS样式),那么此时IE的下载过程会启用单独连接进行下载。
    • 并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载。此时渲染会被阻塞,必须等js、或css资源文件加载并解析完成之后才会继续后面的渲染
    • 样式表在下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以前已经渲染的)重新进行渲染。重新渲染是很耗费性能的,如果可以,尽量把所有的 CSS 样式文件都在 <head> 里面,实在不行,把首屏的 CSS 样式内嵌在页面中,加快首屏显示速度,提升用户体验。

JS 引擎解析过程§

  • JavaScript 的解析阶段
    • JavaScript 是解释型语音,所以它无需提前编译,而是由解释器实时运行。引擎对 JavaScript 的处理过程可以简述如下:
      1. 读取代码,进行词法分析 Lexical analysis,然后将代码分解成词元 token
      2. 对词元进行语法分析 parsing,然后将代码整理成语法树 syntax tree
      3. 使用翻译器 translator,将代码转为字节码 bytecode
      4. 使用字节码解释器 bytecode interpreter,将字节码转为机器码
      5. 总结:核心的 JIT 编译器将源码编译成机器码运行
    • 最终计算机执行的就是机器码。为了提高运行速度,现代浏览器一般采用即时编译 JIT-Just In Time compiler,即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存 inline cache,这样整个程序的运行速度能得到显著提升。
    • 不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码,如 Chrome 的 V8。
  • JavaScript 的预处理阶段:正式执行 JavaScript 前,还会有一个预处理阶段。
    • 分号补全机制:JavaScript 解释器有一个 Semicolon Insertion 规则,它会按照一定规则,在适当的位置补充分号。
      • 加分号规则:
        • 当有换行符,包括含有换行符的多行注释,并且下一个 token 没法跟前面的语法匹配时,会自动补分号。
        • 当有 } 时,如果缺少分号,会补分号。
        • 程序源代码结束时,如果缺少分号,会补分号。
    • 变量提升机制:一般包括函数提升和变量提升。
      • 变量声明,函数声明,形参,实参的优先级顺序,以及 es6 中 let 有关的临时死区、var conost let 等
  • JavaScript 的执行阶段
    • 执行上下文
      • JavaScript 有执行上下文
      • 浏览器首次载入脚本,它将创建全局执行上下文,并压入执行栈栈顶,不可被弹出
      • 然后每进入其它作用域就创建对应的执行上下文并把它压入执行栈的顶部
      • 一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈。
      • 这样依次执行,最终都会回到全局执行上下文
      • 譬如,如果程序执行完毕,被弹出执行栈,然后有没有被引用,没有形成闭包,那么这个函数中用到的内存就会被垃圾处理器自动回收。
      • 每一个执行上下文,都有三个重要属性:变量对象(Variable object,VO)、作用域链(Scope chain)、this
    • VO 与 AO
      • VO 是执行上下文的属性(抽象概念),但是只有全局上下文的变量对象允许通过 VO 的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象)。AO(activation object),当函数被调用者激活,AO 就被创建了。
      • 在函数上下文中:VO === AO
      • 在全局上下文中:VO === this === global
      • 总的来说,VO中会存放一些变量信息(如声明的变量,函数,arguments参数等等)
    • 作用域链,它是执行上下文中的一个属性,原理和原型链很相似,作用很重要。流程
      • 在函数上下文中,查找一个变量 foo
      • 如果函数的 VO 中找到了,就直接使用
      • 否则去它的父级作用域链中(parent)找
      • 如果父级中没找到,继续往上找
      • 直到全局上下文中也没找到就报错
    • this 指针
      • this 是执行上下文环境的一个属性,而不是某个变量对象的属性。
      • this 是没有一个类似搜寻变量的过程
      • 当代码中使用了 this,这个 this 的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻
      • this 的值只取决中进入上下文时的情况
  • JavaScript 回收机制:JavaScript 有垃圾处理器,所以无需手动回收内存,而是由垃圾处理器自动处理。常用的两种垃圾回收规则是:标记清除和引用计数。
    • 标记清除:JavaScript 引擎基础 GC 方案是(Simple GC):mark and sweep 标记清除,简单解释如下:
      • 遍历所有可访问的对象。
      • 回收已不可访问的对象。
    • 引用计数:跟踪记录每个值被引用的次数,当一个值被引用时,次数 +1,减持时 -1,下次垃圾回收器会回收次数为 0 的值的内存(容易出循环引用的bug)。
    • GC 的缺陷:GC 时,停止响应其他操作,这是为了安全考虑。Javascript 的 GC 在 100ms 甚至以上。对一般的应用还好,但对于 JavaScript 游戏,动画对连贯性要求比较高的应用,就麻烦了。
    • GC 优化策略:Generation GC 分代回收机制目前是通过区分“临时”与“持久”对象:
      • 多回收“临时对象”区(young generation)
      • 少回收“持久对象”区(tenured generation)
      • 减少每次需遍历的对象,从而减少每次 GC 的耗时