浏览器基础

V8引擎

V8 引擎执行 JavaScript 流程

抽象语法树(Abstract Syntax Tree)AST 是正常代码转换后的一种格式,AST 是计算机科学的一个概念,有一套标准编译标准,将代码转换为 AST 的过程中可以对代码进行修改,如web开发去 consoleES6转ES5TS 转 JS 等都是在这一步进行的。

示例,将const a = 'hello' 转换后的 AST 是(这个网站可以将代码转换为抽象语法树):

{
  "type": "Program",
  "start": 0,
  "end": 15,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 15,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 6,
          "end": 15,
          "id": {
            "type": "Identifier",
            "start": 6,
            "end": 7,
            "name": "a"
          },
          "init": {
            "type": "Identifier",
            "start": 10,
            "end": 15,
            "name": "hello"
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

字节码(Byte-code):字节是计算机的数据量单位,字节码就是用字节表示的代码,字节码是一种中间码,由于它不随着机器不同而不同,所以做到一次编写,到处运行的目的,字节码还需要被虚拟机编译成机器码才能被计算机执行(注意在不同的机器中机器码可能是不同的)。之所以在高级语言和机器码之前加一层字节码,是因为高级语言直接转换为机器码很麻烦,转换为字节码后在转换为机器码更简单一些。

解释器(Interpreter):直接将高级语言一行一行转义运行的计算机程序(边转义边运行), 在 V8 引擎中执行的 JavaScript 的流程中是,解释器直接执行 字节码。

解释器执行执行码的流程

JIT(Just-in-time):一种优化手段,在引擎中增加一个监视器,为运行的代码打标签,当相同的代码被多次运行时,将代码送到基线编译器去编译,并把编译结果保存起来,当再次运行这行代码的时候直接使用编译后的的版本,这样可以提高代码编译速度。

内联缓存(Inline Cache):IC 就是在运行的过程中,收集一些数据信息,并将信息缓存起来,等再次执行的时候直接利用缓存起来的信息,节省了再次获取信息的消耗,提高性能。

如:

const length = 10000
const obj0 = {x:1, y: 2, z: 3}
function f(obj){
    for(const i in obj){
        obj[i].toString()
    }
    console.log(obj)
}

console.time('t1')
for(let i = 0; i < length; i++){
    const obj1 = {x: 3, y: 2}
    obj1[i] = 3
    f(obj0)
}
console.timeEnd('t1')

console.time('t2');   // 计时开始
for (let i = 0; i < length; i++) {
    const obj2 = {x: 3, y: 2};
    obj2[i] = 3
    f(obj2)
}
console.timeEnd('t2'); // 计时结束

// 上面两段代码, 只有 f(obj0) 与 f(obj2) 不同, 但可以看到运行的时间也会有差异, 主要原因就是 f(obj0) 中的 obj0 被缓存起来了, 减少了获取的消耗, 导致其运行的速度稍快。

内存结构:内存是计算机的一个组成部分,程序运行的指令和数据存储在硬盘中,JavaScript 引擎将硬盘中的数据读取到内存中来运行程序,内存的生命周期基本上如下:

  • 分配所需要的内存
  • 使用分配到的内存(读、写)
  • 释放内存

V8 引擎的内存分类大致分为栈和堆。

  1. 栈(Stack):栈又称堆栈,是一种运算受限的线性表,限定数据只能表尾进行插入和删除的线性表(注意栈中数据的读取是没有这种限制的),在计算机系统中,栈是一个具有以上属性的动态内存区域。在 JavaScript 中栈区的大小默认为 984KIB(注:1KIB = 1024 B,1kb=1000B,1MIB=1024KIB, 1MB=1000KB),用于存放数据的指针。

    之前有些说法是在 JavaScript 中基本类型是存放在栈中,对象是存放在堆中,这种说法是不准确的,所有的数据都存放在堆中,栈中只存放数据的指针,如创建一个 70MIB 的字符串,显然它超过了栈的容积,但还是可以进行存取这一点就可推翻这种说法。

    如创建一个 70MIB 的字符串:

    const string = new Array(10000000).fill(1).reduce((calb) => { return calb + 'aioverg' })
    console.log('70MIB字符串', string)
    

    在浏览器中的打印结果:

    可以看到没有导致栈溢出。

    再来看下面一个例子,代码是

    function Ex() {
        this.a = 'aioverg'
        this.b = 'aioverg'
    }
    const a = new Ex()
    

    打开浏览器的 Memory ,启动堆快照heap snapshot 可有看到 EX 实例中的ab 指向的是同一个地址,

    再看下面的代码,修改了实例 a 的值为 jjjjjjj,并增加了一个属性 c 且赋值为 aiover

    function Ex() {
        this.a = 'aioverg'
        this.b = 'aioverg'
    }
    const a = new Ex()
    a.c = 'aioverg'
    a.a = 'jjjjjjj'
    

    可以看到,此时 a 指向的地址与 b 已经不一样了,但 c 指向的地址与 b 一样

    可见字符串其实是存在其他地方的,且相同的字符串只会存放一次,当字符串相等时。

    其实在 V8 引擎中,有一个名为stringTable的hashmap缓存了所有字符串,在 V8 并将代码转换为 AST 时,会给字符串生成一个 hash 值,并插入的 hsahmap 中,在之后如果遇到了hash值一致的字符串,就不会生成新字符串,否则就开辟一块内存存放字符串,并将地址赋值给变量。

    其他的类型也可以按此方法查验。

  2. 堆(Heap):在 JavaScript 中堆与栈类似,都是一个内存区域,只不过堆更加复杂,V8引擎在初始化内存空间的时主要将堆堆存分为以下几个区域:

    • 新生代内存区(new space):新生代内存区会被划分为两个区域,通常用来临时保存数据,每个区域的默认大小为 16M
    • 老生代内存区(old space):老生代内存区也会被划分为两个区域,通常用来较为持久的保存数据。
    • 大对象区(large object space):存放体积超越其他区大小的对象,主要为了避免大对象的拷贝,使用该空间专门存储大对象。
    • 单元区、属性单元区、Map区(Cell space、property cell space、map space): Map空间存放对象的Map信息也就是隐藏类(Hiden Class)最大限制为8MB;每个Map对象固定大小,为了快速定位,所以将该空间单独出来。
    • 代码区 (code Space):主要存放代码对象,最大限制为512MB,也是唯一拥有执行权限的内存

内存运行的生命周期:

  • 新生代内存区:新创建的数据会选择新生代区的一个存放,之后再创建的数据也继续存放在这个内存中,直到该内存区即将存满(默认16M),V8引擎 会执行垃圾回收,将还在使用的数据放在另一个内存区中,然后清空先前的内存区,当这个内存区快存满时,再进行垃圾回收,重复刚才的操作。
  • 老生代内存区:如果程序经过一段时间的运行,数据依然保存在新生代内存区中,就会将该数据移动到老生代内存区中的一个中去,当该内存区快满时执行垃圾回收,垃圾回收机制与新生代内存区一样。

消息队列

  1. 单线程JavaScript: 作为浏览器脚本语言,主要用来与用户互动和操作 DOM 等,之所以设计成单线程是因为如果存在多个线程同时操作一个 DOM 时,会导致非常难以处理,所以被设计成单线程,但新的 HTML5 标准提出来 web worker 概念,允许用户额外开启显示,但 worker 线程 完全受 主线程 控制,且用来处理一些计算逻辑,没有操作 DOM 的权限。

  2. 异步任务:由于 JavaScript 只有一个主线程来执行任务,同一时间只能执行一个任务,但是有些任务比较耗时,如果等待任务执行完毕,会导致页面卡死,为了提高效率就有了异步任务,V8引擎 通过 消息队列事件循环 让异步任务执行且不用排队等待任务执行完毕。

  3. 消息队列:消息队列是 V8引擎 处理主线程任务外,额外维护的一个队列,主要存放要执行的任务,符合队列 先进先出 的特点,从对列的头部取出任务,从队列的尾部添加任务。

    浏览器本身需要异步的场景非常多,而每一种异步操作的机制也各不相同, 消息队列可以和多种异步场景产生交互。如:

    • 输入事件相关的异步交互(比如onClick)由引擎的 DOM Binding 模块处理,相应的事件触发时,会将对应的回调函数添加到消息队列中。
    • ajax请求相关的异步交互 由引擎的network模块处理,在网络请求完成返回之后,会将对应的回调函数添加到消息队列中。
    • 定时器相关的异步交互由引擎的 timer 模块处理,当时间到达的时候,会将回调函数添加到任务队列中。

    以及一些其他的模块会将异步操作放置到消息队列中,在引擎主线程的任务都执行完成后再执行消息队列中被推送的任务。

    每次执行栈中的代码就是一个宏任务(task),而消息队列中的任务会按顺序放到下一次的宏任务(task)中,每个宏任务(task)在执行时,V8 都会重新创建栈,然后随着宏任务(task)中函数调用,栈也随之变化,最终,当该宏任务(task)执行结束时,整个栈又会被清空,接着主线程继续执行下一个宏任务(task)。

    而浏览器会在一个 宏任务(task) 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染,如图:

    由于主线程执行消息队列中宏任务的时间颗粒度太粗了(主要中间有一次渲染过程),无法胜任一些对精度和实时性要求较高的场景,所以又引入了promise 机制也就是微任务如图:

  4. 事件循环

    • 主线程运行产生了执行栈,在调用执行栈的过程中调用了一些异步函数。
    • 当满足异步函数的触发条件时,会将对应的回调函数推送到消息队列中。
    • 当主栈中的代码执行完毕时,会触发一次页面渲染,然后创建新的主栈。
    • 将消息队列中的回调函数推送到主栈。然后顺序执行主栈的任务。
    • 反复循环执行上述过程就是事件循环。

协程&生成器函数

  • 生成器函数Generator:在 Javascript 最初的规则中中,一个函数开始执行后,就会运行到最后或遇到 return 时结束,运行期间不会有其它代码能够打断它。而ES6中引入的 Generator 打破了这个规则,Generator 可以交出函数的执行权(暂停执行),这也是和普通函数最大的区别。 生成器函数的几个特点:

    1. 调用生成器函数,会返回一个内部指针(遍历器 Iterator 对象)
    2. 通过调用 遍历器Iterator对象的 next 方法,遍历 Generator函数内部的每一个状态
    3. 创建时通过在 function 关键字与函数名添加 * 用以和普通函数做区分
    4. 函数体内通过多个 yield 表达式,定义不同的内部状态,遍历器对象使用 next 遍历时即返回当前的 yield 状态

    Generator 通过构建函数 yiled 构建阶段状态,调用时通过调用 next 返回当前状态的信息 value 以及执行状态 done。通过Generator函数和Promise的结合解决了回调地狱的问题,并于ES7规范推出了官方语法糖 async/await

  • 进程:进程是一个任务,一个线程可以包含多个线程。

  • 线程:线程是处理任务的操作,一个进程可能需要很多线程同时工作来完成。

  • 协程:Generator函数本质上就是协程的一种实现,协程是一种比线程更加轻量级的存在,可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。

    协程概念的提出比较早,单核CPU场景中发展出来的概念,通过提供挂起和恢复接口,实现在单个CPU上交叉处理多个任务的并发功能。本质上就是在一个线程的基础上,增加了不同任务栈的切换,通过不同任务栈的挂起和恢复,线程中进行交替运行的代码片段,实现并发的功能。

    ES6中引入的生成器函数,就是给用户提供了协程特性的入口,通过生成器函数可以使用协程的特性。这些特性使得程序更适合高并发的I/O操作,真正解决了回调地狱的问题。


渲染的重排和重绘

浏览器渲染页面的流程:

  1. 解析 HTML,生成 DOM树,解析 CSS,生成 CSSOM树
  2. DOM树CSSOM树 结合,生成 渲染树(Render Tree)
  3. Layout(重排):根据生成的渲染树,进行 重排(Layout),得到节点的几何信息(位置,大小)。
  4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素。
  5. Display:将像素发送给 GPU,展示在页面上。

重绘:浏览器渲染页面中的 Painting 步骤,当元素样式的改变不影响布局时,浏览器会使用 重绘 对元素进行更新,此时由于只需要 UI 层面的重新像素绘制,省去了 重绘 之前的步骤,因此损耗较少。发生重绘情况:

  • 改变元素颜色
  • 改变元素背景色
  • ······

重排:又叫回流,就是浏览器渲染页面中的 Layout 步骤,当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,此时,浏览器需要重新跑一边渲染过程,因此是较重的操作。发生回流的情况:

  • 页面初次渲染

  • 浏览器窗口大小改变

  • 元素尺寸/位置/内容发生改变

  • 元素字体大小变化

  • 添加或者删除可见的 DOM 元素

  • 激活 CSS 伪类:hover ……)

  • 调用 window.getComputedStyle 方法。

  • 读写 offset、scroll、client 族属性的时候,为了获取这些值,需要重排。

  • more ……

    注意:重排必定会触发重绘,重绘不一定会触发回流。重绘的开销较小,重排的代价较高。

实践意义

  1. 避免频繁使用 style,而是采用修改 class 的方式。如这里会触发2次重排:

    box.style.width = "100px";
    box.style.width = "100px";
    
    box.offsetWidth; // 这里获取属性, 会触发重排 
    
    box.style.position = "relative"; // 这里又需要重排
    
  2. 使用 createDocumentFragment ,创建一个空白文档,着这上面进行批量操作,操作完成后再添加到文档,这样只会触发一次重排。

  3. 在操作 DOM 前,先使用 display 将元素隐藏,操作完成后再显示,不可见的元素不会触发重排和重绘。

  4. 对于 resize、scroll 等进行防抖/节流处理。

  5. 添加 will-change ,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。


浏览器指纹

浏览器指纹是指仅通过浏览器的各种信息,如系统字体、屏幕分辨率、浏览器插件,无需 cookie 等技术,就能近乎绝对定位一个用户,即使是使用浏览器的隐私窗口模式(即虽然不知道具体用户但知道是某个浏览器),理论上你访问了某一个网站,那么这个网站就能识别到你,虽然不知道你是谁,但你有一个唯一的指纹,这样将来无论是广告投放、精准推送,还是其他一些关于隐私的事情,都非常方便。

指纹分类:

  1. 一般指纹

    • Cookie
    • Session
    • Evercookie
    • Flash Cookies
  2. 基本指纹:基本指纹是任何浏览器都具有的特征标识,比如硬件类型(Apple)、操作系统(Mac OS)、用户代理(User agent)、系统字体、语言、屏幕分辨率、浏览器插件 (Flash, Silverlight, Java, etc)、浏览器扩展、浏览器设置 (Do-Not-Track, etc)、时区差(Browser GMT Offset)等众多信息,这些指纹信息“类似”人类的身高、年龄等,有很大的冲突概率,只能作为辅助识别。可以在该网址进行查看本地浏览器的基本特征。

  3. 高级指纹

    • Canvas指纹:相同的HTML5 Canvas元素绘制操作,在不同操作系统、不同浏览器上,产生的图片内容不完全相同。在图片格式上,不同浏览器使用了不同的图形处理引擎、不同的图片导出选项、不同的默认压缩级别等。在像素级别来看,操作系统各自使用了不同的设置和算法来进行抗锯齿和子像素渲染操作。即使相同的绘图操作,产生的图片数据的CRC检验也不相同。

      可以在该网址进行查看本地浏览器的Canvas指纹。

    • AudioContext指纹:主机或浏览器硬件或软件的细微差别,导致音频信号的处理上的差异,相同器上的同款浏览器产生相同的音频输出,不同机器或不同浏览器产生的音频输出会存在差异

      可以在[该网址](AudioContext Fingerprint Test Page (openwpm.com))进行查看本地浏览器的AudioContext指纹。

  4. 硬件指纹

    硬件指纹主要通过检测硬件模块获取信息,作为对基于软件的指纹的补充,主要的硬件模块有:GPU、摄像头、扬声器/麦克风、运动传感器、GPS、电池、CPU、网卡、蓝牙、BOIS等。

    更多细节查看[该网址](phaseIII.eps (arxiv.org))

  5. 综合指纹

    • 将基本指纹和多种高级指纹综合利用,进行分析、计算哈希值作为综合指纹,可以大大降低指纹碰撞率,提高客户端唯一性识别的准确性。

      [测试地址](Cover Your Tracks (eff.org))

    • 跨浏览器指纹,上述指纹都是基于浏览器进行的,同一台电脑的不同浏览器具有不同的指纹信息。这样造成的结果是,当同一用户使用同一台电脑的不同浏览器时,服务方收集到的浏览器指纹信息不同,无法将该用户进行唯一性识别,近期有学者研究了一种跨浏览器的浏览器指纹,其依赖于浏览器与操作系统和硬件底层进行交互进而分析计算出指纹,这种指纹对于同一台电脑的不同浏览器也是相同的。

      [更多细节](crossbrowsertracking_NDSS17.pdf (yinzhicao.org))


浏览器页面呈现

从输入URL到页面呈现流程 - 网络

如图:

  1. 构建请求

    浏览器会构建请求行,如:

    // 请求方法是GET,路径为根路径,HTTP协议版本为1.1
    GET / HTTP/1.1
    
  2. 查找强缓存

    检查强缓存,如果有资源且有效直接使用,否则进入下一步。

  3. DNS解析

    通过 DNS 将域名解析为 IP 地址。

  4. 建立TCP连接

    • 通过三次握手(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接。
    • 进行数据传输。这里有一个重要的机制,就是接收方接收到数据包后必须要向发送方确认, 如果发送方没有接到这个确认的消息,就判定为数据包丢失,并重新发送该数据包。当然,发送的过程中还有一个优化策略,就是把大的数据包拆成一个个小包,依次传输到接收方,接收方按照这个小包的顺序把它们组装成完整数据包。
    • 断开连接的阶段。数据传输完成,现在要断开连接了,通过四次挥手来断开连接。
  5. 发送HTTP请求

    TCP连接建立完毕,浏览器可以和服务器进行通信。浏览器发送 HTTP 请求需要携带请求行请求头请求体

    • 请求行示例

      // 请求方法是GET,路径为根路径,HTTP协议版本为1.1
      GET / HTTP/1.1
      
    • 请求头示例

      Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng;q=0.8,application/signed-exchange;v=b3
      Accept-Encoding: gzip, deflate, br
      Accept-Language: zh-CN,zh;q=0.9
      Cache-Control: no-cache  //缓存
      Connection: keep-alive  //连接状态
      Cookie: /* 省略cookie信息 */
      Host: www.baidu.com
      Pragma: no-cache
      Upgrade-Insecure-Requests: 1
      User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
      
    • 请求体

      请求体只有在POST方法下存在,常见的场景是表单提交

  6. 网络响应

    HTTP 请求到达服务器,服务器进行对应的处理。最后要把数据传给浏览器,也就是返回网络响应。网络响应具有三个部分:响应行响应头响应体

    • 响应行示例

      //由HTTP协议版本、状态码和状态描述组成。
      HTTP/1.1 200 OK
      
    • 响应头示例:响应头包含了服务器及其返回数据的一些信息, 服务器生成数据的时间、返回的数据类型以及对即将写入的 Cookie 信息。

      Cache-Control: no-cache
      Connection: keep-alive
      Content-Encoding: gzip
      Content-Type: text/html;charset=utf-8
      Date: Wed, 04 Dec 2019 12:29:13 GMT
      Server: apache
      Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com
      

      如果请求头或响应头中包含 Connection: Keep-Alive,表示建立了持久连接,这样 TCP 连接会一直保持,之后请求统一站点的资源会复用这个连接。

从输入URL到页面呈现流程 - 算法解析:完成了网络请求和响应,如果响应头中 Content-Type 的值是 text/html ,那么接下来就是浏览器的解析和渲染工作了。解析流程:

  1. 构建DOM树
  2. 样式计算
  3. 生成布局树

从输入URL到页面呈现流程 - 渲染过程

  1. 建立图层树(Layer Tree)
  2. 生成绘制列表
  3. 生成图块并栅格化
  4. 显示器显示内容

跨域

广义的跨域指一个域下的文档或脚本试图去请求另一个域下的资源。通常所说的跨域是由浏览器同源策略限制的一类请求场景。同源策略是浏览器为了保证信息安全指定的标准。

同源策略指

  1. 协议相同
  2. 域名相同
  3. 端口相同 这三者相同,否则会限制这三种行为:
  4. CookieLocalStorageIndexDB 无法读取。
  5. DOM 无法获得。
  6. AJAX 被拦截(跨域时可以发送请求且服务器正常返回结果,只是结果被浏览器拦截了)。

SEO

概述:SEO 指搜索引擎优化(Search Engine Optimization),指在了解搜索引擎自然排名机制的基础上,对网站进行调整优化,改进网站在搜索引擎中的关键词的自然排名,获得更多流量的技术。

SEO 一般包含站内优化和站外优化,站内优化指对网站本身进行调整,如网站结构、HTML 代码 等;站外优化指网站的外部建设,如外链、与行业社群的互动等,这些不是在网站本身进行的操作。

这里主要讲站内的 SEO 优化方面中的页面被搜索引擎收录部分内容。

网页如何被搜索引擎收录(搜索引擎爬虫工作流程)

  1. 爬行和抓取:搜索引擎蜘蛛通过跟踪链接发现和访问网页,读取页面 HTML 代码,存入数据库。

    蜘蛛在访问任何一个网站时,会先访问网站根目录下的 robots.txt 文件,如果 robots.txt 文件禁止搜索引擎爬行和抓取某些文件或目录,蜘蛛将不抓取被禁止的网站。

    搜索引擎蜘蛛有表明身份的代理名称,站长可以在日志文件中看到搜索引擎的特定用户代理,从而来识别搜索引擎蜘蛛,如:

    • Baiduspider+ 百度蜘蛛
    • Mozilla/5.0 Firefox/1.5.0.11;360Spider 360蜘蛛
    • Mozilla/5.0 Google蜘蛛

    为了抓取尽量多的页面,搜索蜘蛛会跟踪页面上的链接,从一个页面爬到下一个页面,这也是搜索引擎蜘蛛名称的由来。简单的爬行策略分为两种,一种是深度优先,即蜘蛛沿着发现的链接一直向前爬行,直到没有其他链接后返回第一个页面,沿着另一个链接再一直往前爬行。另一种是广度优先,指蜘蛛在一个页面上发现多个链接时,先把所有第一层爬取一遍,然后再沿着第二次页面上发现的链接爬向第三层。这两种策略通常是混合使用的,理论上任何一个策论都能爬完整个互联网。

  2. 预处理:搜索程序对抓取来的页面数据进行文字提取、中文分词、索引、倒排索引等处理,以备排名程序调用。

  3. 排名:用户输入查询词后,排名程序调用索引库数据,计算相关性,然后按一定格式生成索引结果页面。影响相关性的主要因素包括一下几个方面:

    • 关键词常用程度。页面中经过分词后的多个关键词,对整个搜索字符串的贡献意义并不相同,越常用的词对搜索词的贡献越小,越不常用的词对搜索词的贡献越大。例如,搜索词是 ”红色aioverg“,“红色”这个词的常用程度非常高,对“红色aioverg”这个词的辨识度和意义相关度贡献就很小,这时包含“红色”这个词的页面对搜索排名相关性几乎没影响,而“aioverg”这个词的常用程度就比较低,对“红色aioverg”这个搜索词的贡献要大的多,所以包含“aioverg”这个词的页面,与“红色aioverg”这个搜索词会更为相关。
    • 词频及密度。在没有关键词的情况下,搜索词在页面中出现的次数越多密度越高,说明页面与搜索词越相关,不过这个因素对排名的影响很小。
    • 关键词的位置即形式。关键词出现在比较重要的位置,如标题标签、黑体、H1标签等,说明页面与关键词越相关。
    • 关键词距离。切分后的关键词越与搜索词相匹配,相关度越高。比如:搜索 “红色星球”,被切分为“红色”和“星球”,当关键词中“红色“和”星球”两个词距离越近与搜索词越相关。
    • 链接分析及页面权重。页面之间的链接和权重关系,页面有越多以搜索词为锚文字的导入链接,页面的相关性也强。

    搜索引擎会把一些常见的搜索词的结果排名缓存起来,而不是每次搜索都进行页面排名操作,并重新生成排名。

单页面应用为何不利于 SEO

  1. 前端框架一般是以简化 DOM 操作为出发点的, 由框架通过 JS 来动态生成 DOM 结构,而爬虫在爬取页面解析内容时,不会执行 JS 代码,就导致了由 JS 生成的 链接 和 内容 无法被获取到的问题(听说谷歌的爬虫做到了爬取页面时执行 JS 代码的能力,感兴趣的可以去验证一下)。
  2. 所有子页面共用一个主页面(一般情况下是 public/index.html 这个文件),导致页面都有同相同的头部,无法对相应页面单独设置 titlekeywordsdescription 等利于 SEO 的属性。

解决单页面应用 SEO 的核心

  1. 如何让 JS 生成的 链接 和 内容,在网页下载下来时已经存在内容中,解决页面无法被爬虫发现的问题。
  2. 如何让页面拥有各自的头部,解决无法设置 titlekeywordsdescription 等利于 SEO 的属性的问题。

目前的几种解决方案

  1. 预渲染(SSG - static site generation)

    思路:核心思想是,前端项目打包的时候将需要优化的路由打包成静态文件,这样在爬虫抓取页面时,就可以解析出文件内容了。

    特点

    1. 如果页面过多,会拖累打包速度。
    2. 如果路由是动态的,则很难使用这种方法,因为事先无法知道存在哪些路由。

    其他:预渲染可以使用 prerender-spa-plugin 这个插件来处理,之前 smile-design-pc 这个项目的 SEO 优化就是采取这种方式进行的,感兴趣的可以回退到 Gitcommitseo-init 之前的提交记录去看一下。

  2. 服务端渲染(SSR - Server-side Rendering)

    思路:核心思想是,在服务端将页面组装好后返回给客户端,这时候爬虫爬取的网页是真正展现给用户的网页(在浏览器中点击查看网页源码,可以看到网页的内容是组装好内容)。

    特点

    1. 拖累服务端性能。
    2. 可以处理动态路由。

    服务端渲染框架:Vue 的 Nuxt.js,React 的 Next.js

  3. 针对爬虫做处理

    思路:核心思想是,通过访问来源来判断是否是爬虫访问(各搜索引擎的爬虫都有各自的标识,上面有列出一些),是爬虫访问时,将页面组装完成后返回给爬虫,这时爬虫获取的就是真正展现给用户的网页。

    特点

    1. 一定程度上优化了 服务端渲染 拖累服务器性能的问题,因为只有在爬虫请求时才会组装页面。
    2. 方案可以抽离出来,供其他项目使用。
    3. 可以处理动态路由。

    其他:可以使用 Phantomjs 来实现,它是一个基于 webkit 的浏览器,核心思想是在服务端跑一个浏览器,当爬虫请求时,使用这个浏览器将页面渲染解析完成,然后将完成后的页面交给爬虫,用于搜索引擎收录。


URI与URL

  1. URI 统一资源标识符是 Uniform Resource Identifier 的缩写,URI 用字符串标识某一互联网资源。

  2. URL 统一资源定位符是 Uniform Resource Locator 的缩写,URL 表示资源的地点,是 URI 的子集。

  3. 格式:

    006

    • 协议方案名:使用的文件传输协议。
    • 登录信息(认证):指定用户名和密码作为从服务器端获取资源时必要的登录信息(身份认证)。此项是可选项。
    • 服务器地址:使用绝对URI必须指定待访问的服务器地址。地址可以是DNS可解析的名称,或是192.168.1.1这类IPv4地址名,还可以是[0:0:0:0:0:0:0:1]这样用方括号括起来的IPv6地址名。
    • 服务器端口号:指定服务器连接的网络端口号。此项也是可选项,若省略则自动使用默认端口号。
    • 带层次的文件路径:指定服务器上的文件路径来定位特指的资源。
    • 查询字符串:针对已指定的文件路径内的资源,可以使用查询字符串传入任意参数。该项是可选项。
    • 片段标识符:使用片段标识符通常可标记出已获取资源中的子资源(文档内的某个位置)。该项是可选项。

HTTP协议

HTTP协议:HTTP是无状态协议,即不保存请求和响应之间的通信状态,这样做的目的是为了更快的处理大量事务,但可以借助Cookie技术实现保存状态功能。

HTTPS协议:HTTP协议与SSL协议组合起来称为HTTPS(超文本传输安全协议)。

TCP/IP协议族:计算机与网络设备要相互通信,双方就必须基于相同的方法,比如,如何探测到通信目标、使用哪种语言通信、怎么通信等都需要事先确定。不同的硬件、操作系统之间的通信都需要一种规则,这种规则称为协议。而把互联网相关联的协议集合起来就称为TCP/IP。通常使用的网络(包括互联网)是在TCP/IP协议族的基础上运作的。

TCP/IP的分层管理:指把TCP/IP协议族分类,这样在进行改动时只需要部分改动。TCP/IP协议族分为四层:应用层、传输层、网络层、链路层。其中:

  • 应用层:决定了向用户提供应用服务时通信的活动。TCP/IP协议族预存了各类通用的应用服务,如FTP(文件传输协议)、DNS(域名系统)等。HTTP协议就处于应用层。
  • 传输层:传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。传输层有两个性质不同的协议,TCP(传输控制协议)和UDP(用户数据报协议)。
  • 网络层:网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单元。网络层决定了通过怎样的路径到达对方计算机,并把数据包传送给对方。
  • 链路层:用来处理连接网络的硬件部分,如操作系统、硬件的设备驱动、网卡等。

TCP/IP通信传输流:发送端从应用层往下传,接收端往应用层传;发送端在层与层之间传输数据时,每经过一层会在数据上标记一个属于该层的首部信息;接收端在层与层之间传输数据时,每经过一层会把数据上标记的属于改层的首部信息去掉。如图:

三种与HTTP相关的协议

  1. IP协议:IP协议位于网络层,用来把数据包传送给对方。其中IP地址和MAC地址是IP协议中确保文件正确传送的两个重要条件。IP地址指明了节点被分配到的地址,MAC地址是网卡所属的固定地址。IP地址可以喝MAC地址进行配对,但IP地址可变换,MAC地址基本上不会更改。

  2. TCP协议:TCP协议位于传输层,提供可靠的字节流服务,字节流服务指为了方便传输将大块数据分割成以报文为单位的数据包进行管理。可靠指能够把数据准确的传递给地方。为了保证将数据准确的送达,TCP协议采用了三次握手策略(除了三次握手TCP协议还有其他方法保证通信的可靠性)。三次握手流程如下:

  3. DNS服务:DNS服务于HTTP协议一样是位于应用层的协议,DNS提供域名到IP地址之间的解析。


本地存储

浏览器的本地存储主要分为 CookieWebStorageIndexedDB,其中 WebStorage 又分为 localStoragesessionStorage

Cookie:Cookie 是为了弥补 HTTP 在状态管理上的缺陷,因为 HTTP 协议是无状态协议,当客户端向服务端发送请求,服务端响应后就完成了,当客户端再次向服务端发送请求时,服务端并不知道客户端是谁,为了能让服务端识别客户端就创造了 Cookie。Cookie 是浏览器存储的一个很小的文件,内部已键值对的方式存储(在 Chrome 开发者面板的 Application 这一栏可以看到),向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。

Cookie 的缺点:

  • 容量缺陷:Cookie 的体积上限只有 4KB,只能用来存储少量的信息。
  • 性能缺陷:Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。
  • 安全缺陷:由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,这时在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly 为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。

第三方 Cookie:所有不属于当前域名下的 Cookie 都是第三方 Cookie,如下图中的 Domain.mmstat.com 就是一个第三方 Cookie

大多数网站会引用一些第三方 SDK 来进行前端异常或性能测试,或者收集用户信息分析用户行为,这些操作一般是通过第三方域名来进行的,而第三方 cookie 就是用来标记用户信息识别用户的。

由于第三方 Cookie 被滥用来获取用户隐私,最近浏览器提出了禁用第三方 Cookie 的提案,比如 Chrome 的 Cookie 中新增了 SameSiteSameParty 两个属性来禁用第三方 Cookie ,当然这样依然无法避免用户隐私的泄露,服务商可以利用浏览器指纹等技术来识别用户。

localStorage:与 Cookie 一样,针对一个域名会存储相同的数据。

与Cookie的区别是:

  • 容量:容量上限是5M。
  • 只存在客户端,默认不参与服务端的通信,避免了 Cooke 带来的性能和安全问题。
  • 接口封装。通过 localStorage暴 露在全局。

利用 localStorage 的较大容量和持久性特征,可以存储原型内容稳定的资源,如官网的 logo 等

sessionStorage:与 localStorage 一样,唯一的区别是 sessionStorage 是会话级别的存储,页面关闭就消失了。可以用它对表单信息进行维护,表单信息存储在里面,保证页面刷新也不会让之前的表单信息丢失。也可以用来存储本次浏览记录(如果页面关闭就不在需要这些记录了)。

IndexedDB:运行在浏览器中的非关系型数据库。存储上限不限。示例


缓存

浏览器的缓存是指将资源存储下来,下次再次请求可以直接从本地读取资源,分为强缓存和协商缓存,强缓存不需要发送 HTTP 请求,协商缓存需要需要发送HTTP请求。

强缓存

  1. 设置强缓存

    在第一次进入页面,根据服务端的响应头( Response Headers ),浏览器会查看是否包含强缓存字段对资源进行缓存。

  2. 使用强缓存

    浏览器在使用缓存的时候会先通过相应字段检查强缓存是否可用,如果可用则直接使用。

    表示强缓存是否可用的字段:

    1. ExpiresHTTP/1.0 中使用,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可用直接从缓存中获取数据,无需再次请求。

      如:Expires: web, 11 Nov 2020 00:00:00 GMT 表示资源在 2020年11月11日0点0分 过期,过期了就得向服务器发送请求。

      Expires 有一个缺点就是,它表示的是本地时间,有时候服务器的时间和浏览器的时间可能不一致,所以在HTTP1.1中不再使用这种方法了。

    2. Cache-ControlHTTP/1.1 中使用, 存在于服务端返回的响应头中,它使用过期时长来控制缓存,而不是过期时间。

      如:Cache-Control: max-age=3000 表示在响应返回后的3000秒内可以直接使用缓存。

      Cache-Control还有很多指令,关键属性如下:

      1. public:客户端和代理服务器都可以缓存。
      2. private:只有浏览器可以缓存,中间的代理服务器不能缓存。
      3. no-cache:跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段。
      4. no-store:不进行任何形式的缓存。
      5. s-maxage:代理服务器的缓存时间。

    注意,当 ExpiresCache-Control 同时存在时,浏览器优先考虑 Cache-Control

    这个资源就是使用的强缓存,如图:

协商缓存

  1. 设置强缓存

    在第一次进入页面,根据服务端的响应头( Response Headers ),浏览器会查看是否包含协商缓存字段对资源进行缓存。

  2. 使用协商缓存

    强缓存失效后,如果协商缓存字段存在,浏览器在请求头中携带相应的 缓存tag 向服务器发送请求,由服务器根据 缓存tag 来判断是否继续使用这个强缓存。缓存tag 分为两种:

    1. Last-Modified:即最后修改时间,浏览器第一次给服务器发送请求后,服务器会在响应头中加入这个字段。浏览器收到响应后,如果再次请求,会在请求头中携带 If-Modified-Since 字段,这个字段的值就是服务器传来的最后修改的时间。服务器拿到请求头中的 If-Modified-Since 字段后,会和服务器中该资源的最后修改时间对比,如果该字段的值小于最后修改时间,则表示浏览器需要更新该资源,则返回 200 和 新资源(根常规HTTP请求响应的流程一样);否则返回 304,告诉浏览器直接使用缓存。
    2. Etag:是服务器根据当前文件的内容给文件生成的唯一标识,只要文件内容改变,这个值就会跟着改变。服务器通过响应头把这个值传递给浏览器。浏览器接收到响应后,如果再次请求,会在请求头中携带 If-None-Match 字段,这个字段的值就是服务器传来的 ETag 。服务器拿到请求头中的 If-None-Match 字段后,会与服务器上该资源的 ETag 进行对比,两者不一样,说明资源有改动,则返回 200 和新资源;否则返回 304 ,告诉浏览器直接使用缓存。

注意,当两种方式同时存在时,浏览器优先考虑 ETag

这个资源就是使用的协商缓存,如图:

缓存位置

浏览器中的缓存位置有四种,会按照效率为资源分配缓存位置,按优先级从高到底分别是:

  1. Service Worker

    是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

  2. Memory Cache

    内存缓存,它在效率上是最快的,但存活时间最短,当渲染进程结束后它就不存在了。

  3. Disk Cache

    存储在硬盘中的缓存,读取速度慢,但容量大(主要存储图像和网页等资源)。

  4. Push Cache

    推送缓存,它是 HTTP/2 中的内容,当以上三种缓存都没有命中时,才会被使用,只在会话(Session)中存在,一旦会话结束就被释放。

从这里可以看到资源的来源:

其中,prefetch cache 是 预取缓存,预加载的一种方式,<link> 标签上带了 prefetch,再次加载会出现。

刷新与缓存

  1. ctrl + F5:强制刷新网页,跳过强缓存和协商缓存,从服务端加载。
  2. F5:刷新网页,跳过强缓存,但是会使用协商缓存。
  3. 直接输入URL:走正常流程,使用强缓存和协商缓存。

总结:浏览器在使用缓存时先检查强缓存是否可用,如果可用直接使用;如果不能使用则进入协商缓存,此时发送HTTP请求,查看资源是否更新,若资源更新返回新资源和 200 状态码,若没有更新返回 304 ,继续使用缓存中的资源。


与内存相关的性能问题

内存是有一个根节点开始的树形,NumberBooleanString 是原始类型(primitive)不能引用其他类型,它们总是叶节点或终止节点,如图:

GC(垃圾回收):从根节点无法到达的节点会被垃圾回收。

Retaining Path(保留路径):从根节点可以到达的任意路径。

内存泄露常见来源

  1. 计时器

    const ex = {
        callAgain: function () {
            var ref = this;
            var val = setTimeout(function () {
                console.log('Called again: '+ new Date().toTimeString());
                ref.callAgain();
            }, 1000);
        }
    }
    ex.callAgain() // 这样会得到一个内存泄露
    
  2. 闭包

    var a = function () {
        var largeStr =
            new Array(1000000).join('x');
        return function () {
            return largeStr;
        };
    }();
    
    var a = function () {
        var smallStr = 'x',
            largeStr = new Array(1000000).join('x');
        return function (n) {
            return smallStr;
        };
    }();
    
    var a = function () {
        var smallStr = 'x',
            largeStr = new Array(1000000).join('x');
        return function (n) {
            eval(''); //维持对 largeStr 的引用
            return smallStr;
        };
    }();
    
  3. DOM 内存泄露

    var select = document.querySelector;
    var treeRef = select("#tree");
    var leafRef = select("#leaf");
    var body = select("body");
    body.removeChild(treeRef); // 此时 #tree 不能被垃圾回收,因为 treeRef 在引用它
    treeRef = null; // 此时 #tree 不能被垃圾回收,因为 leafRef 在间接引用它
    leafRef = null; // 此时 #tree 才可以被垃圾回收
    

代码层面避免内存泄露

  1. 在不必要的时候使用局部作用域比引用好些。
  2. 解绑不需要的事件监听。
  3. 管理本地存储,在存储大数据块时要小心,看是否还需要。
  4. 垃圾回收也需要时间,重用对象有助于提高效率。

用Chrome发现内存泄露问题

  • 使用堆快照,查看对象距离 根对象 的距离,如果大多距离很近,而有个别距离很远,需要注意这些距离较远对象,它们可能是泄露的,如图:

  • 使用 closure 来筛选出闭包,如图:

  • Summary:按构造函数名称分组。

    Comparison:比较两个快照。

    Containment:结构图。

    Statistics:汇总饼图。

  • 在时间线中找出内存泄露,静止的网站的 堆栈 一般来说是在一个固定值周围波动的,如果随着时间增长可能存在内存泄露。

可以感知的性能问题

  • 随着时间的推移页面性能越来越差,这可能是内存泄露造成的,随着时间的推移内存占用越来越多,导致的页面性能越来越差。
  • 页面性能一直很差,这可能是内存过大造成的,即页面使用的内存超过了页面最佳速度所需要的内存。
  • 页面出现延迟或频繁暂停,这可能是频繁垃圾回收造成的,浏览器在进行垃圾回收的时候会暂停所有脚本的执行。

用 Chrome 发现问题

示例文档

  • 在时间线中查看各项指标,使用 Performance monitor ,如:

    其中频繁的垃圾回收会使 JS heap size 的时间线条波动频繁。


Chrome控制台Console选项卡介绍

Console 有两个主要功能,运行 JavaScript 和 查看日志。

1:按 日志来源 过滤日志,在这里 各种日志 会按照日志来源(来源文件)进行归类。

2:清空输出面板

3:选择上下文,top 指主文档的上下文,当在页面中使用了如 <iframe> 嵌套其他页面的话,可以选择其他页面的上下文。

4:时时观察表达式。如:输入 document.activeElement 观察当前激活的元素,按 ctrl+enter 保存,激活不同的元素时会看到值的变化。

5:按 文本 过滤日志,日志中包含改 文本 的才会显示,过滤支持 正则表达式。按 url 过滤消息,格式是:url:XXXX,按 url 反向过滤消息(不显示该 url 的消息):-url:XXXX

6:按 日志级别 过滤日志

7:

8:控制台设置。

  • Group similar message in console:对消息进行分组。
  • Log XMLHttpRequests:记录 XMLHttpRequestFetch 请求。
  • Preserve Log:跨页面保存日志,一般在加载新页面时会清空前一个页面的日志,使用这个设置可以在加载新页面时保存之前的日志。
  • Hide network:隐藏网络消息。
  • Selected Context Only:是否只显示当前页面上下文的日志,如当页面中使用了 <iframe> 嵌套其他页面话,默认也会将嵌套页面的日志输出。
  • Eager Evaluation:表达式预览,当输入一个表达式时会显示一些预览内容。
  • Autocomplete From History:历史自动补全,当输入内容时,浏览器会自动从历史中显示匹配的信息以供选择。

console 选项卡 与其他 选项卡一起使用:打开其他选项卡,按 ESC 键。或者在 更多选项卡工具 中选择 Show Console Drawer

一些 console 函数

  1. console.assert(expression, object):当 表达式expression 错误时,将 object 在控制台打出。

  2. console.clear():清空控制台。

  3. console.count([label]):相同 lableconsole 的运行计数。

  4. console.countReset([label]):重置 labelconsole 的运行计数。

  5. console.debug(object [, object, ...]):与 Verbose 级别的日志。

  6. console.dir(object):打印指定对象的 JSON 表示。

  7. console.dirxml(node):打印 节点 后代的 XML 表示。

  8. console.error(object [, object, ...]):打印 object ,将其输出为一个错误,并包含在堆栈中。

  9. console.group(label):将 label 标签的日志分为一组打印。与console.groupEnd(label)console.groupCollapsed(label) 一起使用,如:

    const label = 'A'
    console.groupCollapsed(label) // 折叠分组, 不写分组是默认展开的。
    console.group(label)          // 分组开始
    console.info(1)               // 分组项
    console.log(2)
    console.error(3)
    console.groupEnd(label)       // 分组结束
    
  10. console.info(object [, object, ...]):打印信息,与 console.log() 相同。

  11. console.log(object [, object, ...]):打印信息。

  12. console.table(array):打印一个表格。如:

    console.table([
        {one:1, two: 2},
        {one:3, two:4, thr: 5}
    ])
    
  13. console.time([label]):启动一个标签为 lable 的定时器。

  14. console.timeEnd([label]):结束一个标签为 lable 的定时器,并返回定时器运行时间。

  15. console.trace():打印 堆栈 到控制台。如:

    const fx = () => {console.trace()}
    fx()
    
  16. console.warn(object [, object, ...]):打印一个警告信息。

一些特殊符号和函数

  1. $_:返回最近求值的表达式的值。

  2. $0$1$2$3$4:返回 Elements 面板中最后检查的五个元素,或在Profiles 面板中选择的最后五个 JavaScript 堆对象。

  3. $(selector, [startNode]):返回指定选择器的 selector 的第一个元素。startNode 用于指定从指定元素内开始搜索。

  4. $$(selector, [startNode]):与 $(selector, [startNode]) 一样,但会选择所有匹配元素。

  5. clear():清空控制台。

  6. copy(object):将 object 复制到剪切板。

  7. debug(function):调用函数 function 时将进行 debug 中断调试。

  8. dir(object):与 console.dir() 一样。

  9. dirxml(object):与 console.dirxml() 一样。

  10. inspect(object/function):当参数是一个 节点对象 时打开 Elements 面板并选中改元素,当参数是 函数 时打开 Sources 面板,并打开源文件。

  11. getEventListeners(object):返回在指定对象上注册的事件监听器。

  12. keys(object):返回由对象的 组成的数组。

  13. monitor(function):监视指定函数,当函数被调用时返回函数的名称和调用的参数。如:

    function fx(x, y) {
      return x + y
    }
    monitor(fx)
    fx()
    // 输出
    // function sum called with arguments: 1, 2      这个是 monitor(fx) 的输出
    // 3 这个是 fx() 的输出
    
  14. monitorEvents(object[, events]):当指定 事件events 在指定 对象object 上发生时,打印 Event 对象。如:

    monitorEvents(window, ["resize", "scroll"]) // 在 window 上监听 resize 和 scroll 对象
    
  15. profile([name])profileEnd([name]):使用 name 启动和关闭一个 JavaScript CPU 剖析会话。

  16. queryObjects(Constructor):返回一个由自定构造函数创建的对象数据,作用域是当前上下文。

  17. table(data[, columns]):与 console.table() 一样。

  18. undebug(function):停止指定函数的 debug 调试。

  19. unmonitor(function):停止监视指定函数。

  20. unmonitorEvents(object[, events]):停止监听指定对象上的事件,不写 events 参数时,停止所有事件。

  21. values(object):将对象的 组成数组返回。


Elements 选项卡

  1. DOM树

    在文档的 DOM 树中可以对 DOM 增加属性,编辑 DOM,插入新DOM,排序,使 DOM 保持某种状态(active 等)等操作。

  2. Styles:Styles:显示所有应用于元素的规则,包括被重写不生效的规则。Styles 选项卡下方是和模型,双击可以进行修改。如图:

    • .cls :可以为元素增加一个类。

    • +:用于增加一个样式规则,长按会弹出将样式规则应用于哪里的选框。

    • 修改颜色,选中颜色前面的图块,会弹出颜色选择器,同时可以在页面上吸取颜色进行设置,如图:

    • 修改角度,同样选择角度前面的仪表盘,会弹出角度选择器。

  3. Computed:Computed:显示生效的元素规则。Show all 选项可以显示继承的属性。如图

  4. Layout:页面布局,显示页面上使用 grid 布局和 flex 布局的元素,如图



Performance 选项卡

记录和分析页面在运行时的所有活动,解决性能瓶颈。为了消除插件等因素的影响在 隐身模式 下进行性能分析。

性能分析示例文件

  • 01 - 启用屏幕帧,在录制性能时录制每一帧的截图。
  • 02 - 在录制页面时点击会强制进行垃圾回收。
  • 03 -
  • 04 -
  • 05 -
  • 06 - 在录制性能时是否在 Main 中记录 JavaScript函数 的调用栈。
  • 07 -
  • 08 - 控制网络状态。
  • 09 - 降低 CPU 性能,进行录制。
  • 10 - 开始录制性能指标。
  • 11 - 重新加载页面并录制性能指标。

性能指标

  • 01 - FPS图,绿色越高帧率越高,当顶部出现红色时,标识帧率并不太理想。

  • 02 - CPU图,蓝色的部分标识CPU活动情况,当颜色全是蓝色时,标识CPU占用过高。

  • 03 -

  • 04 - 帧,将鼠标悬浮在某一帧上时,会显示当前帧的FPS。要查看实时帧率也可以在 Rendering 中设置,如图:

  • 05 -

  • 06 - 主线程活动图,X 方向表示时间,Y 方向标识表示调用栈,每个条形表示一个事件,上下堆叠的条形表示上层事件导致了下层事件,点击事件,下方会显示该事件的触发函数,点击链接可以在源文件中查看该事件函数的更多信息,如图:

    其他的几个选项的含义是,Bottom-Up(花费最多时间的根活动)Call Tree(导致最多工作的根活动)Event log(按照活动顺序查看根活动),跟活动指导致浏览器工作的活动,例如当点击一个页面时,浏览器触发一个 Event 事件作为跟活动,Event 事件可能导致处理程序执行等。在 Main 中,根活动位于火焰图最顶部,在 Call TreeEvent log 根活动是顶级项。

    Bootom-up 为例:

    Slf Time:直接花费在该活动上的时间。

    Total Time:花费在该活动和其子项上的总时间。

    Activity:折叠项的第一级是根活动,其他项是根活动的调用栈。

    Show Heaviest Stack:展开会在右侧显示最重的调用堆栈。

  • 07 - 光栅活动

  • 08 - GPU 活动。

  • 09 -

  • 10 -

各种名词解释

连接


Network 选项卡

01:是否开启网络日志。

02:清空网络日志。

03:展开过滤功能,可以按照请求类型等进行过滤。

  • Hide data URLs:隐藏 Data URLsData URLs 是嵌入文档的其他小文件,请求中以 data: 开头。

04:展开搜索功能。

06:是否跨页面保存网络日志。

07:快速设置网络类型,如 3G 等。

08:更多网络设置功能。

09:禁用浏览器缓存,可以模仿用户首次打开页面的效果。

10

11:其他设置。

  • Show overview:网络请求快照。
  • Caputre screenshots:页面加载快照,点击快照会显示当前快照的网络信息。

12:网络活动日志。每一行代表一个资源,默认按资源顺序列出,默认显示:

  • Name:资源名称。
  • Status:资源状态。
  • Type:资源类型
  • Initiator:资源请求源,点击连接会打开请求资源的文件。
  • Size:资源大小。
  • Time:资源耗费时间。
  • Waterfall:请求资源不同阶段的图形表示,默认按照请求开始的时间从左到右。

网格日志的列是可配置的,放在列头点击鼠标右键会弹出所有列的列表,选择一个资源会展示它的详细信息。如:

  • 1 - 所有可选的列。
  • 2 - 当前列的排序规则。
  • 3 - 重置。
  • 4 - 请求头中的字段作为列显示出来,可以自定义。
  • 5 - Waterfall 的排序规则。

当鼠标在某一行日志的 Name 上,单击鼠标右键会弹出如下弹框:

  • Clear broswer cache:清空浏览器缓存。
  • Clear broswer cookies:清空浏览器 Cookies
  • Replay XHR:重新发送这个请求。
  • Sort By:当前列的排序规则,默认以时间进行排序。

请求详情

点击一个请求,可以打开请求详情(注意,当请求是 WebSocket 时,会与 HTTP 有差异)。

  • Cookies :存放当是前请求使用的 Cookies,当没有使用 Cookies 时没有这个字段。

  • Timing:存放请求的时段信息。如图:

    Queueing:排队时段,浏览器会在这些情况下将请求加入队列,1、有优先级更高的请求;2、这个起点已经打开了6个 TCP 连接(是浏览器的限制);3、浏览器在磁盘中分配空间。

    Stalled:请求延迟的时段(请求可能由于其他原因延迟)。

    Proxy negotiation:浏览器与代理服务器协商请求的时段。

    Request sent:请求发送的时段。

    DNS Lookup:浏览器解析请求 IP 地址的时段。

    Initial connection:建立连接的时段,包括建立 TCP 等。

    Request sent:请求正在发送的时段。

    ServiceWorker Preparation:浏览器启动服务的时段。

    Request to ServiceWorker:请求发送的时段。

    Waiting (TTFB):浏览器等待响应的时段。

    DNS Lookup:浏览器接收响应的时段。

    Receiving Push

概览

概览中会显示请求数,总加载时间等信息。


Application 选项卡

用来管理浏览器的存储、缓存等。

Cookies

  1. Name:名字。
  2. Value:值。
  3. Domain:允许接收 cookie 的主机。
  4. Path:接收 cookie 的路径。
  5. Expires / Max-Age:过期时间。
  6. Size:大小
  7. HttpOnly:是否只在 HTTP 中使用。
  8. Secure:是否只能通过 HTTPS 发送。
  9. SameSiteCookie 是否使用 SameSite 属性,如果使用了值为 strictlaxstrict 阻止第三方 Cookielax 阻止在使用危险 HTTP 请求方法时携带第三方 Cookie (如 POST 请求)。
  10. SameParty:是否开启共享 cookie,用来指定那些站点可以共享 Cookie(由服务端设置)。
  11. Priority:是否使用了 Priority 属性(已废弃),如果使用了值为 lowmediumhegh

Background Services

用于浏览器的后台活动,即使没有打开控制台。


Source 选项卡

  • 1 - 文件导航窗格,页面请求的文件都在这里。

  • 2 - 文件编辑窗格,选择一个文件后文件内容会在这里显示,可以直接在这里对代码进行编辑调试。

  • 3 - 调试窗格。

    01 - 恢复执行脚本,直到下一个断点。

    02 - 执行下一行代码(一行一行执行代码,但不会进入代码详情,如执行一个函数 fx() 不会跳到 fx 内部去)。

    03 - 执行下一行代码(一行一行执行代码,会进取代码详情)。

    04 - 跳出当前执行块(执行代码在无关函数中时,执行 跳出 会将当前函数执行完毕,并跳出去)。

    07 - 异常断点设置。

    08 - 线程,展开会显示可选的上下文,如 08 表示主上下文,箭头表示当前选中的上下文。

    10 - 监视,可以在里面输入表达式对变量等进行监听。

    11 - 所有行断点,可以在这里取消行断点。

    12 - 当在一行代码上暂停时,会显示当前定义的变量(局部变量和全局变量)。

    13 - 当前调用的堆栈。

    14 - XHR 断点设置。

    15 - 设置 DOM 类型的断点。

    17 - 事件监听断点,可以选择在某类型的事件触发时进行断点

断点类型

  • Line-of-code:行断点。在 控制台 相应代码行可以加入行断点,或者在源码中使用 debugger 触发。

  • Conditional line-of-code:条件行断点,当条件满足时触发断点。设置一个条件断点,在 控制台 中打开源代码,在需要加入点断的行号单击鼠标右键,弹出弹框,选择 Add conditional breakpoint,如图:

  • DOMDOM 断点,当修改 DOM 时触发的断点。在 Elemets 选项卡中,选择 DOM 单击鼠标右键,弹出弹框设置断点(subtree modifications(删除或新增当前节点的子节点时触发)attribute modifications(在当前节点添加删除修改属性时触发)node removal(当前节点被移除时触发)),如图:

  • XHR:当 XHR URL 包含指定子字符串时触发的断点。设置方法如图:

  • Event listener:事件断点,当事件发生时触发的断点。

  • Exception:异常断点,在抛出捕获或未捕获的异常时触发的断点。设置方法如图:点击图标开始异常断点,Pause on caught exceptions 是是否开启已经捕获的异常的断点。

  • Function:函数断点,当指定函数运行时触发的断点。在源码中使用 debug(函数名) 来对指定函数进行断点。

忽略代码

忽略一个无关的脚本,可以在调试的时候跳过它。

  1. 从编辑器窗格中忽略脚本,在编辑器中打开源代码 => 选中要忽略的代码 => 单击鼠标右键 => 选择忽略当前代码。如图:

  2. 在调用栈中忽略脚本,如图:

  3. 在设置里面进行配置,如图:

创建代码片段

当需要在很多页面重复运行一些代码时,可以创建代码片段来完成。

查看创建的代码片段,如图:

新增、查看、运行代码片段,如图:

1 - 新增代码片段。

2 - 查看代码片段,选中代码片段单击右键可以重命名、删除、运行代码片段。

3 - 编辑代码片段。

4 - 运行代码片段。

开启禁用 JavaScript

在命令面板中搜索 Disable JavaScript

在命令面板中搜索 Enable JavaScript


Lighthouse 选项卡

用来测试页面性能、SEO等,并给出测试结果,辅助页面优化。

  • 01 - 每次审计时清除与页面相关的数据。
  • 02 - 节流,使用较差的网络和性能进行审计。
  • 03 - 审计网页性能。
  • 04 -
  • 05 -
  • 06 -
  • 07 - 审计网页SEO能力。
  • 08|09 - 审计的设备类型,08是移动设备,09是桌面设备。

如对 必应 的检查结果如下:

会给每一个检查指标一个得分,在指标详情下会给出优化建议。


浏览器控制台选项卡介绍(Other选项卡)

  1. Coverage:查看当前页面加载的 CSSJS 文件,并用 红色 表示被使用的部分,绿色表示未被使用的部分,如图:

  2. CSS Overview:CSS 概览,生成页面的 CSS 概览信息,分为五类:

    • Overview summary:页面 CSS 的汇总摘要。
    • Colors:页面的所有颜色,会按背景、文本等进行分类,点击颜色会显示使用颜色的元素信息。
    • Font info:页面的所有字体信息,按字体大小、行高等进行分类,点击查看受影响的元素。
    • Unused declarations:页面所有没有效果的样式,按没有效果的原因进行分组。
    • Media queries:页面中的所有媒体查询,按出现次数排序,点击可以查看受影响的元素。
  3. Animations:检查和修改动画,Animations 会捕捉页面正在运行的动画,如果动画不是自动运行的,需要将动画运行起来才能捕捉到改动画。如图:

    • 1 区可以运行暂停所有动画,调节动画运行的速度。
    • 2 区是动画概览,鼠标悬浮可以预览动画,单击可以选中动画。
    • 3 区是选中的动画的时间轴,可以开启和暂停选中的动画。
    • 4 区是动画的元素,点击可以查看动画代码。
    • 5 区是动画组,有元素的所有动画组成,在这里可以调节动画的起止时间和持续时间,查看动画某一时间的状态等。
  4. Rendering:预览打印功能。

  5. Issues:显示浏览器检测到的问题的解决方案(出现有些报错,浏览器会在 Issues 显示解决方案)。

  6. Run Command:命令菜单,提供一些快速打开选项卡和操作的函数。

  7. performance monitor(性能监控器)

  8. Memory inspector(内存检查器)示例文档 如图:

    • 1 - 禁止编码
    • 2 - 当前字节码
    • 3 - ASCII 码
  9. Media:在使用了媒体播放器的页面可以检查播放器相关的内容。

  10. WebAuthn:虚拟身份验证器。