# 笔记

# 2021 年 12 月 1 日

# 解构赋值

收录了一些结构赋值常用的场景~

文档地址:ES6 官方文档 🚀 (opens new window)

# 交换变量的值

let x = 1
let y = 2

;[x, y] = [y, x]

# 从函数返回多个值

function example() {
  return {
    foo: 1,
    bar: 2
  }
}
let { foo, bar } = example()
// 同返回数组

# 函数参数的定义

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

# 提取 JSON 数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
}

let { id, status, data: number } = jsonData

# 函数参数的默认值

jQuery.ajax = function(
  url,
  {
    async = true,
    beforeSend = function() {},
    cache = true,
    complete = function() {},
    crossDomain = false,
    global = true
    // ... more config
  } = {}
) {
  // ... do stuff
}

# 遍历 MAP 结构

const map = new Map()
map.set("first", "hello")
map.set("second", "world")

for (let [key, value] of map) {
  console.log(key + " is " + value)
}

# 输入模块的指定方法

const { SourceMapConsumer, SourceNode } = require("source-map")

# 2021 年 12 月 2 日

# BigInt

号称 ECMAScript 的第八种数据类型。有以下特点

  • 必须加上后缀 n
  • 只用于表示整数
  • 不限于各种进制
  • 没有位数限制
  • 可以使用负号,但不能使用正号(+
  • 与普通整数不相等: 42n === 42 的结果为 false
  • typeof 返回的值为 BigInt

可以通过 BigInt() 函数将其他类型转换成 BigInt

BigInt 还包含 toString()valueOf()toLocaleString() 等一些方法 🚀 (opens new window)

# 2021 年 12 月 3 日

缺席...

# 2021 年 12 月 4 日

缺席...

# 2021 年 12 月 5 日

缺席...

# 2021 年 12 月 6 日

# referer === referrer ?

HTTP 中的 referer 是请求头的一个字段,用于表示这个请求是从哪个来源网页发出的,即可以检查访客从哪里来,也可以用于对付伪造的跨网站请求(csrf)

referer 的正确英文是 referrer,这是早期 HTTP 规范当中存在的拼写错误,后来为了保持向下兼容就将错就错。一般说 referrer 指的就是 document.referrer

因引用地址信息 referer 可能会带来隐私权问题,不少网页浏览器允许用户设置不要提交这个信息,有些代理服务器和防火墙也会将引用地址信息过滤掉,以避免外部获知非公开的网络地址。因此如果想要保证 referer 信息的上传,可以手动设置一下 meta 头

<meta content="always" name="referrer" />

referer 不被允许修改,浏览器会提示:Refused to set unsage header "Referer"

其他不能修改的请求头如下:

Accept-Charset
Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
Cookie2
Date
DNT
Expect
Feature-Policy
Host
Keep-Alive
Origin
Proxy-
Sec-
Referer
TE
Trailer
Transfer-Encoding
Upgrade
Via

# 2021 年 12 月 7 日

# Content-Security-Policy

作为前端开发,应该都知道浏览器的同源策略:浏览器仅加载同协议、域名及端口的脚本文档。Content-Security-Policy 也就是浏览器内容安全策略 CSP,其安全模式也是来源于“同源策略”,通过控制脚本/资源的来源来达到保证安全的目的。CSP 是一个可以显著降低 XSS 攻击的风险和影响的一种防护功能

CSP 可通过 http 标头(首选且优先级较高,一般在 nginx 配置)或 meta 标签进行配置,相关可配置资源有如下:

<!-- meta标签使用方式 -->
<meta
  http-equiv="Content-Security-Policy"
  content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'"
/>
name policy
base-uri 限制 base 标签内容来源
child-src 限制 iframe 内容来源
connect-src 限制 http 请求地址(XHRWSEventSource
font-src 限制字体文件来源地址
form-action 限制 form 表单提交地址
frame-ancestors 限制被 iframe 嵌套的来源地址(不能在 HTML 上使用)
frame-src 已弃用。请改用 child-src
img-src 限制图片资源地址
media-src 限制音视频资源地址
object-src 限制 flash 来源地址(可执行外部 js 代码)
plugin-types 限制插件来源地址
report-uri 限制发送报告的地址(不能用于 meta
script-src 限制 js 来源地址
style-src 限制 css 来源地址
upgrade-insecure-requests 指使 UserAgentHTTP 更改为 HTTPS

需要针对某一项指令配置多个值时,可以直接添加多个值。注意中间只需要空格隔开。多项指令用 隔开

script-src https://host1.com https://host2.com

如果不针对某一项指令设置值时,默认以 * 作为有效来源(即相当于无限制)。可以使用 default-src 指令替换默认行为,但需要注意,default-src 主要针对 -src 类指令的来源进行限制

指令的值可以指定域名地址,也可以有以下(不限,部分项没列举出来,只是常用项)几种关键字:

name desc
none 不执行任何匹配
self 与当前来源(非子域)匹配
unsafe-inline 允许内联 jscss
unsafe-eval 允许执行 eval 等命令

如果设置了 unsafe-inlineunsafe-eval,需要做进一步的处理。由于 CSP 可以明确规定哪些为浏览器可接受的资源集,如果是采用 XSS 攻击(内联脚本),这是无法处理的。如果要使用的话,需要使用一个加密随机数(仅使用一次)或一个哈希值将指定脚本列入白名单

<!-- 生成加密随机数 -->
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa"></script>
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

<!-- 计算脚本自身的SHA哈希值并插入到script-src指令中 -->
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
<!-- 或者 -->

相关文档:网络基础 🚀 (opens new window)

# 2021 年 12 月 8 日

# Mixed Contents

混合内容(mixed contents):当 https 页面包含有 http 的内容时,即为 mixed-contents。这种页面部分加密,部分未加密,容易受到攻击

Chrome 79 开始,Chrome 将逐渐转向默认阻止所有混合内容,并且会自动将混合资源升级到 https://

可以通过 CSP 配置相关指令(取值目前就找到一个...):

<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content" />

谷歌日志文档:https://blog.chromium.org/2019/10/no-more-mixed-messages-about-https.html (opens new window)

# 2021 年 12 月 9 日

# Referrer-Policy

这里的 Referrer-Policy 是指 http 响应头的参数,其用来监管哪些访问来源信息会在 referrer 中发送(可以理解为 A 页面跳转到 B 页面时所包含的 A 的来源信息,可以通过 document.referrer 获取)。主要有以下几种情况:

描述
no-referrer 不会发送
no-referrer-when-downgrade(default) 同安全级别下会发送(https=>https)
origin 仅发送域名
origin-when-cross-origin 同源下发送完整的 URL,否则仅发送域名
same-origin 同源下发送
strict-origin 同安全级别下发送域名
strict-origin-when-cross-origin 同源下发送完整 url,同安全级别发送域名
unsafe-url 发送完整 url

可通过 meta 标签进行设置。

<meta name="referrer" content="no-referrer" />

测试发现:修改了 Referrer-Policy 后,jscss 等资源都更改成对应值,但对 index.htmllocalhost)这个文件却不一定,其他如:部分图片、字体文件等也不一定会跟随改变。具体原因暂未查明

TIPS:这部分内容跟 referrer、CSP 等内容挺相关的,可以结合一起看看会更好一些(可直接搜索)

参考文档:

# 2021 年 12 月 10 日

# Cannot read property 'tapPromise' of undefined

这里是指使用 compression-webpack-plugin 时遇到 Cannot read property 'tapPromise' of undefined 异常的问题及解决方案

出现这个异常的问题是由于 webpack 版本导致。最新版的插件仅适用于 webpack5,而对于 webpack4 已经不支持。因此需要降低 compression-webpack-plugin 插件的版本。

经测试 6.1.1 版本可满足 webpack4 的需求,同时也是下载次数较多的一个版本

# 2021 年 12 月 11 日

# MaxInitialRequests(12.13 补)

MaxInitialRequests 是利用 SplitChunksPluginwebpack 代码分割优化的其中一个选项,是指入口代码块最多允许的并行请求数。

// 测试使用版本
"webpack": 4.46.0
"vue": 2.6.14

# 2021 年 12 月 12 日

# Preload 和 Prefetch

  • Preload 提前加载资源但并未执行,等真正被用到的时候才会立即执行。
  • Prefetch 是告诉浏览器未来可能会用到的某个资源,浏览器会在空闲时间去加载对应的资源

注意:一旦页面关闭了,preload 会立即停止获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断

注意:使用时可以考虑一下资源使用情况:vue 项目中默认会给首页需要加载的 js/css 文件加上 preload,而非首页的文件加上 prefetch。但实际上并非所有页面都会被用户访问,即并非需要加载所有的资源文件。所以需要衡量一下。大致可以考虑以下几点吧:

  1. 项目是属于对外的还是对内的(门户网站/管理后台)
  2. 项目是运行在移动端还是 PC 端(移动端可能会浪费用户流量)
  3. 项目本身的优化程度(使用预加载对项目性能的提升情况)
  4. 服务器带宽(带宽较小的情况下还是有必要考虑一下)

使用方式如下:

<!-- vue项目中已默认开启prefetch和preload -->
<head>
  <link href=/path/some.js rel=preload/prefetch as=script>
  <!-- 注意文件类型,as=script/style -->
  <link href=/path/some.css rel=preload/prefetch as=style>
</head>

相关文章:https://zhuanlan.zhihu.com/p/48521680 (opens new window)
兼容性:https://caniuse.com/?search=prefetch (opens new window)

# 2021 年 12 月 13 日

# JS 异步加载之 async、defer

asyncdefer 平时用的比较少,一般 vue 项目下的话都是直接使用楼上的 preloadprefetchasyncdefer 都是指异步加载脚本。为了更深入理解一些,所以手动去测试了一下。相关环境及说明如下:

  • 运行环境为 live-server
  • 脚本都为同步脚本
  • 浏览器版本:谷歌(96.0.4664.93

测试代码:https://github.com/Real102/testAsyncDefer.git (opens new window)

测试结果:

  • 只有 async 脚本下:脚本的执行在 DCL(DOMContentLoaded) 之后
  • 只有 defer 脚本下:脚本执行在 DCL 之前
  • async 脚本和 defer 脚本:defer 脚本始终在 DCL 之前执行。如果 async 脚本在前,其脚本的执行时间跟脚本的执行时长有关系,在 DCL 前后都有;而大部分 async 脚本在后,其脚本执行时间都在 DCL
  • async 脚本和普通脚本下:普通脚本会在 DCL 之前执行,但 async 脚本执行时间在 DCL 前后不定
  • defer 脚本和普通脚本下:defer 脚本在普通脚本后,且都在 DCL 之前
  • 混合脚本下:async 脚本不稳定,其他都在 DCL 之前

大致结论:

  • 对于脚本执行顺序,async 的脚本是加载完就执行;defer 脚本是并行加载,且刚好是在 DCL 事件前普通脚本执行后再执行(这一点没什么疑问,都一样)
  • async 脚本执行顺序按加载完成顺序依次进行,defer 脚本执行顺序根据在页面中引入顺序依次进行
  • async 脚本的执行时间点不定,如果只有 async 脚本,那么会在 DCL 事件后,如果有 defer 或普通脚本,那么在 DCL 前后都可能出现
  • defer 脚本与放置在 body 结束标签前的脚本相似,只不过 defer 脚本会提前先加载好

使用选择:

  • 如果脚本没有任何依赖,可以使用 async
  • 如果脚本依赖于另一个脚本,那么使用 defer
  • 如果脚本比较少并且作为另外一个脚本的依赖,那么可以使用内联 script 并且加上 async 属性

文字内容有点多,阅读起来可能有点乱。建议自己手动操作一遍,配合浏览器开发者工具的 performance 分析,理解会更深入一些

相关文档:

# 2021 年 12 月 14 日

缺席...

# 2021 年 12 月 15 日

# 运算符扩展

指数运算符

ES2016 新增的指数运算符 **,在此之前使用 Math 的方法Math.pow

链判断运算符

业务逻辑中经常需要用到的一种情况:判断对象内部的某个属性,比如读取 userInfo.userName 这个属性,需要写成这样:

const userName = (userInfo && userInfo.userName) || "default"

如果对象层次再深一些的话,需要判断的次数就越多。因此,在 ES2020 引入了链判断运算符?.

const userName = userInfo?.userName || "default"

除此之外,还可以判断对象方法是否存在,如果存在就立即执行

iterator.return?.()

Null 判断运算符

ES2020 引入一个新的 Null 判断运算符??,它的行为类似于||,当运算符左侧的值为 nullundefined 时,返回右侧的值

逻辑赋值运算符

ES2021 引入了三个新的逻辑赋值运算符,将逻辑运算符与赋值运算符进行结合:||=&&=??=。相当于先进行逻辑运算,然后根据运算结果,再进行赋值运算

// 老的写法
user.id = user.id || 1
// 新的写法
user.id ||= 1

# 2021 年 12 月 16 日

# splitChunks 小 tips

背景前提:vue 项目下的 splitchunks 配置

  • 如果在首页(home/app.vue)页引入的组件,打包时不论 minChunksminSize 条件达到与否,都会打包到 app.js 里面
  • 未在首页引入的组件,且chunks != initial 时,只要符合 minSize 且被引用次数超过一次的代码块,会被提取到一个新的 chunk,命名为:fileNameA~fileNameB.hash.js
  • chunks = initial 时,不论满足与否,都不会抽取出来

# 2021 年 12 月 17 日

缺席...

# 2021 年 12 月 18 日

缺席...

# 2021 年 12 月 19 日

缺席...

# 2021 年 12 月 20 日

# JSX in vue

  • render 函数内同样只能有一个根元素,包括赋值到变量中的 HTML 结构
  • slot 不能直接 <slot></slot>,需要使用 this.$slot.default,同函数式组件
  • 行内的三元运算符需要放在双大括号内
  • 不能使用 v-if,可以使用 v-show
  • v-model 需要自己实现(似 vue3 的方式)
  • 事件监听跟原生 js 类似on-click,如果方法需要携带参数,需要通过一个 function 返回 on-click={() => this.handleClick(payload)}

# 2021 年 12 月 21 日

# animation

  • animation-delay:延迟时间
  • animation-direction:每次运行完后是反向运动还是重新回到开始的位置重复运行(normalalternatereversealternate-reverse
  • animation-duration:动画周期(second
  • animation-iteration-count:重复次数(numberinfinite
  • animation-name:关键帧名称
  • animation-play-state:允许暂停和恢复动画(runningpaused
  • animation-timing-function:动画速度(easelinearstep-startcubic-bezier 等)
  • animation-fill-mode:运动完后的状态是初始还是结束时的状态(noneforwordsbackwordsboth

# 2021 年 12 月 22 日

缺席...

# 2021 年 12 月 23 日

缺席...

# 2021 年 12 月 24 日

# 观察者模式与发布-订阅模式

观察者模式

观察者模式为对象间的一种一对多的依赖关系,多个观察者对象同时监听一个目标对象,当目标对象发生变化时,主动通知所有观察者对象,使他们可以自动更新

观察者模式的优势在于形成了一个响应系统,在目前变化时就会通知监听者;而缺点是目标与监听者是耦合在一起的

订阅-发布模式

订阅-发布模式并非是 24 种基本设计模式中的一个,而是观察者模式的一个别称,或者说是一个更优解。订阅-发布模式下的订阅者与发布者没有直接的联系,是完全解耦的,他们之间都是通过事件中心(调度中心)来进行联系。发布者不需要关心谁是订阅者,而订阅者同样不用关系谁是发布者。

订阅-发布模式的优势在于发布者和订阅者是完全解耦的,两者是由事件中心来进行联系;而缺点一方面会导致数据交互变得复杂,另一方面是会导致性能的消耗会增加,创建订阅者及维护事件队列都需要耗费一定内存,订阅者创建了就会一直存在,如果没有发布,那么就永远不会被响应。

不过 vue 中提供了 object.freeze 方法,就是为了避免以上情况出现,主动将不必要的数据移除监听

观察者模式与订阅-发布模式

参考文档:https://www.cnblogs.com/onepixel/p/10806891.html (opens new window)

# 2021 年 12 月 25 日

# HTML 语义化属性

aria-*

aria 的全程为 Accessible Rich Internet Application,意思是无障碍富互联网应用

这个属性的主要用途是:增强网页在残障辅助阅读设备上的识别读取。如当页面聚焦到某一添加了 aria-label 属性的标签时,屏幕阅读器会读出 aria-label 的值,但在页面上不会有任何显示效果的区别。

role

本质上也是用于增强语义性,当 HTML 标签不能充分表达语义性的时候,可以借助 role 来说明(role 的作用是描述一个非标准的 tag 的实际作用),可以增强其可访问性、可用性和可交互性

比如用 divbutton,那么可以设置 role='button',辅助工具就可以认出这实际上是 button

# 2021 年 12 月 26 日

缺席...

# 2021 年 12 月 27 日

缺席...

# 2021 年 12 月 28 日

缺席...

# 2021 年 12 月 29 日

缺席...

# 2021 年 12 月 30 日

缺席...

# 2021 年 12 月 31 日

# RAF 动画

window.requestAnimationFrame(callback) 意思是:通知浏览器执行一个动画,并且要求在下一次重绘前调用指定的回调函数更新动画

一般电脑屏幕的刷新频率为 60Hz1s 的时间刷新 60 次,即 16~17ms/次RAF 回调函数执行次数通常与浏览器刷新次数相匹配,当屏幕上发生视觉变化时,RAF 将可以保证 JavaScript 在帧开始时运行。而如果是使用 settimeoutsetinterval 时,有可能会使动画在帧的结尾时才调用,因此可能会错过上一帧,导致卡顿

以下是 element-ui 回到顶部的组件的 RAF 案例,兼容了不存在 RAF 的浏览器

请确保总是使用第一个参数(或其它获得当前时间的方法)计算每次调用之间的时间间隔,否则动画在高刷新率的屏幕中会运行得更快

const cubic = value => Math.pow(value, 3);
const easeInOutCubic = value => value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2;

scrollToTop() {
  const el = this.el;
  const beginTime = Date.now();
  const beginValue = el.scrollTop;
  const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16));
  const frameFunc = () => {
    const progress = (Date.now() - beginTime) / 500;
    if (progress < 1) {
      el.scrollTop = beginValue * (1 - easeInOutCubic(progress));
      rAF(frameFunc);
    } else {
      el.scrollTop = 0;
    }
  };
  rAF(frameFunc);
}