Skip to content
大纲

工作原理

浏览器

加载事件

文档的加载流程

  1. 解析 HTML 结构。
  2. DOM 树构建完成。// DOMContentLoaded
  3. 加载图片等外部文件。
  4. 页面加载完毕。// load
  5. 刷新或离开页面。 // beforeunloadunload
  • DOMContentLoaded: DOM 加载完触发, 无需等待 样式表、图片、子框架 等资源完全加载
  • load: 页面即依赖资源完全加载后触发
  • beforeunload: 刷新或关闭页面时触发, 若返回 false 则会弹出确认对话框
  • unload: 刷新或关闭页面确认后触发

事件流

事件冒泡/捕获

  • addEventListener 第三个参数为 true 表示捕获,也可以是一个对象
    • useCapture: 是否捕获
    • once: 是否只一次
    • passive: 是否禁用默认事件
  • event.stopPropagation(): 停止冒泡/捕获
  • 冒泡捕获优先级
    • 若元素无子元素,则其冒泡和捕获执行顺序依据对应事件的注册顺序
    • 若元素有子元素,则同一事件的捕获优先级高于冒泡
  • 事件代理:在父节点上进行监听,根据 event.target 判断具体节点

并非所有事件都支持冒泡/捕获,以下事件则不支持

  • 文档事件:load unload abort error
  • 鼠标事件:focus blur mouseenter mouseleave
  • 元素事件:DOMNodeInsertedIntoDocument DOMNodeRemovedFromDocument

缓存控制

本地缓存

方式大小服务端特点
cookie4kHeader 中携带存在 domain 限制(20),可由服务端控制
localStorage5m无交互长期缓存,除非手动清除
sessionStorage5m无交互会话缓存,页面关闭时清除(刷新不清除)
indexedDB无限无交互长期缓存,除非手动清除

注意:同源窗口下(如 http://www.xxx.com/ahttp://www.xxx.com/b)

  • localStorage 在不同页面之间共享
  • sessionStorage 在不同页面之间独立

网络缓存

强缓存: 不发送请求,直接从内存和硬盘中读取

  • http1.0: 根据 Expires: 时间 字段判断是否命中
  • http1.1: 根据 Cache-Control: max-age=xxx 判断是否命中

协商缓存: 发送请求,由服务器判断是否使用缓存,若命中则返回 304Not Modified

  • http1.0: 根据 Last-Modified 字段判断是否命中
    • 首次请求时服务端返回 Last-Modified 字段
    • 再次请求时客户端携带 If-Modified-Since 字段
  • http1.1: 根据 ETag 判断是否命中
    • 首次请求时服务端返回 ETag 字段
    • 再次请求时客户端携带 If-None-Match 字段

Cache-Control 可以控制缓存方式

  • no-store: 表示不缓存,跳过强缓存和协商缓存
  • no-cache: 表示缓存立即失效,下次请求时进入协商缓存

跨域通信

同源策略

浏览器回对请求响应进行判断,在未设置 CORS 时,非同源响应都会被拦截
同源要求:同协议、同域名(一级、二级、三级等)、同端口
允许跨域:<link>, <script> <img> 标签请求的资源可以跨域

跨域请求

JSONP

  • 利用 script 允许跨域的特点,后端返回脚本字符串(方法(数据)),前端加载后执行
  • 只支持 get ,易受 XSS 攻击,存在安全问题
  • 通常用于第三方服务提供接口查询服务

CORS

  • 通过配置 Access-Control-Allow- 相关字段,允许跨域资源访问
  • 常见字段有 OriginMethodsHeadersCredentials
  • 通常用于开发阶段或提供第三方服务

nginx

  • 利用服务端请求无同源策略的特点,通过配置代理进行请求转发
  • 通常用于开发阶段

postMessage

  • 利用 H5 消息 API 进行通信
  • 发送消息需要获取到目标源,target.postMessage(message, origin)
  • 接收消息需要监听全局事件,window.addEventLister('message', e => { e.data })
  • 通常用于跨域父子页面通信

document.domain

  • 通过设置相同的二级域名,将跨域页面设置为同域
  • a.xxx.comb.xxx.com,设置 document.domain = 'xxx.com'
  • 只能用于一二级域名相同的情况

渲染引擎

工作流程

  1. 解析:HTML -> DOM Tree; CSS -> Style Rules
  2. 构建:DOM Tree + Style Rules -> Render Tree
  3. 布局:Render Tree 计算坐标信息
  4. 绘制:由用户界面后端层对节点进行绘制

回流重绘

  • 回流:重新布局,并触发重绘
  • 重绘:仅改变外观样式,开销较低

触发回流的行为

  • 页面首次渲染
  • 窗口尺寸变动
  • 增减可视元素
  • 元素尺寸变动
  • 元素位置变动
  • 元素内容变动
  • 元素尺寸获取(为了确保获取最新值)

复合图层

页面呈现的最终内容是由多个图层复合而成的
不同图层之间的渲染互不影响,适当的创建图层可以减少大规模回流的触发
满足以下条件时会创建新的图层

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS 动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

脚本与样式表处理顺序

  • 脚本 <script>
    • 同步脚本: 加载和执行阶段都会阻止文档解析
      • 内部脚本: 直接执行脚本
      • 外部脚本: 等待资源加载完毕后解析和执行
    • 异步脚本: 加载时不阻止文档解析, 执行时阻止文档解析
      • defer 标记: 延迟加载, 文档解析后, DOMContentLoaded 事件之前执行, 有序执行
      • async 标记: 异步加载, 加载完成后立即执行, 无序执行
  • 预解析
    • 执行脚本时, 其他线程会解析文档的其他内容, 找出并加载需要通过网络加载的其他资源
    • 这里的预解析只会解析外部资源的引用, 不会修改 DOM 树
  • 样式表
    • 样式表加载不阻塞 DOM 树解析
    • 样式表加载会阻塞 DOM 树渲染
    • 样式表加载会阻塞脚本运行
    • 样式表解析时对脚本的阻塞(原文说加载和解析过程, 测试发现加载时都会阻塞)
      • Gecko: 样式表在加载和解析时会禁止脚本
      • Webkit: 仅当脚本尝试访问的样式属性可能受未加载的样式表影响时会禁止脚本

事件循环

解决 js 单线程允许不阻塞的机制

  • 同步任务在主线程执行
  • 异步任务会添加到任务队列中
  • 主线程中同步任务执行完后会读取任务队列
  • 如此反复形成循环

任务队列中的任务分为

  • 宏任务:setTimeout postMessage MessageChannel UIrender
  • 微任务:Promise MutationObserver

当前执行栈执行完毕时优先处理微任务队列,再处理宏任务

注:事件冒泡优先级高于宏任务,低于微任务

垃圾回收

新生代

  • 内存空间分为两部分:from 和 to
  • 新分配对象进入 from 空间,from 空间占满时,执行 GC
  • 遍历 from 中存活对象复制到 to 空间,复制完成后 from 与 to 空间互换

老生代:新生代中执行一次 GC 存活和 to 空间大于 25%时,会对新生代对象转移进老生代

  • 标记清除:当某空间没有分块、空间中对象超过限制不能保证新生代对象进入老生代时触发 GC
    • 遍历堆中所有对象,标记存活对象,销毁未标记对象
    • 标记清除会造成全停顿
    • 2011 年,使用增量标记延缓全停顿的状态
    • 2018 年,使用并发标记,在 GC 扫描和标记对象时允许 JS 执行
  • 标记压缩:对象清除后会造成堆内存出现碎片的情况
    • 将存活对象向一段移动,直到所有对象移动完成