页面FPS分析

chrome现在有一个高大上的fps观测工具,你可以不用再去分析performance面板的跑分了。

开启FPS工具

方法一

打开devTools,如果你是mac请按组合键 commend + shift + p (win系统自行翻译),然后再搜索框中输入fps,你会看到如下的状态。

选择第一个结果回车即可

方法二

在更多工具的Rendering面板下面,选中“Frame Rendering Stats”即可

这个工具有个缺点是位置不能移动。

根据工具分析FPS

看下面这张图就够了

总结来说就是:

  • 左上角的百分比越高越好
  • 红色块越少越好
  • 红色块越均匀越好

参考资料:

chrome工具之performance面板

前言

你可以通过performance标签下面的learn more,

或者直接跳转

来学习官方的教程。

这里对重点部分做一个摘要来方便你的快速使用。

生成性能数据

1、在DevTools中,单击记录 。当页面运行时,DevTools捕获性能指标。

2、等待一段时间,期间可以执行一些自己想要检测的操作

3、点击红点或者点击stop停止录制查看结果

简单分析数据

官方已经写得很通俗易懂了不需要再概括了,请直接看官方教程

https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/?utm_source=devtools#analyze

文中也给了一些优化建议,有兴趣也可以看看。

注:教程中fps分析部分已经过时这里可以查看页面FPS分析

chrome工具之memory面板

原始版本的工具叫profiles,chrome87+以后一部分改为了memory和performance。

开发过程中难免会遇到内存问题,本次笔记主要是记录一下项目运行过程中内存情况的解析工具。

查看页面在浏览器中使用的内存

如果只想查看当前浏览器的各个 tab 正在使用的内存量,则在设置 - 更多工具 - 任务管理器即可。效果如下图:

Memory 面板介绍

上面有三个按钮:

  • Heap snapshot – 用以打印堆快照,堆快照文件显示页面的 javascript 对象和相关 DOM 节点之间的内存分配
  • Allocation instrumentation on timeline – 在时间轴上记录内存信息,随着时间变化记录内存信息。
  • Allocation sampling – 内存信息采样,使用采样的方法记录内存分配。此配置文件类型具有最小的性能开销,可用于长时间运行的操作。它提供了由 javascript 执行堆栈细分的良好近似值分配。

下面详细介绍一下三个功能(时间关系这里直接粘贴了原文内容)

Heap snapshot

给个 html,里面只有一句 js 代码 var _____testArray_____ = [ {value: 'hello'} ];,打个堆栈看看:

右上那块区域,从左到右有三个操作:查看方式、对象归类的筛选、对象选择。

左边有 Summary 字样的那个,可以选择查看内存快照的方式,可选方式如下:

  • Summary – 可以显示按构造函数名称分组的对象。使用此视图可以根据按构造函数名称分组的类型深入了解对象(及其内存使用),适用于跟踪 DOM 泄漏。
  • Comparison – 可以显示两个快照之间的不同。使用此视图可以比较两个(或多个)内存快照在某个操作前后的差异。检查已释放内存的变化和参考计数,可以确认是否存在内存泄漏及其原因。
  • Containment – 此视图提供了一种对象结构视图来分析内存使用,由顶级对象作为入口。
  • Statistic – 内存使用饼状的统计图。

附上 Comparison 效果,大致如下:

代码:

var _____testArray_____ = [{ value: 'hello' }]

function someTodo() {
  _____testArray_____.push({
    value: ':::::::::'
  })
}

document.querySelector('#btn').addEventListener('click', someTodo, false)

点击按钮后,数组中 push 了新的一项对象

图(array 那块列表展开就看不到下面列表了,就没展开):

附上 Containment 视图,它的排列稍微有些不同,大致如下:

入口有:

  • DOMWindow – 是被视为 JavaScript 代码 “全局” 对象的对象。
  • GC – VM 的垃圾使用的实际 GC 根。GC 根可以由内置对象映射、符号表、VM 线程堆栈、编译缓存、句柄作用域和全局句柄组成。
  • 原生对象 – 是 “推送” 至 JavaScript 虚拟机内以允许自动化的浏览器对象,例如 DOM 节点和 CSS 规则。

中间的 Class filter 只能够按照列出来的 Constructor 值进行筛选。

右边的 All objects 能够选择查看哪些阶段的对象、如”Objects allocated before Snapshot1″、”Objects allocated between Snapshot1 and Snapshot2″

右中那块区域显示的内存快照信息,可以在各个数据上右键选择一些操作( Reveal in Summary view ),各个字段代表信息如下:

  • Contructor – 表示使用此构造函数创建的所有对象
  • Distance – 显示使用节点最短简单路径时距根节点的距离
  • Shallow Size – 显示通过特定构造函数创建的所有对象浅层大小的总和。浅层大小是指对象自身占用的内存大小(一般来说,数组和字符串的浅层大小比较大)
  • Retained Size – 显示同一组对象中最大的保留大小。某个对象删除后(其依赖项不再可到达)可以释放的内存大小称为保留大小。
  • New – Comparison 特有 – 新增项
  • Deleted – Comparison 特有 – 删除项
  • Delta – Comparison 特有 – 增量
  • Alloc. Size – Comparison 特有 – 内存分配大小
  • Freed Size – Comparison 特有 – 释放大小
  • Size Delta – Comparison 特有 – 内存增量

右下那块区域显示的是被选中对象的详细信息,如上面图片的内容一样一样的…可以在各个数据上右键选择一些操作( Reveal in Summary view )。

注意:图中最最下面那块最有用,就是搜索,ctrl/command + f 唤出

最后,根据上面的图来分析一下上面代码产生的效果,根据 js 的类型和引用的关系来分析,变量_____testArray_____ 在列表中的情况是:

  • 基础类型 string 值为 hello ,内存标记是 string@353953,这个 string 值存在于 Object @362113 对象上的 value 属性上;
  • Object @362113 在 Object 列表里,在 Array @356493 的索引 0 位置存在该对象的引用;
  • Array @356493 在 Window / @353829 对象上存在引用,属性名为”___testArray___”;
  • Window / @353829 是个 Windows 对象,在 Windows 列表里。
"hello" -> 在(string)列表里 -> string@353953 -> value in Object @362113

    Object  -> 在 Object 列表里 -> [0] in Array @356493

        Array -> 在(array)列表里 -> _____testArray_____ in Window / @353829

                Windows ->  在 Windows 列表里 -> Window / @353829

Allocation instrumentation on timeline

看完静态的快照,再来看看动态的。

代码如下:

var _____testArray_____ = [{ value: 'hello' }]
var count = 1

function someTodo() {
  // 每次点击 字符串长度都以上一次为基础增加到5倍,拉大差异突出效果,并且之后在字符串头部加上count值做区分
  count *= 5
  var str = new Array(count * 10).join(':')
  _____testArray_____.push({
    value: count + str
  })
}

document.querySelector('#btn').addEventListener('click', someTodo, false)

选择 Allocation instrumentation on timeline 点击开始记录的按钮,然后得到如图所示:

每条线的高度与最近分配的对象大小对应,竖线的颜色表示这些对象是否仍然显示在最终的堆快照中。蓝色竖线表示在时间线最后对象仍然显示,灰色竖线表示对象已在时间线期间分配,但曾对其进行过垃圾回收。(这图中不是很明显,放大 devtool 面板后,图中的蓝色线顶部是有部分是灰色的…)

可以选择时间范围,查看该时间范围内的内存变化情况,如上图 5 次变化的情况分别是:

# 前面的数字代表本次记录索引,点击了5次

# 0  Shallow Size : 112
Constructor         Distance   Shallow Size    Retained Size
- - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - -
(array)×9                3      5008  0%         5008  0%
(system)×60              3      2416  0%         2640  0%
(closure)×1              3      4768  0%         2928  0%
Object×3                 3      144   0%         768   0%
MouseEvent×3             4      112   0%         7200  0%
(string)×2               5      96    0%         96    0%
(concatenated string)×2  4      64    0%         160   0%
Event                    5      56    0%         2040  0%
UIEvent                  5      32    0%         648   0%

# 1
(string)×2               5      296   0%         296   0%
(concatenated string)×2  4      64    0%         360   0%
Object                   3      32    0%         392   0%

# 2
(string)×2               5      1296  0%         1296  0%
(concatenated string)×2  4      64    0%         1360  0%
Object                   3      32    0%         1392  0%

# 3
(string)×2               5      6296  0%         6296  0%
(concatenated string)×2  4      64    0%         6360  0%
Object                   3      32    0%         6392  0%

# 4
(string)×2               5      31296 0%         31296 0%
(array)                  4      80    0%         80    0%
(concatenated string)×2  4      64    0%         31360 0%
(system)                 4      32    0%         32    0%
Object                   3      32    0%         31392 0%

当勾选 Record allocation stacks 框后,还可以在 Allocation stack面板里打印出调用堆栈。

如上面代码的效果:

Allocation sampling

这个功能仅根据名称和说明的话,不是很能看得懂是什么…

但是,还是通过一些案例给出了效果图,可以看出这块的功能应该是:哪些函数影响了内存的分配,并且该函数所耗内存在内存分配中占比多少。效果图如下:

图中函数可以直接点击跳转到函数定义的文件和位置。

参考资料

前端主要性能的相关指标

这里主要以谷歌的技术文章为依据,此处主要是对参考文章的总结和摘要。

定义指标

为了确保用户体验,我们通常围绕以下问题来进行性能的相关优化:

Is it happening?发生了么Did the navigation start successfully? Has the server responded?导航是否成功启动?服务器响应了吗?
Is it useful?是有用的么Has enough content rendered that users can engage with it?是否渲染了足够的内容给用户?
Is it usable?可以使用了么Can users interact with the page, or is it busy?用户可以与页面进行交互,还是页面仍在忙碌中?
Is it delightful?流畅么Are the interactions smooth and natural, free of lag and jank?交互是否流畅,没有延迟和卡顿?

如何度量

  • 通过实验环境进行测试
  • 通过实际生产环境进行测试

指标类型

  • 感知的加载速度:页面加载并将其所有可视元素呈现到屏幕的速度。
  • 加载响应度:页面加载和执行任何必需的JavaScript代码以使组件快速响应用户交互的速度
  • 运行时响应性:页面加载后,页面对用户交互的响应速度有多快。
  • 视觉稳定性:页面上的元素是否会造成用户不期望的方式移动,并可能干扰他们的交互?
  • 平滑度:过渡和动画是否以一致的帧速率渲染并从一种状态流畅地流动到另一种状态?

重要的衡量指标

  • First contentful paint (FCP): measures the time from when the page starts loading to when any part of the page’s content is rendered on the screen. (labfield)
    • 第一个内容的绘制:测量从页面开始加载到屏幕上呈现页面内容的任意部分的时间。
  • Largest contentful paint (LCP): measures the time from when the page starts loading to when the largest text block or image element is rendered on the screen. (labfield)
    • 最大内容的绘制:测量从页面开始加载到屏幕上最大的文本块或图像元素被渲染的时间。
  • First input delay (FID): measures the time from when a user first interacts with your site (i.e. when they click a link, tap a button, or use a custom, JavaScript-powered control) to the time when the browser is actually able to respond to that interaction. (field)
    • 首次输入的延迟:测量从用户第一次与您的站点交互(即,当他们单击链接、点击按钮或使用自定义的、JavaScript驱动的控件)到浏览器实际能够响应该交互的时间。
  • Time to Interactive (TTI): measures the time from when the page starts loading to when it’s visually rendered, its initial scripts (if any) have loaded, and it’s capable of reliably responding to user input quickly. (lab)
    • 互动时间:衡量从页面开始加载到可视化呈现之间,它的初始脚本(如果有)已加载以及可以可靠快速地响应用户输入的时间。
  • Total blocking time (TBT): measures the total amount of time between FCP and TTI where the main thread was blocked for long enough to prevent input responsiveness. (lab)
    • 总阻塞时间:测量FCP和TTI之间的总时间。如果主线程被阻塞的时间足够长,会阻止输入响应。
  • Cumulative layout shift (CLS): measures the cumulative score of all unexpected layout shifts that occur between when the page starts loading and when its lifecycle state changes to hidden. (labfield)
    • 累积布局偏移:衡量在页面开始加载到页面生命周期状态 变为隐藏之间发生的所有意外布局转移的累积分数。
    • 这个性能分数和其他指标不太一样,该指标依赖视觉及交互相关的设计
    • 计算公式:layout shift score = impact fraction * distance fraction
  • Frames Per Second(FPS): 是指画面每秒传输帧数
该处的内容会有一个专题来描述每个指标具体怎么看。

指标的参考值

注意参考值中的“参考”,表示以下标准适用于大多数场景,但具体情况仍需要具体分析,不可一概而论。

  • FCP:1秒内
    • To provide a good user experience, sites should strive to have First Contentful Paint occur within 1 second of the page starting to load. To ensure you’re hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.
  • LCP:2.5秒内
    • To provide a good user experience, sites should strive to have Largest Contentful Paint occur within the first 2.5 seconds of the page starting to load. To ensure you’re hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.
  • FID:100毫秒以内
    • To provide a good user experience, sites should strive to have a First Input Delay of less than 100 milliseconds. To ensure you’re hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.
  • TTI:5秒内
    • To provide a good user experience, sites should strive to have a Time to Interactive of less than 5 seconds when tested on average mobile hardware.
  • TBT:300毫秒以内
    • To provide a good user experience, sites should strive to have a Total Blocking Time of less than 300 milliseconds when tested on average mobile hardware.
  • CLS:分数小于0.1
    • To provide a good user experience, sites should strive to have a CLS score of less than 0.1. To ensure you’re hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.
  • FPS: (注:该数据没有找到十分权威的来源,如果你有更权威的来源可以@feiyugao进行补充)
    • 在网页中,帧率能够达到50~60fps的动画将会相当流畅,让人倍感舒适。
    • 帧率在30~50fps之间的动画,因各人敏感程度不同,舒适度因人而异。
    • 帧率在30fps以下的动画,让人感觉到明显的卡顿和不适感。
    • 帧率波动很大的动画,亦会使人感觉到卡顿。

参考资料

前端第三方登录授权策略专题

最近项目中关于授权登录的内容比较多,涉及的同学比较多,但是大家的知识点有些参差不齐,这边的专题科普一下相关点,并抛出一个我关注的iframe中的第三方页面登录态问题

先从什么是SSO单点登录开始

Single Sign-On,简单说就是一次访问,多处可用。

了解SSO之前,我们也可以了解一下CAS( Central Authentication Service ),来帮助我们跟深入的了解这个能力的背景:https://developer.ibm.com/zh/articles/os-cn-cas/

从官方文档总结来看,基础模式的SSO流程主要有以下步骤:

  1. 访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。
  2. 定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。
  3. 用户认证:用户身份认证。
  4. 发放票据: SSO 服务器会产生一个随机的 Service Ticket 。
  5. 验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。
  6. 传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。

那是不是还有多点登录

多点登录用一句话概括是,一个账号可以在多个端登录,但是每个端只能有一个该账号的登录实例。

IM是典型的多点登录应用。

多点登录一般不用于第三方登录,这里不做讨论。

Oauth2.0授权

第三方单点登录主要目的是授权,Oauth2.0,是现在最为推荐使用的授权机制。

入门:http://www.ruanyifeng.com/blog/2019/04/oauth_design.html

进阶:https://tools.ietf.org/html/rfc6749

既然有2.0,那就也有1.0,想要了解历史可以看下面的文章

入门:https://sexywp.com/oauth-1-intro.htm

进阶:http://oauth.net/core/1.0/

iframe中第三方页面授权和登录

说到授权我们通常会有3个角色:

  • 用户
  • 授权方
  • 接入方

该文章主要会从授权方和接入方角度分别讨论。

前提

文章的所有讨论基于以下前提

  • 只讨论iframe和父页面是跨域的场景
  • https场景

iframe的局限性

写这篇文章的背景是因为chrome在80版本以上彻底禁止了iframe中第三方页面通过js操作cookie,但是接口可以通过设置sameSite来设置跨域的cookie。

当前这个问题在需要使用cookie的前提下还没有解决方案:https://support.google.com/analytics/thread/35313442?hl=en

关于sameSite可以参考该文章:https://cloud.tencent.com/developer/article/1589715

safari也有同样的问题:http://vitr.github.io/safari-cookie-in-iframe/

如何处理iframe中js无法操作cookie的问题

而这个问题并不是覆盖所有的浏览器,那么项目中我们怎么对这个问题进行兼容,可以参考下面的策略。

策略来自于:https://www.tinywebgallery.com/blog/advanced-iframe/advanced-iframe-pro-demo/3rd-party-cookie-in-iframe-workaround

cookie.set('the_cookie', 'the_value', '', '', { expires: 365, path: '/' });
const postData = { id: 'checkCookie', state: '0' };
if (cookie.get('the_cookie')) {
    postData.state = '1';
    window.postMessage({ id: 'checkCookie', state: '1' }, '*');
}

window.parent.postMessage(JSON.stringify(postData), '*');
if (window.opener) {
    window.opener.postMessage(JSON.stringify(postData), '*');
}

该段代码的功能就是通过postMessage通知父级页面(父级不仅是iframe的引用页面,也可以是通过window.open打开当前页面的网页)当前页面是否可以通过js操作cookie。

父级页面可以通过接收这个消息来决定sso登录授权的相关策略,是打开新页面还是直接在iframe里面完成业务操作。

我是平台方,第三方帮我实现功能

这个场景是,我是个平台,我提供给第三方一些接口和SSO登录授权,第三方帮我开发一些功能然后通过iframe的方式嵌入到我的页面,让用户可以使用到这个功能。

Web Storage

简介

Web Storage标准: https://www.w3.org/TR/webstorage/
这里主要从localStorage角度去描述。

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage
只读的localStorage 属性允许你访问一个Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除 。

应注意,无论数据存储在 localStorage 还是 sessionStorage ,它们都特定于页面的协议。

另外,localStorage 中的键值对总是以字符串的形式存储。 (需要注意, 和js对象相比, 键值对总是以字符串的形式存储意味着数值类型会自动转化为字符串类型).

使用场景

  • 需要持久化一定数据的时候
  • 需要跨页面处理数据的时候

API

localStorage的API比较简单,只有增删查,改则是通新增覆盖原有的key,这里不做赘述,直接看文档。
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage

storage事件

当前页面使用的storage被其他页面修改时会触发StorageEvent事件

这里在理解概念时着重注意的是,“当前页面使用”,“其他页面修改”,“才会触发当前页面的StorageEvent”。
言外之意是,当前页面使用当前页面修改不会触发当前页面的StorageEvent。
事件返回的event对象详情看文档:
https://developer.mozilla.org/zh-CN/docs/Web/API/StorageEvent

跨页面也是依赖该事件

存储大小

LocalStorage 在大部分浏览器的存储大小为5MB
如果不确定可以通过工具(或者自己写代码)去探测
https://web.dev/storage-for-the-web/

兼容性

Web SQL

Beware. This specification is no longer in active maintenance and the Web Applications Working Group does not intend to maintain it further.
This document was on the W3C Recommendation track but specification work has stopped. The specification reached an impasse: all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path.

从上面的信息来看,简单说就是不建议使用该技术,请继续阅读IndexedDB或Web Storage的部分

IndexedDB

简介

http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

IndexedDB 具有以下特点。

(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以”键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

(3)支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

(4)同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

(5)储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。

(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

使用场景

需要稳定的大存储的时候可以选用该方案处理。
另外indexedDB的操作主要是异步的,避免阻塞应用程序,如果需要同步操作可以另行查看资料。

API

AP简单来说主要是创建数据库和增删改查等操作。
这里的数据库还有一个版本的概念,并且在更新版本时(大于数据库的版本时)会触发upgradeneeded事件
你可以通过这个事件来监听异步操作的完成,来处理后续逻辑。
其他API可以直接看文档,这里就不直接搬运了。

存储大小

这里每个浏览器的策略各有不同,总结来看具备以下共通特征

  • 根据硬盘大小的百分比给浏览器的存储分配空间
  • 超过目标存储大小会触发存储源的回收机制,这个机制各个浏览器也各有不同
  • 都只支持同源存取
  • 所有的源共用一套总大小(描述这一点是为了说明,理论上indexedDB是有可能比localStorage的5MB还小的)

关于存储的限制可以看一下资料:

兼容性

https://caniuse.com/indexeddb

其他