跳到主要内容

· 阅读需 3 分钟

2021.3.6

今天才注意到手机上我的博客打开很慢,虽然页面打开了,但是一直在loading, 考虑到我手机访问和电脑访问的网络环境就一个vpn的差别,因此就把电脑上的小飞机关了,清除缓存后访问我的博客,network一看,获取我的头像的网络请求一直在pending。半年前我把图床从七牛迁移到了github,现在看来是一次失败的迁移,那时候还为省下钱来沾沾自喜,原来小丑🤡竟是我自己。看了下七牛图床https流量的价位和目前的用户量(^_^只有我自己),把https开了。这次让我明白了一件事--一分钱一分货,天下没有免费的午餐。

2020.10.22

几天前访问自己的blog,发现图片全加载不出来,控制台一看,看到浏览器的告警,不允许在https页面加载http的资源。因为之前的图片全是存储于免费的七牛云空间,只能使用http,如果要走https就需要付费。身为新时代省钱好男生怎么能在这种地方花钱呢,果断转github图床。

步骤大致如下:

  1. 在七牛云控制台把之前存储的图片都download下来
  2. 在github创建图床的仓库,并且生成一个github的token用于picgo操作图床
  3. 配置七牛云,把仓库名,分支(注意是main不是master)都配置好
  4. 把仓库拉到本地,再把本地的图片放到仓库,push
  5. 把原来博客中的文章里面图片的域名替换为github的
  6. 博客打包上传,完工

· 阅读需 5 分钟

模板

// BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多

// 计算从起点 start 到终点 target 的最近距离
function bfs(start, target) {
const queue = []; // 核心数据结构
const visited = new Set(); // 避免走回头路, 如果是对树处理就可以不需要visited了

queue.push(start); // 将起点加入队列
visited.add(start); // 记录扩散的步数
let level = 0;

while (queue.length) {
const len = queue.length;
/* 将当前队列中的所有节点向四周扩散 */
for (let i = 0; i < len; i++) {
const cur = queue.shift();
/* 划重点:这里判断是否到达终点 */
if (cur === target) {
return level;
}
/* 将 cur 的相邻节点加入队列 */
for (let x of cur.neighbors) {
if (visited.has(x) === false) {
queue.push(x);
visited.add(x);
}
}
}
/* 划重点:更新步数在这里 */
level++;
}
}

真题

二叉树的最小深度

/*
* @lc app=leetcode.cn id=111 lang=javascript
*
* [111] 二叉树的最小深度
*/

// @lc code=start
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function(root) {
if (!root) return 0;
const queue = [root];
let depth = 1,
cur;
while (queue.length) {
const len = queue.length;
for (let i = 0; i < len; i++) {
cur = queue.shift();
if (!cur.left && !cur.right) return depth;
cur.left && queue.push(cur.left);
cur.right && queue.push(cur.right);
}
depth++;
}
return depth;
};

// @lc code=end

二叉树的层次遍历 II

/*
* @lc app=leetcode.cn id=107 lang=javascript
*
* [107] 二叉树的层次遍历 II
*/
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrderBottom = function(root) {
if (!root) return [];
let queue = [];
let levelArr = [];
let res = [];
queue.push(root);
queue.push("#");
while (queue.length) {
let p = queue.shift();
if (p === "#") {
res.push(levelArr);
levelArr = [];
queue.length && queue.push("#");
} else {
levelArr.push(p.val);
p.left && queue.push(p.left);
p.right && queue.push(p.right);
}
}
return res.reverse();
};

打开转盘锁

/*
* @lc app=leetcode.cn id=752 lang=javascript
*
* [752] 打开转盘锁
*/

// @lc code=start
/**
* @param {string[]} deadends
* @param {string} target
* @return {number}
*/
var openLock = function(deadends, target) {
const deads = new Set(deadends);
const queue = [];
const visited = new Set();
queue.push("0000");
visited.add("0000");
let step = 0,
cur,
len,
i,
j;

while (queue.length) {
len = queue.length;
for (i = 0; i < len; i++) {
cur = queue.shift();
if (cur === target) return step;
if (deads.has(cur)) continue;
for (j = 0; j < 4; j++) {
const up = addOne(cur, j);
if (!visited.has(up)) {
queue.push(up);
visited.add(up);
}

const down = minusOne(cur, j);
if (!visited.has(down)) {
queue.push(down);
visited.add(down);
}
}
}
step++;
}
return -1;
};

function addOne(str, position) {
str = str.split("");
let cur = +str[position];
cur = (cur + 1) % 10;
str[position] = cur;
return str.join("");
}
function minusOne(str, position) {
str = str.split("");
let cur = +str[position];
cur = (cur - 1 + 10) % 10;
str[position] = cur;
return str.join("");
}
// @lc code=end

单词接龙

/*
* @lc app=leetcode.cn id=127 lang=javascript
*
* [127] 单词接龙
*/

// @lc code=start
/**
* @param {string} beginWord
* @param {string} endWord
* @param {string[]} wordList
* @return {number}
*/
var ladderLength = function(beginWord, endWord, wordList) {
const wordSize = beginWord.length;
const allow = new Set(wordList);
if (!allow.has(endWord)) return 0;
const queue = [beginWord];
const visited = new Set(queue);
let step = 1,
len,
i,
j,
k,
newWord,
cur;
while (queue.length) {
len = queue.length;
for (i = 0; i < len; i++) {
cur = queue.shift();
if (cur === endWord) return step;
for (j = 0; j < wordSize; j++) {
for (k = 0; k < 26; k++) {
newWord = getNewWord(cur, j, k);
if (newWord !== cur && allow.has(newWord) && !visited.has(newWord)) {
queue.push(newWord);
visited.add(newWord);
}
}
}
}
step++;
}
return 0;
};

function getNewWord(str, pos, letter) {
str = str.split("");
str[pos] = String.fromCharCode(letter + 97);
return str.join("");
}
// @lc code=end

滑动谜题

/*
* @lc app=leetcode.cn id=773 lang=javascript
*
* [773] 滑动谜题
*/

// @lc code=start
/**
* @param {number[][]} board
* @return {number}
*/
var slidingPuzzle = function(board) {
let start = board[0].concat(board[1]).join("");
const target = "123450";
const neighborMap = [
[1, 3],
[0, 2, 4],
[1, 5],
[0, 4],
[1, 3, 5],
[2, 4],
];
let queue = [start];
let visited = new Set(queue);
let step = 0;

while (queue.length) {
let len = queue.length;
for (let i = 0; i < len; i++) {
let curBoard = queue.shift();
if (curBoard === target) {
return step;
}
let zeroIndex = curBoard.indexOf("0");

let neighbor = neighborMap[zeroIndex];
neighbor.forEach((neighborPos) => {
let newBoard = swap(curBoard, zeroIndex, neighborPos);

if (!visited.has(newBoard)) {
queue.push(newBoard);
visited.add(newBoard);
}
});
}
step++;
}
return -1;
};

function swap(str, i, j) {
str = str.split("");
[str[i], str[j]] = [str[j], str[i]];
return str.join("");
}
// @lc code=end

· 阅读需 9 分钟

模板

var leetcodeXXX = function(nums) {
let res = [];
let track = [];

function backtrack(start) {
if (满足条件) {
// 将当前所做的选择列表传入结果列表中
res.push(track.slice());
return;
}
for (
let i = start /* 这里取0还是取start需要看具体情况 */;
i < nums.length;
i++
) {
// 减枝操作
// 判断哪些情况下可以跳过,这一步也可以放到子递归中去处理
if (需要跳过) continue;

// 做出选择
track.push(nums[i]);
// 拿着刚刚做出的选择看结果
backtrack(i + 1);
// 撤回刚刚做出的选择
track.pop(nums[i]);
}
}
// 启动回溯算法
backtrack();
return res;
};

理解

解决一个回溯问题,实际上就是一个决策树的遍历过程 决策树必须包含问题的所有解 只需要思考 3 个问题: 1、路径:也就是已经做出的选择。 2、选择列表:也就是你当前可以做的选择。 3、结束条件:也就是到达决策树底层,无法再做选择的条件。

回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。 某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划

真题

全排列

/*
* @lc app=leetcode.cn id=46 lang=javascript
*
* [46] 全排列
*/

// @lc code=start
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let res = [];
let track = [];
function backtrack(chooses) {
if (chooses.length === track.length) {
res.push(track.slice());
return;
}
for (let i = 0; i < chooses.length; i++) {
if (track.includes(chooses[i])) continue;
// 作出选择
track.push(chooses[i]);

backtrack(chooses);

// 所做的选择反悔
track.pop();
}
}
backtrack(nums);
return res;
};
// @lc code=end

组合

/*
* @lc app=leetcode.cn id=77 lang=javascript
*
* [77] 组合
*/

// @lc code=start
/**
* @param {number} n
* @param {number} k
* @return {number[][]}
*/
var combine = function(n, k) {
let res = [];
let track = [];
function backtrack(start) {
if (track.length === k) {
res.push(track.slice());
return;
}

for (let i = start; i <= n; i++) {
track.push(i);
backtrack(i + 1);
track.pop();
}
}
backtrack(1);
return res;
};
// @lc code=end

子集

/*
* @lc app=leetcode.cn id=78 lang=javascript
*
* [78] 子集
*/

// @lc code=start
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function(nums) {
let res = [],
track = [];
function backtrack(nums, start) {
res.push(track.slice());
for (let i = start; i < nums.length; i++) {
track.push(nums[i]);
backtrack(nums, i + 1, track);
track.pop();
}
}
backtrack(nums, 0);
return res;
};

// @lc code=end

四数之和

/*
* @lc app=leetcode.cn id=18 lang=javascript
*
* [18] 四数之和
*/

// @lc code=start
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
// 回溯算法, 疯狂减枝
var fourSum = function(nums, target) {
let res = [];
let track = [];
nums.sort((a, b) => a - b);
if (
nums.length &&
(nums[nums.length - 1] * 4 < target || nums[0] * 4 > target)
) {
return [];
}

function backtrack(start = 0, Sum) {
if (Sum === target && track.length === 4) {
res.push(track.slice());
return;
}
if (track.length > 4) return;
for (let i = start; i < nums.length; i++) {
if (nums.length - i < 4 - track.length) continue;
if (i > start && nums[i] === nums[i - 1]) continue;
if (
i < nums.length - 1 &&
Sum + nums[i] + (3 - track.length) * nums[i + 1] > target
)
continue;

if (
i < nums.length - 1 &&
Sum + nums[i] + (3 - track.length) * nums[nums.length - 1] < target
)
continue;

track.push(nums[i]);
backtrack(i + 1, Sum + nums[i]);
track.pop(nums[i]);
}
}
backtrack(0, 0);
return res;
};
// @lc code=end

// console.log(fourSum([1, 0, -1, 0, -2, 2], 0));

括号生成

/*
* @lc app=leetcode.cn id=22 lang=javascript
*
* [22] 括号生成
*/

// @lc code=start
/**
* @param {number} n
* @return {string[]}
*/
var generateParenthesis = function(n) {
let track = [];
let res = [];
/**
* leftNum 剩余左括号数量
* rightNum 剩余右括号数量
*/
function backtrack(leftNum, rightNum) {
// 合格的括号对满足以下条件
// 1. 任意字串[0,...,i],左括号数量都大于右括号数量
// 2. 对于所有合法的括号对,左括号数都等于右括号数

// 剩余左括号数大于剩余右括号数,说明字串的左括号数小于右括号数,不符合条件
if (leftNum > rightNum) return;
// 剩余左括号数或者剩余右括号数不足,不符合条件
if (leftNum < 0 || rightNum < 0) return;
// 经过第一个判断,左括号数量肯定大于等于右括号数量,如果剩余左右括号数都为0,说明就是合格的括号对
if (leftNum === 0 && rightNum === 0) {
res.push(track.join(""));
return;
}

// 做选择
track.push("(");
backtrack(leftNum - 1, rightNum);
// 回溯
track.pop();

// 做选择
track.push(")");
backtrack(leftNum, rightNum - 1);
// 回溯
track.pop();
}

// 剩余左右括号数各给n个
backtrack(n, n);
return res;
};
// @lc code=end

N 皇后

/*
* @lc app=leetcode.cn id=51 lang=javascript
*
* [51] N 皇后
*/

// @lc code=start
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function(n) {
const getMatrix = (n, stuff = "") => {
const res = [];
for (let i = 0; i < n; i++) {
const list = [];
for (let j = 0; j < n; j++) {
list.push(stuff);
}
res.push(list);
}
return res;
};
const res = [];
const emptyBoard = getMatrix(n, ".");
backtrack(emptyBoard, 0, res);
return res;
};

function backtrack(matrix, row, res) {
if (matrix.length === row) {
res.push(matrix.map((item) => item.join("")));
return;
}

const rowData = matrix[row];
for (let col = 0; col < rowData.length; col++) {
if (!isValid(matrix, row, col)) {
continue;
}
rowData[col] = "Q";
backtrack(matrix, row + 1, res);
rowData[col] = ".";
}
}

function isValid(matrix, row, col) {
const size = matrix.length;
for (let i = 0; i < size; i++) {
if (
matrix[i][col] === "Q" ||
matrix[row][i] === "Q" ||
matrix[i][row + col - i] === "Q" ||
matrix[i][i + col - row] === "Q"
)
return false;
}

return true;
}
// @lc code=end

解数独

/*
* @lc app=leetcode.cn id=37 lang=javascript
*
* [37] 解数独
*/

// @lc code=start
/**
* @param {character[][]} board
* @return {void} Do not return anything, modify board in-place instead.
*/
var solveSudoku = function(board) {
const maxRow = 9,
maxCol = 9;
function isValid(i, j, ch) {
for (let k = 0; k < 9; k++) {
if (board[i][k] === ch) return false;
if (board[k][j] === ch) return false;
const chunkI = Math.floor(i / 3);
const chunkJ = Math.floor(j / 3);
const chunkIStart = chunkI * 3;
const chunkJStart = chunkJ * 3;
const iIndex = chunkIStart + Math.floor(k / 3);
const jIndex = chunkJStart + (k % 3);
if (board[iIndex][jIndex] === ch) return false;
}
return true;
}

function backtrack(i, j) {
if (j === maxCol) {
return backtrack(i + 1, 0);
}
if (i === maxRow) {
return true;
}

if (board[i][j] !== ".") {
return backtrack(i, j + 1);
}

for (let t = 1; t <= 9; t++) {
const ch = String(t);
if (!isValid(i, j, ch)) continue;
board[i][j] = ch;

if (backtrack(i, j + 1)) {
return true;
}
board[i][j] = ".";
}
return false;
}
backtrack(0, 0);
};
// @lc code=end

let matrix = [
["5", "3", ".", ".", "7", ".", ".", ".", "."],
["6", ".", ".", "1", "9", "5", ".", ".", "."],
[".", "9", "8", ".", ".", ".", ".", "6", "."],
["8", ".", ".", ".", "6", ".", ".", ".", "3"],
["4", ".", ".", "8", ".", "3", ".", ".", "1"],
["7", ".", ".", ".", "2", ".", ".", ".", "6"],
[".", "6", ".", ".", ".", ".", "2", "8", "."],
[".", ".", ".", "4", "1", "9", ".", ".", "5"],
[".", ".", ".", ".", "8", ".", ".", "7", "9"],
];
solveSudoku(matrix);
console.log(matrix);

目标和

/*
* @lc app=leetcode.cn id=494 lang=javascript
*
* [494] 目标和
*/

// @lc code=start
/**
* @param {number[]} nums
* @param {number} S
* @return {number}
*/
var findTargetSumWays = function(nums, S) {
let res = 0;
let count = 0;
function backtrack(i) {
if (i === nums.length) {
if (res === S) {
count++;
}
return;
}
res += nums[i];
backtrack(i + 1);
res -= nums[i];

res -= nums[i];
backtrack(i + 1);
res += nums[i];
}
backtrack(0);
return count;
};
// @lc code=end

· 阅读需 1 分钟

二叉树遍历

void traverse(TreeNode root) {
// 前序遍历代码
traverse(root.left);
// 中序遍历代码
traverse(root.right);
// 后序遍历代码
}

N 叉树遍历

void traverse(ListNode root) {
for(child of root.children){
// 前序遍历代码
traverse(child);
// 后序遍历代码
}
}

二叉搜索树遍历

void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}

链表遍历

这里链表遍历得着重说明一下,为什么将它放到树的遍历模板中呢?因为链表可以看作是一颗每个节点都只有一个子节点的树,因此也可以用树遍历的思想遍历链表

void traverse(ListNode head) {
// 前序遍历代码
traverse(head.next);
// 后序遍历代码
}

· 阅读需 3 分钟
const DRAFT_STATE = Symbol("immer-draft-state");

const isDraft = (value) => !!value && !!value[DRAFT_STATE];
const isDraftable = (value) => value !== null && typeof value === "object";
const has = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
const shallowCopy = (value) =>
Array.isArray(value) ? [...value] : { ...value };

function createDraft(parent, base, proxies) {
const state = {
finalized: false,
parent,
base,
copy: undefined,
drafts: {},
};
const p = Proxy.revocable(state, {
get(state, prop) {
if (prop === DRAFT_STATE) return state;
if (state.copy) {
const value = state.copy[prop];
if (value === state.base[prop] && isDraftable(value)) {
return (state.copy[prop] = createDraft(state, value, proxies));
}
return value;
}
if (has(state.drafts, prop)) return state.drafts[prop];
const value = state.base[prop];
if (!isDraft(state) && isDraftable(value)) {
return (state.drafts[prop] = createDraft(state, value, proxies));
}
return value;
},
has(state, prop) {
return Reflect.has(state.copy || state.base, prop);
},
ownKeys(state) {
return Reflect.ownKeys(state.copy || state.base);
},
set(state, prop, value) {
if (!state.copy) {
if (
(has(state.base, prop) && state.base[prop] === value) ||
(has(state.drafts, prop) && state.drafts[prop] === value)
)
return true;
markChanged(state);
}
state.copy[prop] = value;
return true;
},
deleteProperty(state, prop) {
markChanged(state);
delete state.copy[prop];
return true;
},
getOwnPropertyDescriptor(state, prop) {
const owner =
state.copy || has(state.drafts, prop) ? state.drafts : state.base;
return Reflect.getOwnPropertyDescriptor(owner, prop);
},
});
proxies.push(p);
return p.proxy;
}

function markChanged(state) {
if (!state.copy) {
state.copy = shallowCopy(state.base);
Object.assign(state.copy, state.drafts); // works on Array
if (state.parent) markChanged(state.parent);
}
}

function finalize(draft) {
if (isDraft(draft)) {
const state = draft[DRAFT_STATE];
const { copy, base } = state;
if (copy) {
if (state.finalized) return copy; // TEST: self reference
state.finalized = true;
Object.entries(copy).forEach(([prop, value]) => {
// works on Array
if (value !== base[prop]) copy[prop] = finalize(value);
});
return copy;
}
return base;
}
return draft;
}

function createImmer(base) {
const proxies = [];
const draft = createDraft(undefined, base, proxies);
const finish = () => {
const res = finalize(draft);
proxies.forEach((p) => p.revoke());
return res;
};
return { draft, finish };
}

function produce(base, producer) {
if (typeof base === "function" && producer !== "function") {
return function (s = producer, ...args) {
// TEST: produce as object method
return produce(s, (draft) => base.call(this, draft, ...args));
};
}
const { draft, finish } = createImmer(base);
const p = producer.call(draft, draft);
if (p instanceof Promise) return p.then(() => finish());
return finish();
}

const baseState = [
{
todo: "Learn typescript",
done: true,
},
{
todo: "Try immer",
done: false,
},
];
const nextState = produce(baseState, (draftState) => {
draftState.push({ todo: "Tweet about it", done: false });
draftState[1].done = true;
});
console.log(baseState, nextState);

· 阅读需 2 分钟
const fs = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const core = require("@babel/core");
const path = require("path");

function readCode(filePath) {
// read file content
const content = fs.readFileSync(filePath, "utf-8");
// generate ast
const ast = parser.parse(content, { sourceType: "module" });
// find the dependent relationship in current file
const dependencies = [];
traverse(ast, {
ImportDeclaration({ node }) {
dependencies.push(node.source.value);
},
});

// transform the code into es5 by AST
const { code } = core.transformFromAst(ast, null, {
presets: ["@babel/preset-env"],
});

return {
filePath,
dependencies,
code,
};
}

function getDependencies(entry) {
// read entry file
const entryObject = readCode(entry);
const dependencies = [entryObject];

// dependencies is not static, it will change while pushing dependency inside, so the iterator will execute several rounds
for (const assets of dependencies) {
// get file directory
const dirname = path.dirname(assets.filePath);
assets.dependencies.forEach((relativePath) => {
// get absolute path
const absolutePath = path.join(dirname, relativePath);

if (/\.css$/.test(absolutePath)) {
const content = fs.readFileSync(absolutePath, "utf-8");
const code = `
const style = document.createElement('style')
style.innerText = ${JSON.stringify(content).replace(/\\r\\n/g, "")}
document.head.appendChild(style)
`;
dependencies.push({
filePath: absolutePath,
relativePath,
dependencies: [],
code,
});
} else {
const child = readCode(absolutePath);
child.relativePath = relativePath;
dependencies.push(child);
}
});
}
return dependencies;
}

function bundle(entry) {
entry = path.resolve(__dirname, entry);
dependencies = getDependencies(entry);

let modules = "";
dependencies.forEach((dep) => {
const filePath = dep.relativePath || entry;
modules += `'${filePath}': (
function(module, exports, require){${dep.code}}
),`;
});

const result = `
(function(modules){
function require(id){
const module = {exports: {}}
modules[id](module, module.exports, require)
return module.exports
}
require('${entry}')
})({${modules}})
`;

fs.writeFileSync(path.join(__dirname, "dist", "bundle.js"), result);
}

module.exports = {
bundle,
};

· 阅读需 1 分钟

const sandboxProxies = new WeakMap()
function compileCode(src) {
src = "with (sandbox) {" + src + "}";
const code = new Function("sandbox", src);

function has (target, key) {
// 给予调用外界console的权力
if (['console'].includes(key)) {
return target[key]
}

// 对于其他属性,单独要做检查
if (!target.hasOwnProperty(key)) {
throw new ReferenceError(`${key} is not defined`)
}

// 对于未知特殊情况,就假设属性存在于内部做兜底
return true;
}

return function (sandbox) {
// 设置缓存,减少Proxy的实例化
if (!sandboxProxies.has(sandbox)) {
const sandboxProxy = new Proxy(sandbox, { has });
sandboxProxies.set(sandbox, sandboxProxy)
}

return code(sandboxProxies.get(sandbox));
};
}

globalParam = "globalParam";
let outside = "outside";

compileCode(`
let inside = 'inside'
console.log('inside') // ok
console.log(preset) // ok
console.log(globalParam) // error
console.log(outside) // error
`)({ preset: "preset" });

· 阅读需 3 分钟
function createStore(reducer, preloadedState, enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer, preloadedState);
}

let currentState = preloadedState;
let currentReducer = reducer;
let listeners = [];

function getState() {
return currentState;
}

function dispatch(action) {
currentState = currentReducer(currentState, action);
listeners.forEach((fn) => fn());
}

function subscribe(listener) {
listeners.push(listener);
return function () {
listeners = listeners.filter((fn) => fn !== listener);
};
}

function replaceReducer(nextReducer) {
reducer = nextReducer;
// 强行调用一次reducer获取外部的默认state
dispatch({ type: "@@redux/REPLACE" });
}

// 强行调用一次reducer获取外部的默认state
dispatch({ type: "@@redux/INIT" });
return {
getState,
dispatch,
subscribe,
replaceReducer,
};
}

function combineReducers(reducers) {
return function (state = {}, action) {
let combinedState = {};
Object.keys(reducers).forEach((key) => {
combinedState[key] = reducers[key](state[key], action);
});
return combinedState;
};
}

// 个人认为叫bindDispatch更合适一点
function bindActionCreators(actionCreators, dispatch) {
let boundActionCreators = {};
Object.keys(actionCreators).forEach((key) => {
boundActionCreators[key] = (...args) => {
dispatch(actionCreators[key](...args));
};
});
return boundActionCreators;
}

function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

function applyMiddleware(...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
let store = createStore(reducer, preloadedState);
const chain = middlewares.map((middleware) => middleware(store));
let dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
};
}

if (require.main === module) {
let defaultState = {
a: 1,
};
let reducer = (state = defaultState, action) => {
switch (action.type) {
case "ADD": {
const { a } = state;
return {
...state,
a: a + 1,
};
}
default:
return state;
}
};
let store = createStore(reducer);

console.group("get initial state");
console.log(store.getState());
console.groupEnd();

console.group("get state after dispatch type ADD");
store.dispatch({ type: "ADD" });
console.log(store.getState());
console.groupEnd();

console.group("subscribe");
const unsubscribe = store.subscribe(() => {
console.log("listener1");
});
store.dispatch({ type: "ADD" });
console.groupEnd();

console.group("unsubscribe");
unsubscribe();
store.dispatch({ type: "ADD" });
console.groupEnd();

console.group("test combineReducers");
let defaultState1 = {
a: 1,
};
let reducer1 = (state = defaultState1, action) => {
if (action.type === "reducer1") {
return { ...state, a: state.a + 1 };
}
return state;
};
let defaultState2 = {
b: 1,
};
let reducer2 = (state = defaultState2, action) => {
if (action.type === "reducer2") {
return { ...state, b: state.b + 1 };
}
return state;
};

let combinedReducer = combineReducers({
reducer1,
reducer2,
});

store = createStore(combinedReducer);
store.dispatch({ type: "reducer1" });
console.log(store.getState());
store.dispatch({ type: "reducer2" });
console.log(store.getState());
console.groupEnd();

console.group("applyMiddlewares");
const logger = (store) => (dispatch) => (action) => {
console.log("before", store.getState());
dispatch(action);
console.log("after", store.getState());
};
let storeWithLogger = createStore(
reducer,
defaultState,
applyMiddleware(logger)
);
storeWithLogger.dispatch({ type: "ADD" });

console.groupEnd();
}

· 阅读需 1 分钟
  1. 先用 browser-sync 创建三个不同源的服务器
const browserSync = require("browser-sync");
const bs = browserSync.create();

const serve = () => {
bs.init({
port: 8081, // 8082 8083
server: {
baseDir: ".",
},
});
};

module.exports = {
serve,
};
  1. 使用 gulp 启动
  2. 8081 作为数据接收方
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>page1</h1>
<iframe src="http://localhost:8083" style="display: none;"></iframe>
</body>
</html>
<script>
window.addEventListener("message", function(e) {
console.log(e.data);
});
</script>
  1. 8082 作为数据发送方
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>page2</h1>
<iframe src="http://localhost:8083" style="display: none;"></iframe>
</body>
</html>
<script>
let i = 0;
setInterval(() => {
window.frames[0].window.postMessage(i++, "http://localhost:8083");
}, 1000);
</script>
  1. 8083 作为中转方
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body></body>
</html>
<script>
const bc = new BroadcastChannel("Port");
window.addEventListener("message", function(e) {
bc.postMessage(e.data);
});

bc.addEventListener("message", function(e) {
window.parent.postMessage(e.data, "\*");
});
</script>

· 阅读需 22 分钟

[[toc]]

JS 源码实现

原型链

instanceof

const instanceOf = (instance, constructor) => {
let p = instance.__proto__
while(p) {
if(p === constructor.prototype) return true
p = p.__proto__
}
return false
}

console.log(instanceOf(1, Number));

new

function NEW(className, ...args) {
const obj = {};
obj.__proto__ = className.prototype;
const res = className.call(obj, ...args);
return res instanceof Object
? res
: obj;
}

function classA(age) {
this.name = "start";
this.age = age;
}
classA.prototype.sayName = function() {
console.log(this.name);
};
classA.prototype.sayAge = function() {
console.log(this.age);
};

const a = NEW(classA, 19);
a.sayName();
a.sayAge();
a.name = "a";
a.sayName();

Object.create

/**
*
* @param {*} prototype 传入的prototype将作为新对象的__proto__
* @param {*} propertyDescriptors 自定义的属性描述符
*/
function create(prototype, propertyDescriptors) {
const F = function() {};
F.prototype = prototype;
let res = new F();
if (propertyDescriptors) {
Object.defineProperties(res, propertyDescriptors);
}
return res
}

/* Object.create 和 new的本质上都是创建一个新对象,将__proto__指向prototype
两者的区别是前者直接接收prototype, 而后者接收constructor,通过constructor.prototype来间接获得prototype
*/
function create1(prototype, propertyDescriptors) {
let res = {};
res.__proto__ = prototype
if (propertyDescriptors) {
Object.defineProperties(res, propertyDescriptors);
}
return res
}

let obj = {a: 11}
let copy = Object.create(obj, {mm: {value: 10, enumerable: true}})
console.log(copy);
console.log(obj)

let obj1 = {a: 11}
let copy1 = create(obj, {mm: {value: 10, enumerable: true}})
console.log(copy1);
console.log(obj1)

let obj2 = {a: 11}
let copy2 = create1(obj, {mm: {value: 10, enumerable: true}})
console.log(copy2);
console.log(obj2)

继承

// es3
function __extends(child, parent) {
// 继承静态方法和属性
child.__proto__ = parent;
// 常规继承
function __() {
this.constructor = child;
}
__.prototype = parent.prototype;
child.prototype = new __();
}

// es5
function Parent(val) {
this.val = val;
}
Parent.StaticFunc = function() {
console.log("static");
};

Parent.prototype.getValue = function() {
console.log(this.val);
};

// 常规继承
function Child(value) {
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
// 继承静态属性方法
Child.__proto__ = Parent;

const child = new Child("bob");
Child.StaticFunc();
child.getValue();

// es6就用class继承,太过简单就不写了

this

call

Function.prototype.myCall = function(context = window, ...args) {
const unique = Symbol("fn");
context[unique] = this;
const res = context[unique](...args);
delete context[unique];
return res;
};

apply

Function.prototype.myApply = function(context = window, args = []) {
const unique = Symbol("fn");
context[unique] = this;
const res = context[unique](...args);
delete context[unique];
return res;
};

bind

// 第一版
Function.prototype.myBind = function(context, ...bindArgs) {
const _this = this;
context = context || window;

return function(...args) {
_this.apply(context, [...bindArgs, ...args]);
};
};

// 精简版
Function.prototype.myBind = function(context = window, ...bindArgs) {
return (...args) => this.apply(context, [...bindArgs, ...args]);
};

// 无依赖版
Function.prototype.myBind = function(context = window, ...bindArgs) {
const uniq = Symbol("fn");
context[uniq] = this;
return function(...args) {
const res = context[uniq](...bindArgs, ...args);
delete context[uniq];
return res;
};
};

函数式

curry

function curry(fn, ...fixs) {
return fn.length <= fixs.length
? fn(...fixs)
: (...args) => curry(fn, ...fixs, ...args);
}

// 函数柯里化核心:fn.length
// 当传入的参数列表长度大于等于 原函数所需的参数长度(fn.length)时,执行原函数
// 否则返回一个能接收参数继续进行柯里化的函数

const add = (a, b, c) => a + b + c;
const curried = curry(add);
console.log(curried(1, 2)(3));
console.log(curried(1)(2, 3));
console.log(curried(1)(2)(3));
console.log(curried(1, 2, 3, 4));
function add(...args) {
let arr = args;
function fn(...rest) {
arr = [...arr, ...rest];
return fn;
}
fn[Symbol.toPrimitive] = function() {
return arr.reduce((acc, cur) => acc + parseInt(cur));
};
// fn.toString = fn.valueOf = function () {
// return arr.reduce((acc, cur) => acc + parseInt(cur));
// };

return fn;
}

const res = add(1)(2);
console.log(res + 10); // 13
console.log(add(1)(2)(3));
console.log(add(1, 2, 3));
/**
* 函数柯里化另一种实现思路,可以实现对不定参数的函数实现柯里化,原理是每次调用只存储传入的参数,并且把存储参数的函数返回出去,重写函数的toString和valueOf,在外部对该函数进行使用时,就会调用重写后的toString和valueOf
*/

compose

function compose(...func) {
return func.reduce((a, b) => {
return (...args) => a(b(...args));
});
}

防抖节流

throttle

function throttle(fn, timeout = 200) {
let lastTime = 0;
let cur;
let result;
return function cb(...args) {
cur = Date.now();
if (cur - lastTime >= timeout) {
result = fn.call(this, ...args);
lastTime = cur;
}
return result;
};
}

function throttleSetTimeOutVersion(fn, timeout = 1000, immediate = false) {
let timer = null;
let __immediate = immediate;
return function (...args) {
if (__immediate) {
fn.call(this, ...args);
__immediate = false;
}
if (timer) return;
timer = setTimeout(() => {
fn.call(this, ...args);
timer = null;
}, timeout);
};
}

const obj = {
value: 1,
};

function print(val) {
console.log(this.value + val);
}

const printThrottled = throttle(print, 5000);

// 16毫秒执行一次printThrottled方法
setInterval(() => {
printThrottled.call(obj, 1);
}, 16);

debounce

function debounce(fn, timeout = 1000) {
let t;
let result;
return function cb(...args) {
// 在每次调用的时候都清除上一次的定时器,不管定时器内函数是否已经执行
clearTimeout(t);
t = setTimeout(() => {
result = fn.call(this, ...args);
}, timeout);
return result;
};
}

const obj = {
value: 1,
};

function print(val) {
console.log(this.value + val);
}

const printDebounced = debounce(print, 1000);

// 16毫秒执行一次printThrottled方法, 那么print永远不会被执行到
setInterval(() => {
printDebounced.call(obj, 1);
}, 16);

异步

promise 实现

/** 三种状态 */
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
/** 默认为等待态 */
status = PENDING;
/** 当状态为FULFILLED时存储的值 */
value = undefined;
/** 当状态为REJECTED时存储的拒因 */
reason = undefined;
/** 通过时的回调队列 */
successCallbacks = [];
/** 拒绝时的回调队列 */
failCallbacks = [];

constructor(executor) {
/** promise同步代码执行时的错误处理 */
try {
/** 在创建promise时执行执行器函数 */
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}

/** 以value为值通过该promise */
resolve = (value) => {
/** 只有当状态为等待态时才能改变状态 */
if (this.status === PENDING) {
/** 将状态改为完成 */
this.status = FULFILLED;
this.value = value;
/** 执行成功回调队列中的函数 */
this.successCallbacks.forEach((fn) => fn());
}
};

/** 以reason为拒因拒绝改promise */
reject = (reason) => {
/** 只有当状态为等待态时才能改变状态 */
if (this.status === PENDING) {
/** 将状态改为拒绝 */
this.status = REJECTED;
this.reason = reason;
/** 执行成功回调队列中的函数 */
this.failCallbacks.forEach((fn) => fn());
}
};

/** 向promise中执行或注册promise在不同状态时的回调事件 */
then = (onSuccess, onFail) => {
let promise2;
const resolvePromise = (promise2, x, resolve, reject) => {
/**处理promise循环调用自身的情况 */
if (promise2 === x) {
return reject(new TypeError("不允许promise循环调用自身"));
}
if (x instanceof MyPromise) {
x.then((v) => {
resolvePromise(promise2, v, resolve, reject);
}, reject);
} else {
resolve(x);
}
};
const successCallback = (resolve, reject) => {
/** promise2只有在MyPromise的逻辑结束后才能生成,如果因此如果同步执行下面的代码,获取到的promise2是undefined, 因此需要使用settimeout使下面代码执行时间推迟到promise2生成后 */
setTimeout(() => {
/** then回调函数执行时的错误处理 */
try {
/** 处理传入的callback非法的情况,当callback不是函数时,忽略这个then */
if (typeof onSuccess === "function") {
/** 不仅要执行回调函数,还要处理then中return数据作为下一个then的value的情况 */
let x = onSuccess(this.value);
resolvePromise(promise2, x, resolve, reject);
} else {
/** 以上当前promise的value作为promise2 resolve时的value */
resolve(this.value);
}
} catch (e) {
reject(e);
}
}, 0);
};

const failCallback = (resolve, reject) => {
/** promise2只有在MyPromise的逻辑结束后才能生成,如果因此如果同步执行下面的代码,获取到的promise2是undefined, 因此需要使用settimeout使下面代码执行时间推迟到promise2生成后 */
setTimeout(() => {
/** then回调函数执行时的错误处理 */
try {
/** 处理传入的callback非法的情况,当callback不是函数时,忽略这个then */
if (typeof onFail === "function") {
/** 不仅要执行回调函数,还要处理then中return数据作为下一个then的value的情况 */
let x = onFail(this.reason);
resolvePromise(promise2, x, resolve, reject);
} else {
/** 以上当前promise的reason作为promise2 reject时的reason */
reject(this.reason);
}
} catch (e) {
reject(e);
}
}, 0);
};

/** 为了支持then的链式调用,需要每次都返回一个新的promise */
promise2 = new MyPromise((resolve, reject) => {
/** 如果是PENDING状态,存储回调事件,否则直接执行 */
switch (this.status) {
case FULFILLED:
successCallback(resolve, reject);
break;
case REJECTED:
failCallback(resolve, reject);
break;
case PENDING:
this.successCallbacks.push(() => successCallback(resolve, reject));
this.failCallbacks.push(() => failCallback(resolve, reject));
break;
default:
}
});

return promise2;
};

/** this.then(null, fn)的语法糖 */
catch = (fn) => {
/** 需要链式调用,所以需要return */
return this.then(null, fn);
};

/**
* finally传入的回调函数不管promise被reject还是resolve都会被执行
* finally支持链式调用
* 如果finally返回了普通值,将无视该返回值,下一个then接收的值仍然是finally上游的返回值
* 如果返回了promise,下一个then将等待该promise的执行
* 如果回调函数在执行过程中throw了一个错误,则会作为新的拒因传递给下一个then
* 注意,finally的回调函数不接受value或者reason
*/
finally = (fn) => {
/** 需要链式调用,所以需要return */
return this.then(
(value) => {
/** 在如果fn在执行过程中抛出错误,不会执行then中的回调函数,而是继续向下传递 */
return MyPromise.resolve(fn()).then(() => value);
},
(e) => {
/** 在如果fn在执行过程中抛出错误,不会执行then中的回调函数,而是继续向下传递 */
return MyPromise.resolve(fn()).then(() => {
throw e;
});
}
);
};

/** 如果Promise.resolve的参数是一个promise,直接返回该promise,否则创建一个新的promise,该promise状态为fulfilled */
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve, reject) => {
resolve(value);
});
}

/** 不管Promise.reject的参数是什么,都将它作为拒因,创建一个新的promise,该promise的状态为rejected */
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}

/** 下面的race,allSettled,any都是以all为蓝本进行修改的 */
static all(array) {
/** 已经resolve的promise数量 */
let count = 0;
let length = array.length;
/** 返回的结果数组 */
let result = [];
/** 当数组中有一个promise的结果为rejected,直接整个promise reject,并且以该rejected状态的promise的拒因为promise.all的拒因 */
return new MyPromise((resolve, reject) => {
array.forEach((promise, index) => {
/** 通过Promise.resolve将非promise的值转为promise,来统一处理 */
MyPromise.resolve(promise).then((v) => {
result[index] = v;
count++;
/** 只有当已经resolve的promise的数量和传入的数组长度一致,才resolve结果数组 */
if (count === length) {
resolve(result);
}
}, reject);
});
});
}

static race(array) {
/** 当数组中有一个promise被resolve或者reject了,就作为race的value或reason被返回 */
return new MyPromise((resolve, reject) => {
array.forEach((promise) => {
/** 通过Promise.resolve将非promise的值转为promise,来统一处理 */
MyPromise.resolve(promise).then(resolve, reject);
});
});
}

/** 当数组中的promise被resolve或者reject时,都是被settle了,当数组中所有的promise都被settle,返回结果数组 */
static allSettled(array) {
/** 已经settle的promise数量 */
let count = 0;
let length = array.length;
/** 返回的结果数组 */
let result = [];

return new MyPromise((resolve, reject) => {
array.forEach((promise, index) => {
/** 通过Promise.resolve将非promise的值转为promise,来统一处理 */
MyPromise.resolve(promise).then(
(v) => {
result[index] = {
value: v,
status: "fulfilled",
};
count++;
/** 只有当已经settle的promise的数量和传入的数组长度一致,才resolve结果数组 */
if (count === length) {
resolve(result);
}
},
(e) => {
result[index] = {
reason: e,
status: "rejected",
};
count++;
/** 只有当已经settle的promise的数量和传入的数组长度一致,才resolve结果数组 */
if (count === length) {
resolve(result);
}
}
);
});
});
}

/** 借用Promise.all的Promise.allSettle的简化版 */
static allSettled2(array) {
return MyPromise.all(
array.map((promise) => {
return MyPromise.resolve(promise).then(
(v) => ({ status: "fulfilled", value: v }),
(e) => ({ status: "rejected", reason: e })
);
})
);
}

/** https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any */
static any(array) {
/** 已经reject的promise数量 */
let count = 0;
let length = array.length;
/** 当数组中有一个promise的结果为fulfilled,直接整个promise resolve,并且以该ulfilled状态的promise的value为promise.any的value */
return new MyPromise((resolve, reject) => {
array.forEach((promise) => {
/** 通过Promise.resolve将非promise的值转为promise,来统一处理 */
MyPromise.resolve(promise).then(resolve, (e) => {
count++;
/** 当所有的promise都失败时,reject一个 AggregateError*/
if (count === length) {
reject(
new Error(
"AggregateError: No Promise in Promise.any was resolved"
)
);
}
});
});
});
}
static retry(promiseCreator, times, delay) {
return new MyPromise((resolve, reject) => {
function attempt() {
promiseCreator()
.then((data) => {
resolve(data);
})
.catch((err) => {
if (times === 0) {
reject(err);
} else {
times--;
setTimeout(attempt, delay);
}
});
}
attempt();
});
}
}

module.exports = MyPromise;

promisify

function func(a, b, cb) {
const res = a + b;
cb(null, res);
}

function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
args.push((err, data) => {
if (err) reject(err);
else resolve(data);
});
fn.apply(null, args);
});
};
}

const funcPromisify = promisify(func);

funcPromisify(1, 2).then((val) => {
console.log(val);
});

callbackify

const callbackify = (promiseCreator) => {
return (...args) => {
const arg = args.slice(0, -1);
const cb = args.slice(-1)[0];
promiseCreator(...arg)
.then((val) => {
cb(null, val);
})
.catch((err) => {
cb(err, null);
});
};
};

co

function ajax(url) {
return Promise.resolve(url);
}

function* main() {
let res1 = yield ajax("https://www.baidu.com");
console.log(res1);

let res2 = yield ajax("https://www.google.com");
console.log(res2);

let res3 = yield ajax("https://www.taobao.com");
console.log(res3);
}

function co(generator) {
const g = generator();
function handleResult(result) {
if (result.done) return;
result.value.then(
(res) => {
handleResult(g.next(res));
},
(e) => {
g.throw(e);
}
);
}
handleResult(g.next());
}

co(main);

await

const getData = () =>
new Promise((resolve) => setTimeout(() => resolve("data"), 1000));

const test = asyncToGenerator(function* testG() {
const data = yield getData();
console.log("data: ", data);
const data2 = yield Promise.reject(222);
console.log("data2: ", data2);
return "success";
});

// 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
test().then((res) => {
console.log(res);
});

function asyncToGenerator(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments);
return new Promise((resolve, reject) => {
function step(operate, val) {
let genRes;
try {
genRes = gen[operate](val);
} catch (err) {
return reject(err);
}
const { value, done } = genRes;
if (done) {
resolve(value);
} else {
Promise.resolve(value).then(
(val) => step("next", val),
(err) => step("throw", err)
);
}
}
step("next");
});
};
}

数组

filter

Array.prototype.filter = function(filterCb, thisArg) {
const res = [];
const arr = this;
for (let i = 0; i < arr.length; i++) {
if (filterCb.call(thisArg, arr[i], i, arr)) {
res.push(arr[i]);
}
}
return res;
};

console.log([1, 2, 3].filter((v) => v > 1));

flat

function flat(arr, depth = Infinity) {
if (depth === 0) return arr;
return arr.reduce(
(acc, cur) => acc.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur),
[]
);
}

console.log(flat([1, 2, [3, 4, 5, [6, 7, 8]]], 1));

let arr = [1, [2, [3, 4], 5], 6];
let str = JSON.stringify(arr);
console.log(str.replace(/\[|\]/g, "").split(","));
console.log(JSON.parse("[" + str.replace(/\[|\]/g, "") + "]"));

其他

Object.is

function is(x, y) {
if (x === y) {
if (x !== 0 && y !== 0) return true;
// x和y相等的情况下,处理+0和-0的情况
else return 1 / x === 1 / y;
} else {
// x和y不相等的情况下,处理NaN的情况
return x !== x && y !== y;
}
}

console.log(is(+0, -0));
console.log(is(NaN, NaN));

deepClone

function isReferenceType(o) {
return o instanceof Object;
}

/**
* 1. 判断是否引用类型,如果不是直接返回
* 2. 针对正则、函数和Date做异常处理
* 3. 获取到原对象的constructor,创建新对象
* 4. 引入WeakMap解决循环引用问题
* 5. 遍历原对象中的数据,将数据通过深拷贝的方式赋值给新对象
*/
function deepClone(obj, hash = new WeakMap(), parent) {
if (!isReferenceType(obj)) {
return obj;
}

if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 传入父级节点解决负责的函数this指向丢失的情况
if (obj instanceof Function) return obj.bind(parent);

const newObj = new obj.constructor();

// 解决循环引用问题
if (hash.has(obj)) {
return hash.get(obj);
}
hash.set(obj, newObj);

// Reflect.ownKeys可将对象上的可枚举和不可枚举以及symbol都访问到
Reflect.ownKeys(obj).forEach((key) => {
newObj[key] = deepClone(obj[key], hash, newObj);
});

return newObj;
}

function add(a, b, c) {
return a + b + c;
}

const obj1 = {
a: 1,
b: 2,
date: new Date(),
arr: [1, 2, 3],
func: add.bind({}, 1),
};
obj1.obj = obj1;

console.log(deepClone(obj1).func(2, 3));

JSONP

function JSONP(url) {
return new Promise((resolve, reject) => {
const callbackName = `callback_${Date.now()}`;
const script = document.createElement('script');

script.src = `${url}${url.indexOf('?') > -1 ? '&' : '?'}callback=${callbackName}`;
document.body.appendChild(script);
window[callbackName] = function (res) {
delete window[callbackName];
script.remove();
resolve(res);
};
script.onerror = function (e) {
delete window[callbackName];
script.remove();
reject(e);
};
});
}

ajax

const addUrl = (_url, param) => {
let url = _url;
if (param && Object.keys(param).length) {
url += url.indexOf("?") === -1 ? "?" : "&";
Object.keys(param).forEach((key) => {
url += `${encodeURIComponent(key)}=${encodeURIComponent(param[key])}`;
});
}
return url;
};

function ajax({
url = "",
method = "GET",
data = {},
header = {},
async = false,
timeout = 5 * 1000,
onSuccess,
onError,
onTimeout,
}) {
const requestURL = method === "GET" ? addUrl(url, data) : url;
const sendData = method === "GET" ? null : data;

const xhr = new XMLHttpRequest();
xhr.open(method, requestURL, async);

if (header && Object.keys(header).length) {
Object.keys(header).forEach((key) => {
xhr.setRequestHeader(key, header[key]);
});
}

xhr.onload = () => {
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
onSuccess(xhr.responseText);
} else {
onError(xhr.status + xhr.statusText);
}
} catch (err) {
onError(err);
}
};
xhr.timeout = timeout;
xhr.ontimeout =
onTimeout ||
function() {
console.error("request timeout");
};

xhr.send(sendData);
}

node require

const vm = require("vm");
const path = require("path");
const fs = require("fs");

function customRequire(filePath) {
const pathToFile = path.resolve(__dirname, filePath);
const content = fs.readFileSync(pathToFile, "utf-8");

const wrapper = ["(function(require, module, exports) {", "})"];
const wrappedContent = wrapper[0] + content + wrapper[1];
console.log("wrappedContent: ", wrappedContent);

const script = new vm.Script(wrappedContent, {
filename: "index.js",
});
const module = {
exports: {},
};
const func = script.runInThisContext();
func(customRequire, module, module.exports);
return module.exports;
}

const { add } = customRequire("./module.js");
console.log(add(1, 2));

eventEmitter

class EventEmitter {
constructor() {
this.events = {};
this.count = 0;
}

subscribe(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);

return this.unsubscribe.bind(this, type, listener);
}

unsubscribe(type, listener) {
if (this.events[type]) {
this.events[type] = this.events[type].filter((cb) => cb !== listener);
}
}

publish(type, ...args) {
if (this.events[type]) {
[...this.events[type]].forEach((cb) => {
cb.call(this, ...args);
});
}
}
}

const event = new EventEmitter();
event.subscribe("daily", function() {
// 校验call的绑定
console.log(`there has already ${this.count} subscribers`);
console.log("bob");
});

event.subscribe("evening", () => {
console.log("alice");
});

event.subscribe("noon", () => {
console.log("Jim");
});

const JimEveningFn = function() {
console.log("Jim");
};
event.subscribe("evening", JimEveningFn);

event.publish("daily");
console.log("****");
event.publish("evening");
console.log("****");
event.publish("noon");

eventEmitterProxy

class Observer {
constructor(target) {
this.observers = [];
this.target = new Proxy(target, {
set: (target, key, value, receiver) => {
const result = Reflect.set(target, key, value, receiver);
[...this.observers].forEach((cb) => cb(target, key, value, receiver));
return result;
},
});

return [this.target, this.observe];
}

observe = (fn) => {
this.observers.push(fn);
return this.unobserve.bind(null, fn);
};

unobserve = (fn) => {
this.observers = this.observers.filter((o) => o !== fn);
};
}

let [person, observe] = new Observer({ name: 1, age: 2 });
let unobserve = observe((target, key, value) => {
console.log("target, key, value: ", target, key, value);
});

person.name = "bob"; // target, key, value: { name: 'bob', age: 2 } name bob

unsubscribe();
person.name = "aaa"; // 无输出

shuffle

function shuffle(arr) {
for (let i = 0; i < arr.length; i++) {
let changeIndex = Math.floor(Math.random() * i);
[arr[i], arr[changeIndex]] = [arr[changeIndex], arr[i]];
}
return arr;
}

let arr2 = [1, 2, 3, 4, 5, 6];
console.log(shuffle(arr2));

sleep

function sleep (time) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, time);
})
}

async function main(){
console.log(1)
await sleep(2000)
console.log(2)
}

main()