dijkstra 模板

n 个节点,编号从 0 到 n-1
给定节点 start 和 end, 求最短路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def dijkstra(edges, start, end):
g = [[] for _ in range(n)]
for x, y, w in edges:
g[x].append((y, w))
g[y].append((x, w))

dist = [inf] * n
dist[start] = 0
h = [(0, start)]

while h:
dx, x = heappop(h)
if dist[x] != dx:
continue
for y, w in g[x]:
if dx + w < dist[y]:
dist[y] = dx + w
heappush(h, (dx + w, y))
return dist[end] if dist[end] != inf else -1

并查集模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class UnionFind:
def __init__(self):
self.father = {}
self.num_of_tree = 0

def merge(self, x, y):
x_root, y_root = self.find(x), self.find(y)
if x_root != y_root:
self.father[x_root] = y_root

def find(self, x):
root = x
while self.father[root] != None:
root = self.father[root]

while x != root:
origin_father = self.father[x]
self.father[x] = root
x = origin_father

return root

def add(self, x):
if x not in self.father:
self.father[x] = None

def is_connected(self, x, y):
return self.find(x) == self.find(y)

Bitcoin

transaction-based ledger

bitcon 是一个基于交易的账本

bitcoin 会维护一个 UTXO 数据结构

UTXO:upspent trasction output,用来防止 double spending

每笔交易都要指明,从哪几个交易记录到哪几个地址,

备注: 以太坊是 account-based ledger

比特币挖矿奖励减半

比特币每 21万个区块会使挖矿奖励减半,比特币通过控制挖矿难度来控制平均每 10 分钟出一个区块,而 21万个区块差不多等于 4年。

21万 * 10分钟 / (60分钟 * 24小时 * 365天) = 约4年

Bernoulli trial

比特币挖矿的每一次尝试 nonce 都是一次 Bernoulli trial

Bernoulli trial: a random experiment with binary outcome

Bernoulli process: a sequence of independent Bernoulli trials

memoryless: 前面的结果不会影响后面的概

比特币是 memoryless and progress free 的: 这提供了挖矿公平性的保证,每一次尝试都是一样的概率。

比特币网络

The BitCoin Network

appliction layer: BitCoin Block Chain

network layer: P2P Overlay Network

simple robust but not efficient

BTC 挖矿难度

挖矿目标,使得 H(block header) <= target,其中 Header 中的 nonce 可以修改,使得结果小于 target(target 是前缀一连串 0 的二进制数)

bitcoin 挖矿的本质就是尝试出 nonce 使得结果小于 target,从而获得记账权。

SHA-256 即 Secure hash Algorithm 2^256,产出 256 位二进制数。

出块时间太短会有什么问题?

同一时间会有很多分叉,算力会被分散,有恶意的算力可以集中扩展一个分叉。

好人的算力被分散了,这样坏人就不需要 51%的算力进行攻击了。

以太坊将出块时间降低到了15s,这样的话就需要新的共识机制,ghost,(扩展,orphan node, uncle reward)

调整方式?

1
target = target * (actural time / expected time)

其中 actual time = time spent mining the last 2016 blocks

expected time = 2016 * 10 * 60 (秒)

1
next_dificulty = previous_diffulty * (2 weeks / time to mine last 2016 blocks)

target 和 expect_difficulty 成反比

每次调整最大4倍,最小1/4

如何让大家都进行调整?

在 block header 中有一个字段 nBits 4个字节,nBits 是 target 的压缩版本(target 有 256位)

如果有坏矿工不进行调整,那么他产生的区块将不被其他矿工承认。

比特币如何保证安全性?

  1. 密码学上的保证:没有私钥就没办法伪造签名,并且矿工只认有正确签名的交易
  2. 共识机制:工作量证明

BTC 挖矿

从速度上来说:

CPU -> GPU -> ASIC 即 Appliction Specific Integrated Circuit

mining puzzle -> Alternative mining puzzle (目的: ASIC resistance)

有些区块链会使用可变的 mining puzzle,这样可以有效抑制 ASIC 的发展。

矿池

矿池:pool manager has many miner,pool manager 也就是全节点,有很多职责,而 miner 只负责计算 hash。

矿池如何分配奖励:使用 almost valid block,矿工只需要挖到矿池规定的具有一定数量前导 0 的 nonce,就可以得到一个 share,最终的奖励根据 share 分配。

矿池的弊病:让 51% Attack 更容易

51%算力的矿池可以干的坏事:

  • 分叉攻击
  • boycott:封锁账户

BTC 脚本

  • P2PK: pay to public key
  • P2PKH: pay to public hash
  • P2RSH: pay to redeem script hash

交易树和收据树

bloom filter 是块头的一个域

bloom filter 快速过滤掉不符合条件的区块

以太坊:transaction-driven state machine

(比特币的状态机是 utxo)

权益证明

Proof of stake

virtual mining

AltCoin Infanticide

Proof of Deposit

nothing of stake

Casper the Friendly Finality Gadget (FFG)

Validator 2/3

100 epoch -> 50 epoch,连续两个 epoch 有 2/3 通过

EOS

DPOS: Delegated Proof of Stake

vue 知识点梳理

Vue 基本使用,组件使用

高级特性

vuex vue-router 使用

webpack 知识点梳理

基本配置

拆分配置和 merge

拆分配置为 webpack.common.js、webpack.dev.js、webpack.prod.js

webpack.dev.js、webpack.prod.js 通过 webpack-merge 的 smart 继承 webpack.common.js

1
import { smart } from 'webpack-merge'

在 webpack5 中,更改为

1
import merge from 'webpack-merge'

dev、prod 区别

mode、plugin: webpack.DefinePlugin、devServer

启动本地服务

安装 webpack-dev-server,通过 –config 指定 config 文件

proxy 端口代理

处理 es6

对 js 文件处理,使用 babel-loader

使用 babelrc 文件进行配置 preset & plugin

处理样式

css:

Loader: [‘style-loader’, ‘css-loader’, ‘postcss-loader’]

postcss-loader: 配置文件 postcss.config.js 其内容 plugins: [require(‘autoprefix’)]

less:

Loader: [‘style-loader’, ‘css-loader’, ‘less-loader’]

loader 执行顺序从后往前

处理图片

url-loader: 小于指定 limit 大小的图片使用 base64,可以通过指定 output 把文件统一放在某个目录下

url-loader file-loader 区别

它们都可以用于载入静态资源文件,url-loader 比 file-loader 的优势在于可以将文件转为 base64

they are both deprecated in webpack v5,使用 asset module 代替

url-loader file-loader 原理:

loader 本质上是一个函数,它们遇到需要解析的文件,会使用 webpack loader 的 api emitFile 将文件移动到指定的路径,并将文件改写为以下内容

1
export default '/absolute/path'

or

1
export default 'data:imgae/jpeg:base64....'

这样的内容。

output

1
2
3
output: {
filename: 'bundle.[currentHash:8].js'
}

使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。

模块化

TODO: 为什么可以使用 import output 的语法?

高级配置

多入口

  1. 需要配置多个 entry

    1
    2
    3
    4
    5
    // webpack.common.js
    entry: {
    index: path.join(srcPath, 'index.js'),
    other: path.join(srcPath, 'other.js'),
    }
  2. 对应的 output 需要需改

    1
    2
    3
    4
    5
    // webpack.prod.js
    output: {
    filename: 'bundle.[currentHash:8].js',
    path: distPath, // 输出文件路径
    }

    使用 currentHash 的好处:当内容不变时,hash 值不变,浏览器可以使用缓存。

  3. HtmlWebpackPlugin 需要多个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // webpack.common.js
    // 配置多入口
    plugins: [
    new HtmlWebpackPlugin({
    template: path.join(srcPath, 'index.html'),
    filename: 'index.html',
    // 如果不指定 chunks,会引入所有 chunks
    chunks: ['index']
    }),
    new HtmlWebpackPlugin({
    template: path.join(srcPath, 'other.html'),
    filename: 'other.html',
    chunks: ['other']
    })
    ]
  4. 其他

    CleanWebpackPlugin: 会清空 output.path 指定的路径

抽离压缩 css 文件

使用 MiniCssExtractPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rules: [
{
test: '/\.less$/',
loader: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
'postcss-loader'
]
}
],
plugin: [
// 抽离 css 文件
new MiniCssExtractPlugin: {
filename: 'css/main.[contenthash:8].css'
}
],
optimization: {
minimizer: [new TerserJsPlugin({}), new OptimizeCssAssetPlugin()]
}

抽离公共代码

公用代码和第三方代码不会经常变,如果不抽离,每次都需要被重新打包,因为每次的hash值都发生变化,所以不能使用缓存,浏览量每次都会加载很慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
plugins: [
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
chunks: ['index', 'vender', 'common']
}),
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other', 'common']
})
],
optimization: {
splitChunks: {
/** initial 入口 chunk,对于异步导入的 chunk 不处理
* async 异步 chunk,只对异步导入的文件处理
* all 全部 chunk
*/
chunks: 'all',
// 缓存分组
cacheGroups: {
vendor: {
// 第三方模块
vendor: {
name: 'vender', // chunk 名称
priotrity: 1, // 权限更高,优先抽离,重要!!!
test: /node_modules/, //
minSize: 10 * 1024, // 模块的大小限制
minChunks: 1 // 最少复用过多少次
},
// 公用模块
common: {
name: 'common', // chunk 名称
priotrity: 0, // 优先级
test: /node_modules/, //
minSize: 10 * 1024, // 模块的大小限制
minChunks: 2 // 公共模块最少复用过多少次
}
}
}
}
}

怎么懒加载

使用方式:

1
2
3
4
5
6
7
setTimeout(() => {
// vue react 异步组件也是通过这种方式
// 使用 webpackChunkName 指定 chunk name
import(/* webpackChunkName: "dynamic" */ './dynamic-data.js').then(res => {
console.log(res.default.message)
})
}, 5000)

处理 jsx 和 vue

  1. react 使用 @babel/preset-react

    1
    2
    3
    4
    5
    // .babelrc
    {
    "presets": ["@babel/preset-react"],
    "plugins": []
    }
  2. vue 使用 vue-loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module: {
    rules: [
    {
    test: '/\.vue$/',
    loader: ['vue-loader'],
    include: srcPath
    }
    ]
    }

性能优化 - 优化打包效率

1. 优化 babel-loader

1
2
3
4
5
6
7
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // 开启缓存
include: srcPath, // 明确范围
// 或者
exclude: /node_module/
}
  • 开启缓存
  • 限定范围

2. IgnorePlugin

避免引用无用模块

例子:

1
2
3
4
5
// webpack.prod.js
plugins: [
// 忽略 moment 的 locale 目录
new webpack.IgnorePlugin(/\.\/local/, /moment/)
]

使用的地方

1
2
3
import moment from 'moment' // 235.4K (gzipped: 66.3K)
import 'moment/locale/zh-cn'
moment.locale('zh-cn') // 设置语言为中文

3. noParse

1
2
3
4
module: {
// 对于 react.min.js 不打包
noParse: [/react\.min\.js$/]
}
  • IgnorePlugin 直接不引于,代码中没有(同时优化产出代码)
  • noParse 引入,但不打包

4. HappyPack 多进程打包

JS 是单线程的,要开启多进程打包

提高构建速度,特别是对于多核 CPU

1
2
// webpack.prod.js
const HappyPack = require('happypack')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// webpack.prod.js
module: {
rules: [
{
test: /\.js$/,
use: ['happypack/loader?id=babel'],
include: srcPath,
}
]
},
plugins: [
new HappyPack({
// 用唯一的标标识符 id 来代表当前的 HappyPack 是用来处理哪一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法核 loader 配置一样
loaders: ['babel-loader?cacheDirectory']
})
]

5. ParallelUglifyPlugin 多进程压缩代码

使用多进程压缩代码

1
2
// webpack.prod.js
const ParallelUglifyPlugin = require('ParallelUglifyPlugin')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// webpack.prod.js
plugins: [
new ParallelUglifyPlugin({
// 本质上还是用了 UglifyJS,以下是传给 UglifyJS 的参数,优化的点在于开启了多进程
uglifyJS: {
output: {
beautify: false, // 紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次,但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]

PS:

  • 项目较大,打包较慢,开启多进程能提高速度
  • 项目较小,打包很开,开启多进程返回会降低速度(进程开销)
  • 按需使用

6. webpack 配置自动刷新和热更新

自动刷新 vs. 热更新

  • 自动刷新: 整个网页全部刷新,速度慢,状态丢失
  • 热更新: 新代码生效,页面不刷新,速度快,状态不丢失

wepack 有自带的配置字段 watch 配置自动刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
module.exports = {
entry: {
// index: path.join(srcPath, 'index.js'),
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js'),
],
other: path.join(srcPath, 'other.js'),
},
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩

hot: true,

// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',

// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': '',
},
},
},
},
};

使用的地方

1
2
3
4
5
6
7
8
9
10
11
12
import { sum } from './math'

const sumRes = sum(10, 20)
console.log('sumRes', sumRes)

// 增加开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 30);
console.log('sumRes in hot', sumRes);
});
}

7. 使用 DllPlugin

动态链接库插件

  • 前端框架如 vue、react 体积大、构建慢
  • 比较稳定,不常升级版本
  • 同一个版本只构建一次即可,不用每次都重新构建

webpack 已经内置 DllPlugin 支持

步骤:

  1. 通过 DllPlugin 打包出 dll 文件

  2. 通过 DllReferencePlugin 引用 dll 文件

性能优化 - 优化产出代码

好处:

  • 体积更小
  • 合理分包,不重复加载
  • 速度更快、内存使用更少

1. 小图片 base64 编码

使用 url-loader 配置 limit

2. bundle 使用 hash

可以使用缓存

3. 懒加载

1
import('./dynamic-data.js').then()

4. 提取公共代码

optimization.splitChunks

5. IgnorePlugin

IgnorePlugin 直接不引于,代码中没有无用的代码

6. 使用 cdn 加速

1
2
3
4
5
output: {
filename: '[name].[contentHash:8].js',
path: distPath,
publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
  1. output 中配置 publicPath,以及静态资源配置 publicPath
  2. 上传静态文件

7. 使用 mode production(包括 Tree-Shaking)

  • 自动开启代码压缩
  • Vue React 等会自动删掉调试代码(如开发环境的 warning)
  • 启用 Tree-Shaking,只有 ES6 Module 才能让 tree-shaking 生效

Tree-Shaking 举例

1
2
3
4
// math.js
export const add = (a, b) => a + b;

export const multi = (a, b) => a * b

在实际使用的地方只有使用到 add,那么 multi 就不会被打包

ES6 Module vs. Commonjs

  • ES6 Module 静态引入,编译时引入
  • commonjs 动态引入,执行时引入
  • 只有 ES6 Module 才能静态分析,实现 Tree-shaking

8. Scope Hoisting

打包出来的代码中,每个文件一个函数会合并成一个函数

好处:

  • 代码体积更小
  • 创建函数作用域更少
  • 代码可读性好
1
2
3
4
5
6
7
8
9
10
11
12
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')

moduleexports = {
resolve: {
// 针对 npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
// 开启 scope hoisting
new ModuleConcatenationPlugin()
]
}

babel

babel 只对语法进行转换,对于新的 api 不进行转换,例如 Promise 或者 [].includes

新的语法需要使用 babel-polyfill 进行转换

babel 也不管模块化,所以需要配合 webpack 使用

环境搭建 & 基本配置

.babelrc

1
2
3
4
5
6
{
"presets": [
["@babel/preset-env"]
],
"plugins": []
}

使用 babel 转换 js 文件

1
npx babel index.js

babel-polyfill

polyfill 即布丁,babel-polyfill = core-js + regenerator,在 babel 7.4 之后弃用,推荐直接使用 core-js & regenerator

core-js & regenerator

core-js 是一系列 polyfill 的合集

regenerator 对 generator 语法进行补充支持

配置按需引入

.babelrc

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": 'usage',
"corejs": 3,
}
]
],
"plugins": []
}

babel-runtime

babel-polyfill 会污染全局环境,

使用 babel-runtime 不会去污染全局环境

如果开发第三方库,必须使用 babel-runtime

构建流程概述

webpack 5

主要是内部效率优化

面试真题

1. 前端为何要进行打包和构建

  1. 代码层面
    • 体积更小(tree-shaking、压缩、合并),加载更快
    • 编译高级语言和语法(TS、ES6、模块化、scss)
    • 兼容性和错误检查(polyfill、postcss、eslint)
  2. 研发流程方面
    • 统一高效的开发环境
    • 统一的构建流程和产出标准
    • 集成公司的构建规范(提测、上线等)

2. module chunk bundle 的区别

  • module 各个源码文件,webpack 中一切皆模块
  • chunk 多模块合并成的,来源:entry、import()、splitChunk
  • bundle 最终的输入文件,一般一个 chunk 对应一个 bundle

chunk 是偏抽象的一个概念,bundle 是 chunk 的输出实体

3. loader vs. plugin

loader: 模块转换器 如:less => css

plugin: 扩展插件,如 HtmlWebpackPlugin

4. 常见的 loader 和 plugin 有哪些

5. babel vs. webpack

  • babel 只关心语法,不关心 api 不关心模块
  • webpack 打包构建工具,是多个 loader plugin 的集合,通常会结合 babel 使用

6. 如何产出一个 lib

配置 output.library

7. babel-polyfill vs. babel-runtime

8. webpack 如何实现懒加载 & vue react 异步加载路由

import()

9. 为啥 proxy 不能被 polyfill

如 class 可以用 function 模拟

如 Promise 可以用 callback 模拟

但是 proxy 的功能无法用 Object.defineProperty 模拟

10. webpack 优化构建速度

Typescript 面试知识点梳理

type 和 interface 的区别

  1. 描述的对象不同
    • interface 只能描述对象和函数
    • type 可以描述所有的类型
  2. 特性不同
    • interface 声明相同的类型名的时候可以合并,如果有属性类型冲突会报错
    • type 相比于 interface,可以声明 别名、联合类型和元组,以及可以使用 typeof 获取变量的类型
  3. 对于扩展的写法不同
    • interface 使用 extends 进行扩展
    • type 使用 &(交叉类型) 进行扩展

手写 promise

简单版实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 定义三种状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

function MyPromise(fn) {
this.state = PENDING;
this.resolveCallback = [];
this.rejectCallback = [];
// fulfilled 或者 rejected 之后需要把结果报错在对象中
this.value = null;

// 使用箭头函数,保证函数体内的 this 指向生成的 promise 对象
const resolve = (value) => {
if (this.state === PENDING) {
this.resolveCallback.map((each) => each(value));
this.value = value;
this.state = FULFILLED;
}
};

const reject = (value) => {
if (this.state === PENDING) {
this.rejectCallback.map((each) => each(value));
this.value = value;
this.state = REJECTED;
}
};

try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
const onFulfilledCallback =
typeof onFulfilled === 'function' ? onFulfilled : (x) => x;
const onRejectedCallback =
typeof onRejected === 'function'
? onRejected
: (r) => {
throw r;
};

// 注册回调函数
if (this.state === PENDING) {
this.resolveCallback.push(onFulfilledCallback);
this.rejectCallback.push(onRejectedCallback);
}

// 如果 promise 对象的当前状态已经 fulfilled,则直接执行 then 中的方法
if (this.state === FULFILLED) {
onFulfilled(this.value);
}

if (this.state === REJECTED) {
onRejected(this.value);
}

return this;
};

测试用例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 测试用例
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
}, 1000);
})
.then('world')
.then(console.log);

setTimeout(() => {
p1.then(console.log);
}, 2000);

// 1s 后输出 hello
// 再过 1s 输出 hello

测试用例 2

1
2
3
4
5
6
7
8
9
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('error');
}, 1000);
}).then(null, (error) => {
console.log(`error: `, error);
});

// 1s 后输出 error: error

Promise.all 实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Promise.myAll = function (promises) {
let count = 0;
return new Promise((resolve, reject) => {
let result = new Array(promises.length);
for (let i = 0; i < promises.length; i += 1) {
const promise = promises[i];
if (promise instanceof Promise) {
promise
.then((res) => {
result[i] = res;
count += 1;
if (count === promises.length) {
resolve(result);
}
})
.catch((err) => {
reject(err);
});
} else {
result[i] = promise;
count += 1;
if (count === promises.length) {
resolve(result);
}
}
}
});
};

测试 case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('hello');
}, 1000);
});

const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve('world');
}, 500);
});

const p3 = 999;

const p4 = Promise.reject('this is an error');

Promise.myAll([p1, p2, p3]).then((res) => {
console.log(`res: `, res);
});
// res: [ 'hello', 'world', 999 ]

Promise.myAll([p1, p2, p3, p4])
.then((res) => {
console.log(`res: `, res);
})
.catch((error) => {
console.log(`error: `, error);
})

// error: this is an error

Promise.allSettled 和 Promise.race 原理类似

eslint plugin 开发笔记

新建项目

1
2
3
4
npm install -g yo generator-eslint
mkdir eslint-plugin-demo
cd eslint-plugin-demo
yo eslint:plugin

目录结构如下:

1
2
3
4
5
6
7
8
9
.
├── README.md
├── lib
│   ├── index.js
│   └── rules
├── package.json
└── tests
└── lib
└── rules

接着执行

1
yo eslint:rule

此时目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── README.md
├── docs
│   └── rules
│   └── no-console-time.md
├── lib
│   ├── index.js
│   └── rules
│   └── no-console-time.js
├── package.json
└── tests
└── lib
└── rules
└── no-console-time.js

在 eslint-plugin-demo 目录下执行

1
npm link

控制台会输出

1
/usr/local/lib/node_modules/eslint-plugin-demo -> /Users/your-name/workspace/eslint/eslint-plugin-demo

然后在需要使用该 npm 包的项目中执行

1
npm link eslint-plugin-demo

控制台会输出

1
/Users/your-name/workspace/eslint/eslint-example/node_modules/eslint-plugin-demo -> /usr/local/lib/node_modules/eslint-plugin-demo -> /Users/daomeng.pan/workspace/eslint/eslint-plugin-demo

如果需要解除 link ,则直接删除项目中 node_modules 下的软链接即可

1
rm /Users/your-name/workspace/eslint/eslint-example/node_modules/eslint-plugin-demo

编写 eslint rule

yo eslint:rule 生成的模板文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @fileoverview this is a description
* @author pandaomeng
*/
'use strict';

module.exports = {
meta: {
docs: {
description: 'this is a description',
category: 'Fill me in',
recommended: false,
},
fixable: null, // or "code" or "whitespace"
schema: [
// fill in your schema
],
},

create: function (context) {
return {
// give me methods
};
},
};
  • meta

    • docs
      • description: rule 的描述
      • category: rule 所属的类目,参考 eslint 官方提供的 rule,有:’Best Practices’、’Stylistic Issues’、’Possible Errors’、 ‘Variables’ 、’Strict Mode’、’ECMAScript 6’ 、’Deprecated’ 、 ‘Removed’等,详见 https://eslint.org/docs/rules/
      • recommended: 当 eslint 配置中开启 “extends”: “eslint:recommended” 时,是否启用这条规则。
    • schema: 使用 Json schema 语法描述 eslint 可接收的 options 的结构
    • fixable: 在使用 --fix 的时候是否自动修复
  • create: (function) 返回一个对象,其中包含了 ESLint 在遍历 JavaScript 代码的抽象语法树 AST (ESTree 定义的 AST) 时,用来访问节点的方法。

    • 如果一个 key 是个节点类型或 selector,在 向下 遍历树时,ESLint 调用 visitor 函数

    • 如果一个 key 是个节点类型或 selector,并带有 :exit,在 向上 遍历树时,ESLint 调用 visitor 函数

    • 如果一个 key 是个事件名字,ESLint 为代码路径分析调用 handler 函数

create 方法举例

我们可以借助 ast语法树分析网站,在里面得到我们的语法树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
// 省略...
create: function (context) {
return {
Identifier: (node) => {
const identifier = node.name
// 可以对 identifier 做一些操作和判断,例如:
if (identifier === 'foo') {
context.report({
node: node.parent,
message: '这是一个自定义报错: 不可以使用 {{name}} 作为标注符',
data: {
name: identifier
}
});
}
},
};
},
};

对 import 语句的校验

使用 eslint-module-utils 提供的方法可以提高开发效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { default: moduleVisitor } = require('eslint-module-utils/moduleVisitor');

module.exports = {
// 省略...
create(context) {
const checkSourceValue = (source, importer) => {
// 具体的校验逻辑, source 为引入的路径,importer 为整个 import 语法节点
};
return {
...moduleVisitor(checkSourceValue, context.options[0]),
}
},
};

参考源码 https://github.com/benmosher/eslint-plugin-import/blob/master/utils/moduleVisitor.js

context 对象

create 函数接收一个 context 对象

提供了很多常用的方法和属性,包括 getSourceCode() 获取源码、getFileName() 获取文件名、context.options 获取配置的参数(schema 中约定好的结构) 等

正畸正颌记

​ 一直觉得自己的下巴比别人的要长,对自己的长相也一直挺自卑的。直到上大学的时候才真正知道,自己的这种症状是因为牙齿咬合不正畸形导致的,我属于比较严重的地包天加偏颌。看了很多网上牙友的看牙经历,知道了正畸正颌这一神奇的治疗方法,也看到了很多治疗成功的案例,我决定等毕业攒够钱一定要去做这个治疗。

​ 2019.06.17 我来到了上海的一家公司,这家公司距离上海九院 5公里。(上海九院是全国正畸正颌做得最好的几家医院之一),但是因为经济的原因,一直没开始我的治疗。2020 年 11 月初,我在网上挂了个九院 ”正畸正颌“ 普通初诊号。

2020.11.14 第一次挂号面诊(周六)

​ 到了初诊的这一天,我 9:00 来到九院,先是尝试在自助挂号机上挂号,但是”正畸正颌“是无法使用医保卡的,自然是挂号失败了。然后只能去窗口排队取就诊卡。九院窗口挂号的队伍很长,排了半小时才排到。挂完号之后就直接去四楼的正畸正颌科等着了。等了不是很久就到我了,面诊我的是8诊台的丁医生,她看了看我的情况,就跟我说了我这种情况是需要做正畸正颌联合治疗的,并且跟我说了治疗时长可能要两三年,费用需要十几万。因为我事先做好调研了,所以我表示都没有问题,接着丁医生就给我开了张单子,开始拍片了。在六楼交完 2800元的拍片费用之后,我当天拍完了牙片、全景、CT,核磁共震约在了下周六 11.21。还做了一项抽血,抽血是为了验是否有乙肝。把 CT 的牙片给了丁医生后,约了下次来取牙模的时间。差不多到中午的时候就全部结束回家了。

​ 进展比我预想的要快很多,本来预期只是来稍微了解下情况,没想到就直接交完钱拍好片了。

2020.11.21 拍核磁共振(周六)

​ 今天是专门来拍核磁共振(MRI)的,顺便取了下之前做的 CT胶片和验血单子。进入核磁共振巨大的铅门,医生让我躺在 MRI 的仪器上,带上耳罩,开始 MRI。其中一段过程要求我咬住一个塑料块。整个过程在头部周围一直有各种频率刺耳的声音。差不多几分钟后就结束了。中午之前就结束回家了。

2020.11.24 拍片&口扫(周二)

今天来口扫了,首先是一个医生拿着扫描器对着你的口腔一顿扫描,估计是为了出 3D 打印模型。然后去拍照,照相师把我的嘴撑开,对着牙的各个角度都拍了照片。最后就是去了一个牙模室,一个护士拿石膏一样的东西盖在你的上牙齿2分钟,等凝固后去下,然后是下牙。接着就跟丁医生约了会诊的时间。差不多也是中午前就结束了。

2020.12.8 会诊 & 拔牙(周二)

​ 会诊就是正畸医生和动手术的正颌医生一起讨论出一个正畸方案,我从早上 8:30 等到了 11点,终于轮到我的会诊了,全程就是丁医生在讲大概的正畸方案,然后正颌医生点了点头,全程两分钟左右就结束了。最后定下来的拔牙方案是,把上颌的两颗4号牙外加3颗智齿,因为有一颗智齿之前拔掉了。智齿不影响正畸带牙套,只要拔掉两个四号牙就可以开始正畸了。本来想的拔牙怎么说也要一个月时间吧,就和丁医生约了 12.31 号上牙套。

​ 下午的时候在颅颌面科挂了个号,排到之后进入诊室,医生开了一张单子让我去一楼拿消炎药和两针麻醉剂。拔牙的过程很快,5分钟不到,两颗4号牙就都拔完了。拔牙比预想的快了很多,就在微信上跟丁医生提前了带牙套的时间,约在了 12.17 号。拔完牙咬了纱布 45 分钟后吐掉了,嘴里还有一点点血,不过过了两小时左右,喝了点水,基本就没有血了。

​ 回家的时候带了个石膏牙模回去。

2020.12.17 上牙套(周四)

​ 今天预约的是早上 10点钟,随意我比平时多睡了会儿。到九院 4楼正畸科的时候刚好 9.50,看到叫号机上写着我的名字,我就直接进去了。丁医生在专家四诊台等着我,我到了以后,丁医生去取了下工具,我躺下来就开始粘牙套的托槽了。正畸医生先往我的牙齿上喷了一些酸酸的东西,酸蚀牙齿,然后一个个托槽往上粘,粘完托槽以后,医生用铁丝将所有托槽连在一起。过程粘了两个小时。

​ 中午回家的时候在白玉兰吃了碗牛肉粒手擀面,右下颌最里面的托槽直接就崩掉了,联系了丁医生,下午又回到九院重新粘上。

​ 晚上和公司同事聚餐,感觉到牙上下牙咬合的时候很疼,不能咬东西,将就吃了点东西。

​ 每顿饭后牙套里都塞满了食物,都需要认真刷牙。买了一款漱口水,刷完牙含漱一会儿。

0%