ConardLi

ConardLi 查看完整档案

北京编辑  |  填写毕业院校字节跳动  |  前端开发工程师 编辑 github.com/ConardLi 编辑
编辑

Reading makes a full man, conference a ready man, and writing an exact man.

个人动态

ConardLi 关注了用户 · 2020-11-05

独钓寒江雪 @king_hcj

🦅 2021/1/11 孤篷 更名 独钓寒江雪
🐦 思否2019年度有奖征文 文采三剑客
👑 Nothing is given, Everything is earned!

关注 190

ConardLi 发布了文章 · 2020-10-09

Facebook 新一代 React 状态管理库 Recoil

React Europe 2020 Conference 上, Facebook 软件工程师 Dave McCabe 介绍了一个新的状态管理库 Recoil

Recoil 现在还处于实验阶段,现在已经在 Facebook 一些内部产品中用于生产环境。毕竟是官方推出的状态管理框架,之前没时间仔细研究,借着国庆期间看了看,给大家分享一下。

State 和 Context 的问题

假设我们有下面一个场景:有 ListCanvas 两个组件,List 中一个节点更新后,Canvas 中的节点也对应更新。

最常规则做法是将一个 state 通过父组件分发给 ListCanvas 两个组件,显然这样的话每次 state 改变后 所有节点都会全量更新。

当然,我们还可以使用 Context API,我们将节点的状态存在一个 Context 内,只要 Provider 中的 props 发生改变, Provider 的所有后代使用者都会重新渲染。

为了避免全量渲染的问题,我们可以把每个子节点存储在单独的 Context 中,这样每多一个节点就要增加一层 Provider

但是,如果子节点是动态增加的呢?我们还需要去动态增加 Provider ,这会让整个树再次重新渲染,显然也是不符合预期的。

引入 Recoil

Recoil 本身就是为了解决 React 全局数据流管理的问题,采用分散管理原子状态的设计模式。

Recoil 提出了一个新的状态管理单位 Atom,它是可更新和可订阅的,当一个 Atom 被更新时,每个被订阅的组件都会用新的值来重新渲染。如果从多个组件中使用同一个 Atom ,所有这些组件都会共享它们的状态。

你可以把 Atom 想象为为一组 state 的集合,改变一个 Atom 只会渲染特定的子组件,并不会让整个父组件重新渲染。

用 Redux 或 Mobx 不可以吗?

因为 React 本身提供的 state 状态在跨组件状态共享上非常苦难,所以我们在开发时一般借助一些其他的库如 Redux、Mobx 来帮助我们管理状态。这些库目前正被广泛使用,我们也并没有遇到什么大问题,那么 Facebook 为什么还要推出一款新的状态管理框架呢?

使用 Redux、Mobx 当然可以,并没有什么问题,主要原因是它们本身并不是 React 库,我们是借助这些库的能力来实现状态管理。像 Redux 它本身虽然提供了强大的状态管理能力,但是使用的成本非常高,你还需要编写大量冗长的代码,另外像异步处理或缓存计算也不是这些库本身的能力,甚至需要借助其他的外部库。

并且,它们并不能访问 React 内部的调度程序,而 Recoil 在后台使用 React 本身的状态,在未来还能提供并发模式这样的能力。

基础使用

初始化

使用 recoil 状态的组件需要使用 RecoilRoot 包裹起来:

import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
  useSetRecoilState
} from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

定义状态

上面我们已经提到了 Atom 的概念, Atom 是一种新的状态,但是和传统的 state 不同,它可以被任何组件订阅,当一个 Atom 被更新时,每个被订阅的组件都会用新的值来重新渲染。

首先我们来定义一个 Atom

export const nameState = atom({
  key: 'nameState',
  default: 'ConardLi'
});

这种方式意味着你不需要像 Redux 那样集中定义状态,可以像 Mobx 一样将数据分散定义在任何地方。

要创建一个 Atom ,必须要提供一个 key ,其必须在 RecoilRoot 作用域中是唯一的,并且要提供一个默认值,默认值可以是一个静态值、函数甚至可以是一个异步函数。

订阅和更新状态

Recoil 采用 Hooks 方式订阅和更新状态,常用的是下面三个 API:

  • useRecoilState:类似 useState 的一个 Hook,可以取到 atom 的值以及 setter
  • useSetRecoilState:只获取 setter 函数,如果只使用了这个函数,状态变化不会导致组件重新渲染
  • useRecoilValue:只获取状态
import { nameState } from './store'
// useRecoilState
const NameInput = () => {
  const [name, setName] = useRecoilState(nameState);
  const onChange = (event) => {
   setName(event.target.value);
  };
  return <>
   <input type="text" value={name} onChange={onChange} />
   <div>Name: {name}</div>
  </>;
}

// useRecoilValue
const SomeOtherComponentWithName = () => {
  const name = useRecoilValue(nameState);
  return <div>{name}</div>;
}

// useSetRecoilState  
const SomeOtherComponentThatSetsName = () => {
  const setName = useSetRecoilState(nameState);
  return <button onClick={() => setName('Jon Doe')}>Set Name</button>;
}

派生状态

selector 表示一段派生状态,它使我们能够建立依赖于其他 atom 的状态。它有一个强制性的 get 函数,其作用与 reduxreselectMobX@computed 类似。

const lengthState = selector({
  key: 'lengthState', 
  get: ({get}) => {
    const text = get(nameState);
    return text.length;
  },
});

function NameLength() {
  const length = useRecoilValue(charLengthState);
  return <>Name Length: {length}</>;
}
selector 是一个纯函数:对于给定的一组输入,它们应始终产生相同的结果(至少在应用程序的生命周期内)。这一点很重要,因为选择器可能会执行一次或多次,可能会重新启动并可能会被缓存。

异步状态

Recoil 提供了通过数据流图将状态和派生状态映射到 React 组件的方法。真正强大的功能是图中的函数也可以是异步的。这使得我们可以在异步 React 组件渲染函数中轻松使用异步函数。使用 Recoil ,你可以在选择器的数据流图中无缝地混合同步和异步功能。只需从选择器 get 回调中返回 Promise ,而不是返回值本身。

例如下面的例子,如果用户名存储在我们需要查询的某个数据库中,那么我们要做的就是返回一个 Promise 或使用一个 async 函数。如果 userID 发生更改,就会自动重新执行新查询。结果会被缓存,所以查询将仅对每个唯一输入执行一次(所以一定要保证 selector 纯函数的特性,否则缓存的结果将会和最新的值不一致)。

const userNameQuery = selector({
  key: 'userName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(userNameQuery);
  return <div>{userName}</div>;
}

Recoil 推荐使用 SuspenseSuspense 将会捕获所有异步状态,另外配合 ErrorBoundary 来进行错误捕获:

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

总结

Recoil 推崇的是分散式的状态管理,这个模式很类似于 Mobx,使用起来也感觉有点像 observable + computed 的模式,但是其 API 以及核心思想设计的又没有 Mobx 一样简洁易懂,反而有点复杂,对于新手上手起来会有一定成本。

在使用方式上完全拥抱了函数式的 Hooks 使用方式,并没有提供 Componnent 的使用方式,目前使用原生的 Hooks API 我们也能实现状态管理,我们也可以使用 useMemo 创造出派生状态,RecoiluseRecoilState 以及 selector 也比较像是对 useContext、useMemo 的封装。

但是毕竟是 Facebook 官方推出的状态管理框架,其主打的是高性能以及可以利用 React 内部的调度机制,包括其承诺即将会支持的并发模式,这一点还是非常值得期待的。

另外,其本身的分散管理原子状态的模式、读写分离、按需渲染、派生缓存等思想还是非常值得一学的。

最后

文章中如有错误,欢迎在评论区指正;如果文章对你有帮助,欢迎点赞、评论、分享、希望能帮到更多人。

本文首发于公众号《code秘密花园》欢迎大家关注,原文:Facebook 新一代 React 状态管理库 Recoil

查看原文

赞 14 收藏 6 评论 0

ConardLi 关注了用户 · 2020-08-12

执鸢者 @zhizhe_5c6eb6478bd93

摸摸头,编程使我快乐。
公众号:执鸢者
欢迎关注公众号,并加群,一起加油

关注 64

ConardLi 关注了用户 · 2020-08-12

JowayYoung @yangzw

谢谢曾经努力的自己,欢迎关注公众号『 IQ前端 』,一个专注于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔

关注 790

ConardLi 发布了文章 · 2020-05-26

解读新一代 Web 性能体验和质量指标

衡量一个 Web 页面的体验和质量一直有非常多的工具和指标 ... 每次我们去关注这些指标的时候都会非常痛苦,因为这些指标真的是又多又难理解,测量这些指标的工具也非常多。

当看到最近发布的 Chrome 83 中又增加了几个性能指标的时候我头都大了...

然而不要着急,这些指标就是为了聚焦关注度和降低理解成本的,下面我们就来具体看一下,新增加的 Core Web Vitals 到底是什么东西?

如何衡量用户体验质量?

优化用户体验的质量一直都是是每个 Web 站点长期成功的关键,衡量用户体验的质量有很多方面。虽然用户体验的某些方面是需要基于特定于站点和上下文的,但是所有站点仍然有一组共同的指标——Core Web Vitals,这些指标包括加载体验、交互性和页面内容的视觉稳定性,他们构成了 2020 年核心 Web 健康指标的基础。

多年来,Google 提供了很多工具:(Lighthouse, Chrome DevTools, PageSpeed Insights, Search Console's Speed Report) 来衡量和报告性能。一些开发人员是使用这些工具的专家,而大部分其他人则发现大量的工具和衡量标准都很难学习和使用。

网站开发者不应该为了理解他们交付给用户的体验的质量指标而成为性能专家。Web Vitals 计划的目的就是简化场景,降低学习成本,并帮助站点关注最重要的指标,即 Core Web Vitals

Core Web Vitals

Core Web Vitals 是应用于所有 Web 页面的 Web Vitals 的子集,所有的站点开发者都应该关注一下,他们将在所有谷歌提供的性能测试工具中进行显示。每个 Core Web Vitals 代表用户体验的一个不同方面,在该领域是可衡量的,并反映了以用户为中心的关键结果的真实体验。

网页核心的性能指标应该是随着时间的推移而不断演变的。当前 2020 年主要关注用户体验的三个方面——加载、交互性和视觉稳定性:

  • Largest Contentful Paint (LCP): 衡量加载体验:为了提供良好的用户体验, LCP 应该在页面首次开始加载后的 2.5 秒内发生。
  • First Input Delay (FID): 衡量可交互性,为了提供良好的用户体验,页面的 FID 应当小于 100毫秒。
  • Cumulative Layout Shift (CLS):衡量视觉稳定性,为了提供良好的用户体验,页面的CLS应保持小于 0.1。

下面我们来详细介绍这三种性能指标:

LCP

加载体验的衡量

衡量 Web 页主要内容的加载速度是众多开发者一直在关注的一个点,而且可衡量的指标非常多。

比如最早的 loadDOMContentLoaded 事件,用这两个事件来衡量页面加载速度是非常糟糕的,因为它们不一定与用户在屏幕上看到的内容相对应。

以用户为中心的更新性能指标(例如First Contentful Paint(FCP))它只能捕捉加载体验的最开始。如果页面最开始显示的是一个 loading 动画,那这个指标就很难关注了。

后来,业界开始建议使用比如 First Meaningful Paint (FMP)Speed Index (SI)(都可以在 Lighthouse 中获取)等性能指标来帮助捕获初次渲染后的更多加载体验,但是这些指标非常复杂,难以解释,而且误报率也比较高。

什么是 LCP

Largest Contentful Paint (LCP) 用于衡量标准报告视口内可见的最大内容元素的渲染时间。为了提供良好的用户体验,网站应努力在开始加载页面的前 2.5 秒内进行 最大内容渲染

相比 FCP ,这个指标就非常有价值了,因为这个值是根据页面加载渲染不断变化的,如果页面有一个 lodaing 动画,然后才渲染出具体内容,那么这个指标计算出来的就是具体内容的加载速度,而非 lodaing 动画的加载速度。

LCP 考虑哪些元素

LCP 目前并不会计算所有元素,因为这样会使这个指标变得非常复杂,它现在只关注下面的元素:

  • <img> 元素
  • <image>元素内的<svg>元素
  • <video> 元素
  • 通过 url() 函数加载背景图片的元素
  • 包含文本节点或其他内联文本元素子级的块级元素。
为了在开始时保持简单,将元素限制到这个有限的集合是有意的。随着研究的深入,将来可能会添加更多的元素。

如何计算 LCP ?

页面上最大的元素即绘制面积最大的元素,所谓绘制面积可以理解为每个元素在屏幕上的 “占地面积”,如果元素延伸到屏幕外,或者元素被裁切了一部分,被裁切的部分不算入在内,只有真正显示在屏幕里的才算数。

图片元素的面积计算方式稍微有点不同,因为可以通过 CSS 将图片扩大或缩小显示,也就是说,图片有两个面积:“渲染面积”与“真实面积”。在 LCP 的计算中,图片的绘制面积将获取较小的数值。例如:当“渲染面积”小于“真实面积”时,“绘制面积”为“渲染面积”,反之亦然。

页面在加载过程中,是线性的,元素是一个一个渲染到屏幕上的,而不是一瞬间全渲染到屏幕上,所以“渲染面积”最大的元素随时在发生变化。

如果元素被删除,LCP算法将不再考虑该元素,如果被删除的元素刚好是 “绘制面积” 最大的元素,则使用新的 “绘制面积” 最大的元素创建一个新的性能条目。

该过程将持续到用户第一次滚动页面或第一次用户输入(鼠标点击,键盘按键等),也就是说,一旦用户与页面开始产生交互,则停止报告新的性能指标。

在以上两个时间轴中,最大的元素随内容加载而变化。在第一个示例中,新内容被添加到 DOM 中,并且更改了最大的元素。在第二个示例中,布局发生更改,以前最大的内容从视口中删除。通常情况下,延迟加载的内容要大于页面上已存在的内容。

改善 LCP

LCP较差的最常见原因是:

  • 服务器响应时间慢
  • 阻断渲染的 JavascriptCSS
  • 资源加载时间慢
  • 客户端渲染

所以我们从上面的角度去考虑改善 LCP

优化服务器

这个很好理解,浏览器从服务器接收内容所需的时间越长,则在屏幕上呈现任何内容所花费的时间就越长。更快的服务器响应时间可以直接改善包括 LCP 在内的所有页面加载指标。

衡量服务器相应时间有一个专门的指标:首字节相应时间(TTFB)是最初的网络请求被发起到从服务器接收到第一个字节这段时间,它包含了 TCP 连接时间,发送 HTTP 请求时间和获得响应消息第一个字节的时间。你可以尝试在下面几个方便优化 TTFB

  • 缓存 HTML 离线页面,缓存页面资源,减少浏览器对资源的请求。
  • 尽量减小资源阻断渲染:CSSJavaScript 压缩、合并、级联、内联等等
  • 对图片进行优化。转化图片的格式为 JPG 或者 WEBP 等等的格式,降低图片的大小,以加快请求的速度。
  • HTML 重写、压缩空格、去除注释等。减少 HTML 大小,加快速度。
  • 使用 preconnect 尽快与服务器建立链接、使用 dns-prefetch 尽快进行 DNS 查找。
  • 使用 CDN 加快请求速度

优化阻断渲染的资源

JavaScriptCSS 都是会阻断页面渲染的资源,需要尽可能的对 CSSJavaScript 文件进行压缩、延迟加载首屏无需使用的 JavaScript、内联关键的 CSS 等来减小阻断时间。

优化资源加载时间

刚才我们上面提到的这些资源,如果在首屏进行渲染,则加载这些元素所花费的时间将直接影响 LCP

  • <img> 元素
  • <image>元素内的<svg>元素
  • <video> 元素
  • 通过 url() 函数加载背景图片的元素
  • 包含文本节点或其他内联文本元素子级的块级元素。

你可以使用下面的手段进行优化:

  • 对图片进行优化。转化图片的格式为 JPG 或者 WEBP 等等的格式,降低图片的大小。
  • 对重要的资源进行预加载,比如为 style 标签添加 rel="preload" 属性
  • 使用 GzipBrotli 压缩页面资源,降低传输时间
  • 使用 service worker 缓存资源

服务端渲染

使用服务端渲染可以确保首先在服务器上呈现页面内容,可以有效改善 LCP,但是相比客户端渲染的缺点是会加大页面从而影响 TTFB、服务端渲染需要等待所有 js 执行完毕后才能相应用户输入,这会使交互体验变差。

FID

第一印象

我们都知道留下一个好的第一印象是多么重要。在网络上,一个好的第一印象可以决定一个人是不是可以成为一个网站的忠实的用户,或者是离开以后再也不会回来。问题是,什么能给人留下好印象,你如何衡量你可能给用户留下什么样的印象?

在网络上,第一印象可以有很多种不同的形式——我们对网站的设计和视觉吸引力有第一印象,对其速度和响应能力也有第一印象。

开发者们使用 First Contentful Paint(FCP) 可以衡量对网站加载速度对第一印象 。但是,网站可以在屏幕上绘制像素的速度只是一部分,同样重要的是用户尝试与这些像素进行交互时你的网站的响应速度!

什么是 FID

FID( First Input Delay) 即记录用户和页面进行首次交互操作所花费的时间 。FID 指标影响用户对页面交互性和响应性的第一印象。 为了提供良好的用户体验,站点应努力使首次输入延迟小于 100 毫秒。

FID 发生在 FCPTTI 之间,因为这个阶段虽然页面已经显示出部分内容,但尚不具备完全的可交互性。这个阶段用户和页面交互,往往会有较大延迟。

如上图所示,浏览器接收到用户输入操作时,主线程正在忙于执行一个耗时比较长的任务,只有当这个任务执行完成后,浏览器才能响应用户的输入操作。它必须等待的时间就此页面上该用户的 FID 值。

例如,以下所有 HTML 元素都需要在响应用户交互之前等待主线程上正在进行的任务完成:

  • 文本输入框,复选框和单选按钮(<input>,<textarea>
  • 选择下拉菜单(<select>
  • 链接(<a>

如何提高 FID

以下几个方面是提高 FID 的重要指标:

减少 JavaScript 执行时间

同上面改善 LCP 的方法:

  • 缩小并压缩 JavaScript 文件
  • 延迟加载首屏不需要的 JavaScript
  • 尽量减少未使用的 polyfill

分解耗时任务

上面提到一个较长的耗时任务是影响 FID 的重要指标,任何阻塞主线程 50 毫秒或更长时间的代码段都可以称为“长任务”,我们可以将长的耗时任务拆分为较小的异步任务。

使用 Web Worker

主线程阻塞是输入延迟的主要原因之一。Web Workers 可以让你在与主执行线程分离的后台线程上运行 JavaScript,这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞。将非 UI 操作移至单独的工作线程可以减少主线程的阻塞时间,从而改善 FID

CLS

视觉稳定性

您是否曾经在访问一个 Web 页面时发生下面的情况?在阅读文章的同时文字突然移动了、你突然找不到你阅读的位置了、点按钮的时候按钮被移动到了其他地方,导致你点了其他东西?

页面内容的意外移动通常是由于异步加载资源或将 DOM 元素动态添加到现有内容上方的页面而发生的。罪魁祸首可能是尺寸未知的图像或视频,渲染后比其后备更大或更小的字体,或者是动态调整自身大小的第三方广告或小部件。

Cumulative Layout Shift (CLS) 可通过测量实际用户发生的频率来帮助您解决此问题。

什么是 CLS?

CLS 会测量在页面的整个生命周期中发生的每个意外的样式移动的所有单独布局更改得分的总和。布局的移动可能发生在可见元素从一帧到下一帧改变位置的任何时候。为了提供良好的用户体验,网站应努力使 CLS 分数小于 0.1

如何计算 CLS?

布局偏移分值

为了计算布局的偏移值,浏览器会查看两个渲染帧之间的视口大小和视口中不稳定元素的移动。布局偏移分是该移动的两个指标的乘积:影响分数和距离分数。

layout shift score = impact fraction * distance fraction

影响分数

前一帧和当前帧的所有不稳定元素的可见区域的并集(占视口总面积的一部分)是当前帧的影响分数。

在上图中,有一个元素在一帧中占据了视口的一半。然后,在下一帧中,元素下移视口高度的25%。红色的虚线矩形表示两个帧中元素的可见区域的并集,在这种情况下,其为总视口的75%,因此其影响分数为 0.75

距离分数

布局偏移值方程的另一部分测量不稳定元素相对于视口移动的距离。距离分数是任何不稳定元素在框架中移动的最大距离(水平或垂直)除以视口的最大尺寸(宽度或高度,以较大的为准)。

在上面的例子中,最大的视口尺寸是高度,并且不稳定元素移动了视口高度的25%,这使得距离分数为0.25

因此,在此示例中,影响分数为0.75,距离分数为0.25,因此版式位移分数为0.75 * 0.25 = 0.1875

如何改善 CLS?

不要使用无尺寸元素

图像和视频等元素上始终需要包括 widthheight 尺寸属性,现代浏览器会根据图像的 widthheight 属性设置图像的默认长宽比,知道纵横比后,浏览器就可以为元素计算和保留足够的空间。

或者,使用 aspect-ratio 也可以提前指定宽高比:

img {
    aspect-ratio: attr(width) / attr(height);
}

那响应式的图片呢?可以使用 srcset 定义图像,使浏览器可以在图像之间进行选择,以及每个图像的大小。

<img 
    width="1000" 
    height="1000"
    data-original="puppy-1000.jpg"
    srcset="puppy-1000.jpg 1000w,
            puppy-2000.jpg 2000w,
            puppy-3000.jpg 3000w"
    alt="ConardLi"
/>
  • 永远不要在现有内容之上插入内容,除非是响应用户交互。这确保了预期的布局变化。
  • 宁可转换动画,也不要转换触发布局变化的属性的动画。以一种提供从一个状态到另一个状态的上下文和连续性的方式动画转换。

提前给广告位预留空间

很多页面广告都是动态插入的,所以一定要提前为广告位预留一定空间。

警惕字体变化

字体通常是大文件,需要一段时间才能加载,一些浏览器直到下载完字体后才呈现文本

font-display: swap 告诉浏览器默认使用系统字体进行渲染,当自定义字体下载完成之后再进行替换。

@font-face {
  font-family: 'Pacifico';
  font-style: normal;
  font-weight: 400;
  src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/xxx.woff2) format('woff2');
  font-display: swap;
}

另外,你可以使用 <link rel="preload"> 更早的加载字体文件。

获取 Core Web Vitals

Google 认为,Core Web Vitals 对于所有网络体验都至关重要。因此,它致力于在其工具中显示这些指标,下面是现有工具中指标的支持情况:

尚未支持这些指标的工具都将在最近得到支持。

web-vitals

现在你可以使用标准的 Web APIJavaScript 中测量每个指标。 Google 提供了一个 npm 包:web-vitals,这个库提供了非常简单的 API,测量每个指标就像调用一个普通函数一样简单:

npm install web-vitals

每个测量函数都接收一个 report 回调函数作为参数,回调函数将在测量完成后触发,另外,对于像 LCPCLS 这样的指标是不断变化的,所以它们的回调函数可能会多次触发,如果你想获取在这期间获取每次变化的数值,你可以指定第二个参数 reportAllChanges,否则回调函数只有在最终测量完成后触发一次。

import {getCLS, getFID, getLCP} from 'web-vitals';

getCLS(console.log, true);
getFID(console.log); // Does not take a `reportAllChanges` param.
getLCP(console.log, true);

这些变化的指标如果触发多次的话可能会多次发送到你的服务器,所以回调函数中提供了下面三个参数:

  • name:指标名称
  • id:本地分析的id
  • delta:当前值和上次获取值的差值

因此你只需要每次上报 delta (当前值和上次获取值的差值),而不需要报告新值。然后在服务器可以通过计算所有id对应值的和来获取最终结果。

import {getCLS, getFID, getLCP} from 'web-vitals';

function logDelta({name, id, delta}) {
  console.log(`${name} matching ID ${id} changed by ${delta}`);
}

getCLS(logDelta, true);
getFID(logDelta);
getLCP(logDelta, true);

你可以很好的结合 Google Analytics 来记录你的上报指标:

import {getCLS, getFID, getLCP} from 'web-vitals';

function sendToGoogleAnalytics({name, delta, id}) {
  ga('send', 'event', {
    eventCategory: 'Web Vitals',
    eventAction: name,
    eventValue: Math.round(name === 'CLS' ? delta * 1000 : delta),
    eventLabel: id,
    nonInteraction: true,
  });
}

getCLS(sendToGoogleAnalytics);
getFID(sendToGoogleAnalytics);
getLCP(sendToGoogleAnalytics);

使用 Chrome 插件

如果你不想在程序中计算,还可以使用 Chrome 插件这样更方便的方式,Google 也提供了一个新的插件 web-vitals-extension 来帮助我们获取这些指标:

这个插件非常简洁,只有 CLS、FID、LCP 这三个核心指标,这样可以大大聚焦我们的关注度,降低理解成本。

徽章的颜色可以告诉你页面有没有通过默认设定的阈值:

  • 灰色:插件不支持或者被禁用
  • 绿色:通过所有指标
  • 红色:一个或多个指标不达标

参考

小结

最后,想在浏览器中使用上面的工具和指标?快升级一下 Chrome 83 版本吧~,更多 Chrome 83 的更新可以点击 Chrome 83 发布,支持直接读写本地文件!新的跨域策略! 查看。

文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注。

想阅读更多优质文章、可关注我的github博客,你的star✨、点赞和关注是我持续创作的动力!

推荐关注我的微信公众号【code秘密花园】,每天推送高质量文章,我们一起交流成长。

查看原文

赞 38 收藏 16 评论 0

ConardLi 赞了文章 · 2020-05-12

拼多多、酷家乐面试总结

前言

离职原因看我这篇文章吧:离开蘑菇街后,我最近的一些想法,然后不得不去找工作恰饭呀。

我目前面了五家公司:滴滴、蚂蚁、拼多多、酷家乐、字节跳动,拼多多和酷家乐基本已拿到 offer,蚂蚁二面完了,滴滴和字节即将三面,我先把我已经面过的面经先总结出来,其他的不管过没过,这周内我都会总结出来,希望能给到正在找工作同学的帮助。

在文章里我不仅会列出面试题,还会给到一些答题建议,个人能力有限,也不能保证我回答都正确,如果有错误,希望能纠正我。

酷家乐

酷家乐是我最早面的一家公司,还没被裁的那周二刚好面完,结果周五就被裁了,可能这就是冥冥之中吧。

面试的部门是工具组,是酷家乐最核心的部门,四面面试官跟我说我面的组是工具团队中的最核心组,会涉及到一些图像相关的技术,比如 Tree.js、WebGL等,所以这个组其实也挺好的,感觉能学到不少技术。

一面(电话面)

  • 你在项目如何优化的

    我在简历里面写到了性能优化相关的,所以这个问题。
  • 你做的项目有什么值得说的

    基本上就是考察项目的亮点,可以说一些项目难点是如何解决的,或者介绍一些项目中用到的牛逼的技术。
  • Ts 有什么优势

    讲道理所有现在在网上能查到的优势都是得益于静态语言的优势。
  • type 和 interface 的区别

    这是一个高频题,如果考察 TS,这应该是最容易考察的,网上也都能查到相关的资料,但是很可能忽略一个点:type 只是一个类型别名,并不会产生类型。所以其实 type 和 interface 其实不是同一个概念,其实他们俩不应该用来比较的,只是有时候用起来看着类似。
  • React 事件机制

    我觉得需要答的点:

    1. React 为什么要用合成事件
    2. 事件机制:注册和分发的过程。这里面至少要说出事件注册都是在元素的最顶层 document 节点上。

    参考资料:一文吃透 React 事件机制原理

  • 聊聊 React 的 diff

    聊 diff 建议先看看我之前写过的一篇关于虚拟 DOM 的文章:从 React 历史的长河里聊虚拟DOM及其价值,有助于理解 diff 的意义。

    diff 的细节可以看我之前写的:详解 React 16 的 Diff 策略

  • React 优化

    可以看之前我写的 React 函数式组件性能优化指南,对于类组件也有对应的 API。
  • 怎么理解闭包

    基础中的基础,虽然社招考得不多,但是如果连闭包都理解不了,应该会减分不少。闭包由于在规范里没有定义,所以很多人下的定义不一样,理解的角度也不同,但是自己要有一套正确的理解方式,如果按照我的理解 JavaScript 里面所有的函数都是闭包,因为有全局环境,所有的函数都可以访问全局变量。
  • 节流怎么实现的

    防抖和节流的代码还是需要会手写的,这也是一个闭包的例子,
  • 原型,class B 继承 class A 翻译成 es5 应该是什么样子

    说实话,我觉得这道题其实蛮有水平的,即考察了如何写出一个好的继承方式,也对 new 过程进行了考察,还对考察了对 Class 的理解。

    注意的点:class 是有重载功能的,怎么在子类的构造函数里面调用 super

二面(现场面)

  • react 的基本原理

    UI = f(state) ,虚拟 DOM、diff 策略、setState
  • react 如何做性能优化

    这个题也是高频,见一面回答
  • redux 的重点概念

    store、reduce、action、actionCreater、dispatch
  • 聊一聊 React 的生命周期

    尽量把 React 15 和 16 的进行对比,然后 16 为什么要准备废除那几个生命周期,以及新增的生命周期可以进行替代。

    这个图好好的理解一下

    react 生命周期

  • 聊一聊 hooks 怎么处理生命周期

    讲道理函数式组件是没有生命周期的,但是如何去模拟类组件的生命周期的作用,都是在 useEffect 里面进行操作的,因为生命周期里面所做的基本都是副作用,放到 useEffect 里是最合适的,专门用来处理副作用。
  • 笔试题一
const a = { b : 3}

function foo(obj) {
  obj.b = 5

  return obj
}

const aa = foo(a)

console.log(a.b)

console.log(aa.b)
  • 笔试题二:
function Ofo() {}

function Bick() {
    this.name = 'mybick'
}

var myBick = new Ofo()

Ofo.prototype = new Bick()

var youbick = new Bick()

console.log(myBick.name)

console.log(youbick.name)
  • 笔试题三:考察盒子模型和 box-sizing 属性,判断元素的尺寸和颜色。
  • 实现一个 fill 函数,不能用循环。

    考察递归
  • 用 ES5 实现私有变量

    考察闭包的使用

三面(现场面)

  • 简历里面的性能优化是如何做的

    减少请求频率、图片压缩、React.memoReact.useMemo
  • class 组件里面如何做性能优化(因为前面我说了用 React.memo 做了性能优化)

    shouldComponentUpdate(简称 SCU)。SCU 跟 immutable 强相关,一定要好好理解 react 的 immutable,否则很可能理解不了为什么不能直接去修改 state,然后再去 setState,以及 redux 的 reducer 要返回一个新的对象。
  • 实现一个 Typescript 里的 Pick

    type Pick<T, K extends keyof T> = { [P in K]: T[P] }
  • 手写 Promise.all
  • 手写并发只能 10 个
  • 算法题,怎么判断单链表相交。

    很多种方法,我当时说的是最后一个节点如果相同,那么就代表相交。
  • 算法题,怎么找到第一个相交的节点。

    同时遍历两个链表到尾部,同时记录两个链表的长度。若两个链表最后的一个节点相同,则两个链表相交。有两个链表的长度后,我们就可以知道哪个链表长,设较长的链表长度为len1,短的链表长度为len2。则先让较长的链表向后移动(len1-len2)个长度。然后开始从当前位置同时遍历两个链表,当遍历到的链表的节点相同时,则这个节点就是第一个相交的节点。

    这是我刚想到的一种方式,不过当时面试的时候我记得好像更简单,但是想不起来了。

四面(现场面)

  • 你觉得你在公司人缘怎么样
  • 你觉得你为你们小组做了什么贡献
  • 为什么要离职
  • 除了我们公司还投了其他公司吗
  • 薪资和层级有什么要求
  • 如何垂直水平居中
  • 你看过开源库的源码吗?
  • 那你聊聊 React 的源码,把你记得起的讲一讲

    我看过 React 的一部分源码的,所以关于 React 源码更新部分的东西,应该基本都能说个大概。
  • FiberNode 有哪些属性
  • stadeNode 有什么用?
  • 还有一些技术问题想不起来了

小结

酷家乐面试体验还是不错的,我是一面电话面,面完觉得 OK 之后就叫到公司去现场面试,6 点半下班了就骑车去了酷家乐,七点开始面试,一口气面完了三面,饭都没来得及吃,不过面试官很好给我倒了水。

感觉面试官对我的项目似乎不太敢兴趣,很少问项目的东西,可能由于他们是工具组,连我简历里面组件库相关的也没面,考察基础的比较多,基础考察得比较全面。

但是由于考虑到我之前做的项目复杂性不够,以及工作年限的问题,给到的评级不高,导致薪资也不是特别高,但是已经是这个评级的顶峰了,要是没有更好的 offer 酷家乐还是非常值得去的,特别是工具组。

拼多多

一面

  • react 16 生命周期有什么改变

    componentWillMountcomponentWillReceivePropscomponentWillUpdate 准备废除,新增了 static getDerivedStateFromPropsgetSnapshotBeforeUpdate

    我还详细的介绍了为什么要用 getDerivedStateFromProps 来代替即将废除的三个生命周期,主要是16 版本 render 之前的生命周期可能会被多次执行,具体的可看我的这篇文章:Deep In React之浅谈 React Fiber 架构(一)

  • 详细的介绍一下 getDerivedStateFromProps
  • 你在项目中如何做性能优化的
  • flex: 0 1 auto; 是什么意思?

    flex 这个属性常考题,好好把阮老师的那篇 flex 语法篇看完 flex 的面试题基本没问题。
  • less 的 & 代表什么?
  • 算法题:求最大公共前缀,如 ['aaafsd', 'aawwewer', 'aaddfff'] => 'aa'

    不能调试,全靠编程素养,只能面试官才能运行。
  • interface 和 type 的区别

    又考了,上面有回答
  • 有用状态管理吗?

    我常用的是 redux 和 dva,然后再聊了聊区别已经 redux 的理念
  • 有用 ssr 吗?

    没用过
  • node 熟悉吗?

    写得少

二面

  • class 组件与函数式组件的区别

    生命周期、设计理念,感觉这道题比较开发,可以看看 dan 的这篇:函数式组件与类组件有何不同?
  • css 优先级

    important > 内联 > ID选择器 > 类选择器 > 标签选择器
  • 避免 css 全局污染。

    我常用的 css modules
  • css modules 的原理

    生成唯一的类名
  • 有一个a标签,如何动态的决定他的样式。

    我说了先写几个 css,然后外部传一个前缀的方式。面试官问了都要这样吗?我说可以通过 context 的方式,就不需要每个组件都传了。
  • import 和 require 导入的区别

    高频题,考察 ES6 模块和 CommonJS 模块 的区别。关键点:1. 前者是值的引用,后者是值的拷贝。 2.前者编译时输出接口,后者运行时加载。

    推荐文章:前端模块化:CommonJS,AMD,CMD,ES6

  • require 有什么性能问题

    好好想想上一个题的区别就能想到了
  • 组件库如何做按需加载

    我常用的是babel-plugin-import
  • webpack 如何实现动态加载

    讲道理 webpack 动态加载就两种方式:import()require.ensure,不过他们实现原理是相同的。

    我觉得这道题的重点在于动态的创建 script 标签,以及通过 jsonp 去请求 chunk,推荐的文章是:webpack是如何实现动态导入的

  • react 里有动态加载的 api 吗?

    React.lazy
  • React.lazy 的原理是啥?
  • webpack 能动态加载 require 引入的模块吗?

    应该是不能的,前面说了,webpack 支持动态导入基本上只能用 import()require.ensure
  • require 引入的模块 webpack 能做 Tree Shaking 吗?

    不能,Tree Shaking 需要静态分析,只有 ES6 的模块才支持。
  • 设计一个input 组件需要哪些属性。我说了 value 、defaultValue、onChange
  • value 的类型是什么?
  • onChange 怎么规定 value 的类型
  • interface 和 type 的区别
  • 写一个 promise 重试函数,可以设置时间间隔和次数。function foo(fn, interval, times) {}

    常规题,网上有参考答案的。

三面

  • 组件平台有哪些功能?

    详细的跟我讨论组件平台的设计,因为他们也想做一个组件平台。
  • 实现一个 redux

    实现 createStore 的功能,关键点发布订阅的功能,以及取消订阅的功能。
  • 用 ts 实现一个 redux

    简单的加上类型,我写的类型没有 redux 源码那么复杂,当时写得比较简单。

小节

一面的时候其实我自己感觉答得不是特别好,连 less 的语法都忘记了,当时面下来感觉要凉了,平时写 样式的时间确实太少了。

很幸运的时候还是给我过了,二面面试官我觉得面得很专业,基本都是从浅入深的考察知识的深度,我感觉答得还可以,因为我是属于那种会的就尽量深一点,暂时不用的就很少花时间,所以我目前知识的广度很有欠缺,对于 node、ssr、移动端、小程序这些方面我的能力都很薄弱,但是面试的时候如果你不熟悉,直接说不熟悉就行,他就不会面了。

我准备面试之前对我自己的要求就是,我会的尽量不会很快就被问倒,所以我重点复习了我擅长的知识。

面下来感觉拼多多也没有想象中那么难,虽然拼多多薪资算行业内高的,不过拼多多在上海,我在杭州,另外就是强制上 6 天班,我比较忌惮这点。考虑到我和女朋友本来就是异地,要是单休,而且节假日也会比正常的少,见面的机会就更少了。

反正选 offer 这种事还是尽量综合考虑吧,团队、薪资、个人生活都应该考虑进去。

最后

我是桃翁,一个爱思考的前端er,期待你的关注。

如果你最近也在找工作,欢迎找我交流,下面是我公众号。

查看原文

赞 69 收藏 49 评论 7

ConardLi 赞了回答 · 2020-04-29

解决sequelize 怎么查询七天内的数据

Model.findAll({
  where: 
    time_col: {
        [Op.lt]: new Date(),
        [Op.gt]: new Date(new Date() - 7 * 24 * 60 * 60 * 1000)
    }
});

关注 2 回答 1

ConardLi 发布了文章 · 2020-04-16

当浏览器全面禁用三方 Cookie

苹果公司前不久对 Safari 浏览器进行一次重大更新,这次更新完全禁用了第三方 Cookie,这意味着,默认情况下,各大广告商或网站将无法对你的个人隐私进行追踪。而微软和 Mozilla 等也纷纷采取了措施禁用第三方 Cookie,但是由于这些浏览器市场份额较小,并没有给市场带来巨大的冲击。

2017 年截至 2019 年底, Google 面临的罚款总额已经超过 93 亿欧元,其中一大原因便是侵犯用户数据隐私。迫于巨大压力,Google Chrome 官方团队前不久也宣布,为了提升用户隐私和安全,未来两年将完全禁用第三方 Cookie

在完全不能写入三方 Cookie 的情况下,将会对前端的数据读写方式,甚至是整个广告行业带来巨大影响。

Cookie 的意义

众所周知,HTTP 协议是无状态的协议,如果你在同一个客户端向服务器发送多次请求,服务器不会知道这些请求来自同一客户端。

这正是 HTTP 协议得以广泛应用的原因,试想一下,如果它是有状态协议,你必须要时刻与服务器建立链接,那么如果连接意外断开,整个会话就会丢失,重新连接之后一般需要从头开始;而如果是无状态协议,使得会话与连接本身独立起来,这样即使连接断开了,会话状态也不会受到严重伤害,保持会话也不需要保持连接本身。

如果 HTTP 协议只是用来访问静态文件,那不会有任何问题,但是如果你要为广大用户提供更好的服务,服务器就需要知道每个请求具体来自于哪个用户,比如你在逛淘宝的时候你只需要登录一次,当你发起一次购买请求,服务器就已经知道你登录过了,不会再让你进行登录。

所以 HTTP 协议需要占用浏览器的一小块存储,存储当前访问用户的一些 ”状态“,然后每次发起 HTTP 请求,请求中就会携带这些状态,从而让服务器知道你是谁。 Cookie 出现的的意义就是为了解决这个问题,让无状态的 HTTP 协议拥有一小块记忆。

但是, Cookie 一经出现,就成了各大广告和购物网站窥探用户隐私的利器,他们使用第三方 Cookie 不断获取你的数据,那么什么第三方 Cookie 呢?

第三方 Cookie

如果是你正常的正在逛着天猫,天猫会把你的信息写入一些 Cookie.tmall.com 这个域下,然而打开控制台你会看到,并不是所有 Cookie 都是 .tmall.com 这个域下的,里面还有很多其他域下的 Cookie ,这些所有非当前域下的 Cookie 都属于第三方 Cookie,虽然你可能从来没访问过这些域,但是他们已经悄悄的通过这些第三方 Cookie来标识你的信息,然后把你的个人信息发送过去了。

.tmall.com 这个域下的 Cookie 都属于第一方 Cookie,那么为什么还需要第三方 Cookie 呢?再打开 taobao.com,你会发现你已经不需要再登录了,因为淘宝、天猫都属于阿里旗下的产品,阿里为他们提供统一的登录服务,同时,你的登录信息也会存到这个统一登录服务的域下,所以存到这个域下的 Cookie 就成了三方 Cookie

我们再打开已经完全禁用了第三方 CookieSafari,发现只剩下 .tmall.com 这个域下的 Cookie 了。

这时你会发现即使你已经登录了天猫,再访问淘宝也还是需要登录的,你已经无法享用这样的功能了,而三方 Cookie 可不仅仅就这么点用途,在 Web 开发中,三方 Cookie 的应用非常之广,下面我们再来具体看几个应用场景:

三方Cookie的用途

前端日志打点

大多数 Web 站点都会引用一些第三方 SDK 来进行前端异常或性能监控,这些 SDK 会通过一些接口将监控到的信息上传到他们的服务器。一般它们都需要标识每个用户来方便排查问题或者统计 UV 数据,所以当你一此请求这个站点的时候,它们可能会在你的站点上 set 一个 Cookie,后续所有的日志上报请求都会带上这个 Cookie

由于一般这些第三方 SDK 都是用于监控的通用服务,它们肯定会拥有自己独立的域名,比如 log.com,它在你的域名 mysite.com 下种下的 Cookie 就属于第三方 Cookie

广告营销神器 - Facebook Pixel

在电商业务中,追踪流量、导流量、转换率、销售额这些都是商家最关心的问题。这时候你就可以使用 Facebook Pixel,简单来说 Facebook Pixel 像素就是一串 JavaScript 代码,可以追踪广告的转化量、改进受众定位、使广告花费回报最大化。

当访客进入到被设有 Facebook Pixel 的页面时,便会触发这段代码。比如,查看了商品或者加入购物车, Facebook Pixel 便会向系统发送请求来记录这些行为,系统可以利用这些收到的行为信息进一步的做追踪和优化。

举一个实际例子,我们进入一个国外的电商网站 Brava Fabrics ,你会发现已经被写入了一堆 facebook.com 下的三方 cookie

我猜测这个 fr 应该就是用来标识我身份信息的 cookie,然后点击几个页面,在 network 里面找到了几个 facebook 发送的请求,下面是其中一个:

https://www.facebook.com/tr/?id=382444918612794&ev=PageView&dl=https%3A%2F%2Fbravafabrics.com%2Fcollections%2Fa-moment-of-bliss&rl=https%3A%2F%2Fbravafabrics.com%2F&if=false&ts=1586868288778&sw=1680&sh=1050&ud[ct]=eb045d78d273107348b0300c01d29b7552d622abbc6faf81b3ec55359aa9950c&ud[country]=eb045d78d273107348b0300c01d29b7552d622abbc6faf81b3ec55359aa9950c&v=2.9.15&r=stable&ec=0&o=30&fbp=fb.1.1586867082370.951509876&it=1586868284974&coo=false&rqm=GET

查看详情你会发现,有下面几个主要的参数:

dl: https://bravafabrics.com/collections/a-moment-of-bliss
rl: https://bravafabrics.com/

这时 facebook 已经知道了我从 https://bravafabrics.com/ 来到了 https://bravafabrics.com/collections/a-moment-of-bliss 这个页面,同时,这个请求会携带 fr=09wX7ui8MrvCh2SIa..BdNoGz.f.F6R.0.0.Belanb.AWXCDx 这个 Cookie

来到 facebook,当你登录后,facebook 会把刚刚这些 Cookie 和你的 facebook Id 关联起来,然后他就可以好好的分析你的行为了:

  • 有人在你的网站上完成了购买。
  • 有人注册进行试用,或者以其他方式将自己标识为你网站上的潜在客户。
  • 有人在你网站上的购买过程中输入他们的付款信息。
  • 有人将产品添加你网站上的购物车中。
  • 有人选择特定版本的产品,例如选择某种颜色。
  • 有人发起了结账,但没有付款
  • ...

而如此强大的追踪能力,只需要你复制一段 Facebook PixelJavaScript 脚本到你的页面上就可以了。而这一切能力就建立在一个小小的 Cookie 的基础上,因为有了这个 CookieFacebook 才能将这些行为与它的账号体系进行关联。

无处不在的的 mmstat

再来看一个我们国内的例子,平时我们在国内的搜索引擎或视频网站上搜索到一些东西,然后打开购物网站就可以收到各种你兴趣的相关推荐,这已经是大众习以为常的事情了,各大购物网站、广告商,会通过第三方 Cookie 收集你的年龄、性别、浏览历史等从而判断你的兴趣喜好,然后带给你精准的信息推荐。

比如,我们在浏览百度、优酷、天猫等网站时,都能看到几个 .mmstat.com 这个域下的 Cookie

百度:

优酷:

天猫:

当你在百度、优酷、淘宝等进行一系列的操作时,.mmstat.com 已经悄悄的通过三方 Cookie 把你的个人信息运送到了他们那边。 .mmstat.com 应该就是阿里旗下的大数据营销平台阿里妈妈旗下的域名(只是个人猜测)。打开阿里妈妈首页,可以看到,其号称是更懂消费者的数据金矿,已经建立起5亿用户的身份识别体系。你的每一次搜索、每一次购买、都会让它变的更精准,下一次你就收到更精准的推荐。

当然,三方 Cookie 只是众多获取你喜好信息的一种方式,只不过这种方式更便捷,成本更低。

浏览器的策略

最近几大浏览器针对 Cookie 策略的频繁改动,意味着三方 Cookie 被全面禁用已经不远了:

Firefox、Safari —— 默认禁用

Safari 13.1Firefox 79 版本中,三方 Cookie 已经被默认禁用,但是由于这些游览器市场份额较小,并没有给市场带来巨大的冲击。因为阿里的登录信息是统一存在一个三方 Cookie 下的,淘宝刚开始的处理方式,甚至是弹个框出来,告诉用户手动开启三方 Cookie

但是这样的处理方式对于庞大的用户来讲肯定体验是极低的,解决方案可能是先将 Cookie 种在当前域下,所有就有了我们上面的测试结果,淘宝、天猫两个网站需要登录两次。

但是三方 Cookie做的事情远不止这些,等到 Chrome 全面禁用之前,一定要提前作出改变。

Chrome —— SameSite Cookie

还好由于三方 CookieGoogle 的广告业务影响较大,所以其没有立即进行禁用,而是一直陆续修改一些小的策略来对三方 Cookie 进行限制,比如 SameSite

SameSiteChrome 51 版本为浏览器的 Cookie 新增的了一个属性, SameSite 阻止浏览器将此 Cookie 与跨站点请求一起发送。其主要目标是降低跨源信息泄漏的风险。同时也在一定程度上阻止了 CSRF 攻击。

SameSite 可以避免跨站请求发送 Cookie,有以下三个属性:

Strict

Strict 是最严格的防护,将阻止浏览器在所有跨站点浏览上下文中将 Cookie 发送到目标站点,即使在遵循常规链接时也是如此。因此这种设置可以阻止所有 CSRF 攻击。然而,它的用户友好性太差,即使是普通的 GET 请求它也不允许通过。

例如,对于一个普通的站点,这意味着如果一个已经登录的用户跟踪一个发布在公司讨论论坛或电子邮件上的网站链接,这个站点将不会收到 Cookie ,用户访问该站点还需要重新登陆。

不过,具有交易业务的网站很可能不希望从外站链接到任何交易页面,因此这种场景最适合使用 strict 标志。

Lax

对于允许用户从外部链接到达本站并使用已有会话的网站站,默认的 Lax 值在安全性和可用性之间提供了合理的平衡。 Lax 属性只会在使用危险 HTTP 方法发送跨域 Cookie 的时候进行阻止,例如 POST 方式。同时,使用 JavaScript 脚本发起的请求也无法携带 Cookie

例如,一个用户在 A 站点 点击了一个 B 站点(GET请求),而假如 B 站点 使用了Samesite-cookies=Lax,那么用户可以正常登录 B 站点。相对地,如果用户在 A 站点提交了一个表单到 B站点(POST请求),那么用户的请求将被阻止,因为浏览器不允许使用 POST 方式将 Cookie 从A域发送到B域。

None

浏览器会在同站请求、跨站请求下继续发送 Cookies,不区分大小写。

策略更新

在旧版浏览器,如果 SameSite 属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于 NoneCookies 会被包含在任何请求中——包括跨站请求。

但是,在 Chrome 80+ 版本中,SameSite 的默认属性是 SameSite=Lax。换句话说,当 Cookie 没有设置 SameSite 属性时,将会视作 SameSite 属性被设置为Lax 。如果想要指定 Cookies 在同站、跨站请求都被发送,那么需要明确指定 SameSiteNone。具有 SameSite=NoneCookie 也必须标记为安全并通过 HTTPS 传送。这意味着所有使用 JavaScript 脚本收集用户信息的请求默认将不能携带三方 Cookie

然而这个改动并不会造成太大的影响,它只是给各大网站提了一个信号,因为你只需要把你想要发送的 Cookie 的属性手动设置为 none 即可:

真正可怕的是我们将无法直接指定 SameSiteNone,只能用户自己去选择,这才是真正的默认禁用。

Chrome 也宣布,将在下个版本也就是 Chrome 83 版本,在访客模式下禁用三方 Cookie,在 2022 年全面禁用三方 Cookie,到时候,即使你能指定 SameSiteNone 也没有意义,因为你已经无法写入第三方 Cookie 了。

当三方 Cookie 被全面禁止

现在,我们想象一下,当浏览器禁用了三方 Cookie,而我们又没有作出任何改变的情况下,会发生什么:

前端日志异常

可能有一天你会突然发现,你的 UV 暴涨,但是 PV 却没有什么变化,那可能是你的打点 SDK 使用的三方 Cookie 被禁用掉了。

这时这个 SDK 将无法在你的域下写入一个三方 Cookie,导致你的每次刷新页面它都会带一个新的 Cookie 回来,后端接受到的信号就是这些都是不同用户的请求,所以都会计入 UV。同时你在排查问题时,你也无法将用户的行为串联起来,导致排查非常困难。

智能广告推荐消失

上面我们提到,广告服务通过你的年龄、性别、行为来推断你的喜好,从而推送给你精准的广告,使用了三方 Cookie 来进行信息追踪的广告主将无法获得你的这些喜好,从而无法推荐给你感兴趣的广告。

这时,广告主只能在你当时的访问环境进行预定义广告,比如你正在访问宠物网站,就给你推荐宠物用品等等。

同时,可能之前广告主还会通过 Cookie 判断你阅读某个广告的次数,一旦你阅读同一个广告多次但是没有发生转化,其就会停止向你推送该广告。或者你已经购买过了这个商品,那你也不会再看到这个广告了。如果没有了频率控制,那么你可能要连续很多天盯着一个你永远也不会去点的广告,或者你会持续看到一个你已经购买过的产品广告。

无法追踪转化率

当你查看一则广告时,该广告会在你的浏览器中放置一个 Cookie,表示你已经看到它。如果随后你进入转化阶段(购买、下载等),广告主们需要能追踪每一个他们投放到你网站上的转化率,这样他们才能计算投放的效果,从而作出优化策略,如果你无法再追踪广告转化率了,那么也很难再进行投放了。

当然,以上只是建立在你没有进行任何改变的基础上,距离全面禁用三方 Cookie 还有一年多的时间,这应该是一个足够的时间让你及时作出应对。

是好是坏

虽然,这对你带来的可能是糟糕的广告体验,但是全面禁用三方 Cookie 对我们用户来讲肯定是一件好事,因为你的信息不会被轻而易举的就被别人追踪了,你的隐私信息也不会容易被泄漏。

然而事情真的那么简单么?贪婪的广告商绝对不会直接放弃对你的信息追踪,首先他们已经对你掌握了足够多的信息,而且三方 Cookie 只是众多获取你信息的一种手段,只不过这种方法更方便简单,为了利益,他们一定会找到更多的替代方案:

使用一方 Cookie 替代 三方 Cookie

如果我们引入了一个三方的 SDK,比如 google analytics ,说明我们对其是信任的,它对我们的信息收集追踪都是在允许范围内的。所以这些 SDK 依然可以使用第一方 Cookie 来完成用户身份标识符。

比如,gtag.jsanalytics.js 会设置以下 Cookie 用户标识用户信息:

但是,这些 Cookie 并不是第三方 Cookie,而是设在你的域下的第一方 Cookie,比如打开 twitterCookie 信息:

我们发现 _ga_gid 这两个 Cookie 正是设置在其自己域下面的。

如果使用正常的 Set-Cookie 的形式,google analytics 是无法直接将 Cookie 设置到 twitter.com 这个域下面的,而且 google analytics 发起的日志收集请求也无法携带 twitter.com 这个域下的 Cookie

打开 sdk 的代码我发现里面有使用 js 设置 Cookie 的代码:

并且,收集日志的请求中也又没携带任何 Cookie,而是把这信息带在了参数中:

这样的方式就模拟了使用三方 Cookie 标识用户信息的过程,并且完全可以替代它。总而言之禁用三方 Cookie 对这种三方 SDK 的影响并不大,只要稍微改变一下思维即可。

当然,由于 SafariFirefox 已经全面禁用了三方 Cookie,一些广告营销服务也正在给出使用一方 Cookie 的替代方案,比如 Facebook Pixel

你允许了其读取一方 Cookie 就意味着它能获取你更多的数据,这意味着你需要承担更大的用户信息泄漏的风险。而且使用一方 Cookie 也不像使用三方 Cookie 那样灵活,在某些场景下也是有很大限制的。

浏览器指纹

三方 Cookie 的主要作用是标识你的身份,从而在你下一次访问时知道你是谁,那么如果有一种技术直接就可以获取你的唯一标识时,那么就不需要再存储 Cookie 了,这个技术就是 “浏览器指纹” 。

“浏览器指纹”是一种通过浏览器对网站可见的配置和设置信息来跟踪 Web 浏览器的方法,浏览器指纹就像我们人手上的指纹一样,每个人拥有一份接近于独一无二的配置。

如果单单拿出一个配置来讲可能很多人和你拥有一样的配置,比如下面的:

  • 系统版本:

    • 我的系统版本是 Mac OS X 10_14_6
    • 大约 11.91% 的人与我的配置相同
    • 大约每 8 个人中有一个和我配置相同
  • Chrome 版本:

    • 我使用的浏览器是 Chrome,并且版本是:81.0.4044.92
    • 大约 0.08% 的人与我的配置相同
    • 大约每 1250 个人中有一个和我配置相同
  • UTC+8 时间:

    • 我的UTC+8 时间是 2020.4.15 23:00:00
    • 大约 2.30% 的人与我的配置相同
    • 大约每 43 个人中有一个和我配置相同

如果单独看每个配置,那他们都不能作为你独一无二的特征,但是综合起来看呢?比如就看这三项,三项的配置与你都相同的人的概率就会大大减小了。以上只是一些简单的特征,比如系统版本,浏览器版本,这些只需要一个简单的 navigator.userAgent 属性就可以拿到。

像这样的属性还有非常多个,他们可能来自 HTTP HeaderJavascript attributes浏览器插件 等等

HTTP Header

上面的 HTTP Header 中就包含了大量的定制化特性,可以看到每一项配置中与我相同的概率是非常低的,然而这些信息属于普通的浏览器指纹,普通指纹可以理解为容易被发现并且容易修改的部分,而且你也可以轻易的篡改他们,有些配置比如 User-Agentlanguage 使用 JavaScriptnavigator 对象获取是最准确而且不会被篡改的。下面还有一些其他常见的 JavaScript 属性:

Javascript attributes

这里面包含一些使用 Javascript 很容易获取的一些配置:

  • Screen width:屏幕宽度
  • Screen height:屏幕高度
  • Cookies enabled:是否允许 Cookie
  • Content language:语言信息
  • List of fonts:字体信息
  • Timezone:时区信息
  • Navigator properties:Navigator 对象中包含的属性信息
  • ...

以上这些信息非常容易获取,而且带有的信息较少,最后生成出来的指纹可能碰撞的概率就越大,实际上通过 JS 能获取的远不止这些,下面还有一些重复率非常低的指标:

Canvas 指纹

CanvasHTML5 中用于在网页上绘制 2D 图形元素。浏览器在绘制图形时,会调用操作系统的绘图接口,即便使用 Cavans 绘制相同的元素,但是由于系统的差别,不同浏览器使用了不同的图形处理引擎、不同的图片导出选项、不同的默认压缩级别、对抗锯齿、次像素渲染等算法也不同。

具体获取流程如下:在画布上渲染一些文字,再用 toDataURL 转换出来,你就会得到属于你的 Cavans 指纹:

    const canvas = document.getElementById("canvas-fingerprint");
    const context = canvas.getContext("2d");
    context.font = "18pt Arial";
    context.textBaseline = "top";
    context.fillText("canvas-fingerprint-test", 2, 2);
    return canvas.toDataURL("image/jpeg");

上面的图中可以看到,Canvas 指纹和我相同的概率是 <0.01% 的,可见这是一个在浏览器指纹中非常重要的指标。

WebGL

WebGL 是一种用于在网页上呈现3D图像的 JavaScript 浏览器API。网站可利用 WebGL 来识别你的设备指纹:

  • WebGL 报告 —— 完整的 WebGL 浏览器报告表是可获取、可被检测的。在一些情况下,它会被转换成为哈希值以便更快地进行分析。
  • WebGL 图像 —— 渲染和转换为哈希值的隐藏3D图像。由于最终结果取决于进行计算的硬件设备,因此此方法会为设备及其驱动程序的不同组合生成唯一值。这种方式为不同的设备组合和驱动程序生成了唯一值。

WebRTC

WebRTC (网页实时通信,Web Real Time Communication),是可以让浏览器有音视频实时通信的能力,通常被需要快速直接连接的网络应用程序所应用。即便你使用了代理,网站也能借此获取你真实的公共和本地IP地址。该插件可被用于泄漏你的本地 IP 地址或追踪媒体设备。WebRTC 会暴露你的:

  • 公共IP地址
  • 本地IP地址
  • 媒体设备的数量及其哈希值

CSS

就算用户禁用了 JavaScript ,网站也可以通过纯 CSS 来获取到一些信息,比如这样:

@media(device-width: 1920px) {
  body {
    background: url("https://example.org/1920.png");
  }
}

通过统计 1920.png 这个图片的请求日志,便可知道有哪些用户的窗口宽度是 1920px

UUID 的计算

综合以上的指标特征,可以计算出一个属于你自己的唯一的 uuid,这就是你的 "浏览器指纹" 了。当然,计算时不能简单的将上述指标进行叠加,因为某些指标在一些场景下聚合度比较高,每个指标带来的信息量也不相同,一般每个指标都拥有一个自己的 "信息熵" :

信息熵(entropy)是接收的每条消息中包含的信息的平均量,熵越高,则能传输越多的信息,熵越低,则意味着传输的信息越少。

在计算 uuid 时,一般信息熵较大的指标会拥有较大的权重,这样可以大大降低碰撞率,提高 uuid 的准确性。

当然,这些也不用你自己去挨个费劲的去获取了,使用 clientjshttps://github.com/jackspirou/clientjs) 可以轻而易举的帮你获取这些指标,并最终获取 uuid

// Create a new ClientJS object
const client = new ClientJS();

// Get the client's fingerprint id
const fingerprint = client.getFingerprint();

// Print the 32bit hash id to the console
console.log(fingerprint);

你也可以单独获取这些信息:

  const client = new ClientJS();
  client.getBrowserData();
  client.getFingerprint();
  client.getCustomFingerprint(...);
  client.isCanvas();
  client.getCanvasPrint();
  client.getFlashVersion();
  client.isSilverlight();
  client.getSilverlightVersion();
  // 。。。

参考

小结

作为一名普通用户,我会感叹,太难了,我很难保护我的个人隐私,收集我信息的平台无处不在,收集我信息的手段也是各种各样。。。

在现实世界里,没有什么会保持不变的。

作为一名开发者,你要时刻保持警惕,有危机意识,第一时间更新你的技术以应对外部环境的变化,否则你就会被淘汰。

文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注。

想阅读更多优质文章、可关注我的github博客,你的star✨、点赞和关注是我持续创作的动力!

推荐关注我的微信公众号【code秘密花园】,每天推送高质量文章,我们一起交流成长。

查看原文

赞 26 收藏 17 评论 1

ConardLi 发布了文章 · 2020-04-09

Chrome 81 发布 !消灭混合内容的最后一步~

Chrome 81 于前天正式发布了,这个版本其实最初是计划在 3 月 17 号 发布的,但由于冠状病毒(COVID-19)爆发而导致推迟到了现在。Chrome 81 的延迟也扰乱了 Google 正常的六周发布时间表。因此 Google 此前也宣布,下一个版本将直接跳过 Chrome 82 ,直接发布 Chrome 83

下面我就来带大家看看 Chrome 81 有哪些重要的更新。

速览

  • 混合内容升级三步走的第三步 —— 禁用混合img资源
  • 删除 FTP 协议支持
  • 弃用 TLS 1.0 和 TLS 1.1(延迟)
  • TLS 1.3 稳定性增强
  • 不安全的下载将被直接阻止
  • 支持 WEB NFC

混合内容升级三步走的第三步 —— 禁用混合img资源

Chrome 81 标志着 Google 分三步走的计划中的最后一个版本,该计划目的是从网络上全面消除混合 HTTPS 内容。

混合 HTTPS 内容早在上个版本(Chrome 80)的更新中我就介绍过了:是指通过 HTTPHTTPS 加载图像、JavaScript 或样式表等内容的网页,这意味着该站点实际上并不完全通过 HTTPS 加载。

Google 宣布的最终目标是将所有 HTTP 内容自动升级到他们的模拟 HTTPS URL。但是,一次性执行这样的操作是很危险的,因为这可能会导致大量混合内容的站点出现问题。

因此,为了防止造成重大破坏,Google 为该过程选择了一个三步计划:

2019 年 12 月发布的 Chrome 79 中,该团队将引入一个新设置来取消阻止特定网站上的混合内容。此设置将应用于混合脚本、iframeChrome 当前默认阻止的其他类型的内容。用户可以通过单击任意 https:// 页面上的锁定图标并单击“站点设置”来切换此设置。这将替换显示在多功能框右侧的屏蔽图标,以取消阻止以前版本的台式机 Chrome 浏览器中的混合内容。

Chrome 80 中,混合的音频和视频资源将自动升级到 https://,如果它们无法通过 https:// 加载,则 Chrome 默认会阻止它们。 Chrome 80 仍然可以加载混合图像资源,但它们会使 Chrome 在状态框上显示不安全。

Chrome 81 中,混合的图像资源会自动升级到 https://,如果无法通过 https:// 加载,Chrome默认会阻止它们。

弃用 TLS 1.0 和 TLS 1.1(延迟)

Chrome 小组早在去年10月就宣布了淘汰旧版TLS版本(TLS 1.0和1.1)的计划。Firefox 74 也在前几天宣布将禁用基于 TLS 1.0 和 TSL 1.1 协议的网站。

Chrome 81 中,Chrome 将用整页警告标记用户不支持 TLS 1.2 更高版本的站点连接不完全安全。

但是,在最新的 Chrome 官方博客中我发现:从 Chrome 删除 TLS 1.0TLS 1.1 加密协议的计划现在延迟到了 Chrome84。延迟删除这两个协议的决定与当前的新冠病毒爆发有关,因为很多重要政府医疗网站还在使用 TLS 1.0 和 1.1 来建立其 HTTPS 连接,现在进行整页警告可能会对抗击疫情造成影响。

目前 Chrome 将继续针对使用 TLS 1.01.1 的网站显示 “不安全” 提示, Chrome 81 Beta 会显示受影响网站的整页插页式警告。

删除 FTP 协议支持

Chrome 81 将不再直接支持 FTP 协议,建议用户使用本机 FTP 客户端。

TLS 1.3 稳定性增强

Chrome 之前的更新中,由于开启了 TLS 1.3,但是兼容性没有处理好,而 TLS 1.3 只有在浏览器端和服务器同时支持的时候才能正常访问。从而导致大量用户无法访问站点,谷歌官方当时给出的解决办法是先关闭浏览器的 TLS 1.3 版本的支持。后来 Chrome 又禁用了一些 TLS 1.3 的功能才使得访问稳定。

Chrome 81 中又对 TLS 1.3 做了加强,防止攻击者降级到 TLS 1.2 及更早版本,并且支持了 TLS 1.3 完整功能的向后兼容。

不安全的下载将被直接阻止

Chrome 83 开始,不安全的下载将直接被阻止,和上面的混合内容更新一样,这个更新也是分步进行的,直到 Chrome 86 所有在安全页面上的不安全的下载将被全部阻止:

支持 WEB NFC

Chrome 中添加的新的 Web NFC 标准将允许网站与 NFC 标签进行交互,从而无需用户在手机上安装特殊的应用程序。

const reader = new NDEFReader();

async function startScan() {
  await reader.scan();
  reader.onreading = (e) => {
    console.log(e.message);
  };
}

Google 相信,新的 Web NFC 标准将在 Web 开发人员中取得广泛的应用,尤其是对于 AndroidChrome 而言,该标准可用于以下场景:

当用户将运行 Chrome 的智能手机或平板电脑触摸展览附近的 NFC 卡时,博物馆和美术馆可以显示有关显示器的其他信息。

处理公司库存的网站,公司站点和 Intranet 将能够读取数据或将数据写入容器或产品上的 NFC 标签,从而简化库存管理。

会议现场可以使用它来扫描 NFC 标签。

AR(增强现实)支持

谷歌为 WebXR API 增加了两个沉浸性特性,允许在相机视图中放置虚拟物体。

查看原文

赞 10 收藏 2 评论 1

认证与成就

  • 获得 2976 次点赞
  • 获得 24 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 24 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2018-02-01
个人主页被 9.7k 人浏览