算法笔记

前缀和

nums = [1, 2, 4, 3]
原地前缀和, 新的 nums[i] 表示 0 ~ i 的累计和

1
2
3
n = len(nums)
for i in range(1, n):
nums[i] += nums[i - 1]

另外一种写法, 得到的 s 的长度为 n + 1, s[0] = 0

1
s = list(accumulate(nums, initial=0))

拓扑排序

1
from graphlib import TopologicalSorter

拓扑排序思想:
1、找到所有入度为 0 的节点存入双端队列
2、BFS,遍历到时入度减一,当入度为 0 时入队列
3、最终的 len(order) == 总的节点数,则表示排序成功,否则排序失败

模板

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
def topo_sort(self, edges):
g = defaultdict(list)
inDeg = defaultdict(int)
for x, y in edges:
inDeg[x] = 0

for x, y in edges:
g[x].append(y)
inDeg[y] += 1

q = deque([x for x, d in inDeg.items() if d == 0])

order = []
while q:
x = q.popleft()
order.append(x)
for y in g[x]:
inDeg[y] -= 1
if inDeg[y] == 0:
q.append(y)

if len(order) == len(inDeg):
return order
else:
return None

前缀树模板

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
class Trie:
def __init__(self):
self.children = [None] * 26
self.isEnd = False


def insert(self, word: str) -> None:
node = self
for c in word:
idx = ord(c) - ord('a')
if not node.children[idx]:
node.children[idx] = Trie()
node = node.children[idx]
node.isEnd = True


def search(self, word: str) -> bool:
node = self
for c in word:
idx = ord(c) - ord('a')
if not node.children[idx]:
return False
else:
node = node.children[idx]
return node.isEnd


def startsWith(self, prefix: str) -> bool:
node = self
for c in prefix:
idx = ord(c) - ord('a')
if not node.children[idx]:
return False
else:
node = node.children[idx]
return True

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

Floyd 模板

1
2
3
4
5
6
7
8
9
10
11
12
def floyd(edges):
w = [[inf] * n for _ in range(n)]
for x, y, wt in edges:
w[x][y] = wt
w[y][x] = wt

f = w
for k in range(n):
for i in range(n):
for j in range(n):
f[i][j] = min(f[i][j], f[i][k] + f[k][j])
return f
1
f[i][j] 代表 i 到 j 的最短距离 

并查集模板

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 原理类似

0%