Skip to content

常见手撕

基础题

大数相加

js
var addStrings = function(num1, num2) {
    const maxLen = Math.max(num1.length, num2.length);
    num1 = num1.padStart(maxLen, '0');
    num2 = num2.padStart(maxLen, '0');
    let carry = 0;
    const res = [];
    for(let i = (maxLen - 1); i >= 0; i--){
        let tempNum = parseInt(num1[i]) + parseInt(num2[i]) + carry;
        if(tempNum >= 10){
            carry = 1;
        } else {
            carry = 0;
        }
        res.unshift(tempNum % 10);
    }
    if(carry === 1) res.unshift('1');
    return res.join('');
};

大数相乘

js
let multiply = function (num1, num2) {
    if (isNaN(num1) || isNaN(num2)) return '';
    if (num1 === '0' || num2 === '0') return '0';

    let l1 = num1.length,
        l2 = num2.length;

    let result = [];

    for (let i = l1 -1; i >= 0; i--) {
        for (let j = l2 - 1; j >= 0; j--) {
            let index1 = i + j;
            let index2 = i + j + 1;

            let product = num1[i] * num2[j] + (result[index2] || 0);
            result[index2] = product % 10;
            result[index1] = Math.floor(product / 10) + (result[index1] || 0);
        }
    }
    return result.join('').replace(/^0+/, '');
}
console.log(multiply('123', '234')) //28782

双指针

打印所有回文子串

中心扩展法:

js
function palindrome(s) {
  const res = new Set();

  for (let i = 0; i < s.length; i++) {
    let left = i - 1;
    let right = i + 1;
    while (left >= 0 && right <= s.length && s[left] === s[right]) {
      res.add(s.slice(left, right + 1));
      left--;
      right++;
    }
  }
  return res;
}

console.log(palindrome('abacdfdcaba')); //  {'aba', 'dfd', 'cdfdc', 'acdfdca', 'bacdfdcab', …}

字符串压缩

js
var compressString = function(S) {
    if (S.length < 2) {
        return S
    }
    let res = ''
    let i = 0
    while (i < S.length) {
        let j = i + 1
        let num = 1
        while (j < S.length) {
            if (S[i] === S[j]) {
                j++
                num++
            } else {
                break
            }
        }
        res += `${S[i]}${num}`
        i += j - i
    }
    return S.length > res.length ? res : S
};

压缩字符串展开

js
var decompressString = function(S) {
    let res = '';
    let i = 0;

    while (i < S.length) {
        let char = S[i];
        i++;

        // 提取数字部分,可能是多位数字
        let num = '';
        while (i < S.length && !isNaN(S[i])) {
            num += S[i];
            i++;
        }

        // 将字符重复 num 次
        res += char.repeat(parseInt(num));
    }

    return res;
};

// 示例用法:
console.log(decompressString("a2b3c1"));  // 输出: aabbbc

合并两个有序数组

js
var merge = function(nums1, m, nums2, n) {
    let p1 = 0, p2 = 0;
    const sorted = new Array(m + n).fill(0);
    var cur;
    while (p1 < m || p2 < n) {
        if (p1 === m) {
            cur = nums2[p2++];
        } else if (p2 === n) {
            cur = nums1[p1++];
        } else if (nums1[p1] < nums2[p2]) {
            cur = nums1[p1++];
        } else {
            cur = nums2[p2++];
        }
        sorted[p1 + p2 - 1] = cur;
    }
    for (let i = 0; i != m + n; ++i) {
        nums1[i] = sorted[i];
    }
};

三数之和

题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

思路:

  1. 首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sum 判断是否满足为 0,满足则添加进结果集
  2. 如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环
  3. 如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
  4. 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
  5. 当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
  6. 时间复杂度:O(n^2 ),n 为数组长度
js
var threeSum = function(nums) {
    let ans = [];
    const len = nums.length;
    if(nums == null || len < 3) return ans;
    nums.sort((a, b) => a - b); // 排序
    for (let i = 0; i < len ; i++) {
        if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
        let L = i+1;
        let R = len-1;
        while(L < R){
            const sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                ans.push([nums[i],nums[L],nums[R]]);
                while (L<R && nums[L] == nums[L+1]) L++; // 去重
                while (L<R && nums[R] == nums[R-1]) R--; // 去重
                L++;
                R--;
            }
            else if (sum < 0) L++;
            else if (sum > 0) R--;
        }
    }        
    return ans;
};

滑动窗口

无重复字符最长字串

js
var lengthOfLongestSubstring = function(s) {
    var ans = 0;
    for(let i = 0,j = 0;j <= s.length;j++){
        var str = s.substring(i,j)
        if(str.indexOf(s.charAt(j)) != -1)
                i += str.indexOf(s.charAt(j)) + 1;
        ans = Math.max(ans,str.length);
    }
    return ans;
};

二分

二分查找

js
var search = function (nums, target) {
    // 有序数组
    let right = nums.length - 1;
    let left = 0;

    while(right >= left){
        let mid = Math.ceil((right+left)/2);
        if(nums[mid] === target) return mid;
        if(nums[mid] > target){
            right = mid - 1;
        } else {
            left = mid + 1;
        }
        // console.log(left, right);
    }
    return -1;
};

二叉树

和为目标值的路径

js

var pathSum = function (root, target) {
  let res = [];
  const backtrack = (root, path, sum) => {
    if (root == null) return null;
    path.push(root.val);
    // 路径中加入当前节点的值
    sum += root.val;
    // 到了叶子节点 并且 整个路径的值的和 和target相等,则推入结果集中
    if (root.left == null && root.right == null && target == sum)
      res.push(path.slice());
    // 递归的去左右子树当中查找路径
    backtrack(root.left, path, sum);
    backtrack(root.right, path, sum);
    sum -= root.val;
    path.pop();
  };
  backtrack(root, [], 0);
  return res;
};

二叉树的遍历

前序遍历 - 递归

js
// 树节点的定义
class TreeNode {
  constructor(val) {
    this.val = val;
    this.left = null;
    this.right = null;
  }
}

// 前序遍历递归实现
function dfsPreorder(root) {
  if (!root) return; // 如果根节点为空,则直接返回

  console.log(root.val); // 访问当前节点的值
  dfsPreorder(root.left); // 递归遍历左子树
  dfsPreorder(root.right); // 递归遍历右子树
}

// 示例用法
const root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);

console.log("DFS 前序遍历结果:");
dfsPreorder(root);

二进制二叉树

js
function calculateDecimal(node, currentNumber = "") {
    if (!node) return 0; // 如果节点为空,返回0

    // 更新当前的二进制数
    currentNumber += node.value.toString();

    // 如果是叶子节点,将二进制数转换为十进制数
    if (!node.left && !node.right) {
        return parseInt(currentNumber, 2);
    }

    // 继续遍历左右子树
    return calculateDecimal(node.left, currentNumber) + calculateDecimal(node.right, currentNumber);
}

// 计算整棵树所有叶子节点值的和
let totalDecimal = calculateDecimal(root);
console.log(totalDecimal); // 输出计算得到的十进制数

层序遍历 - 队列

js
// 层序遍历实现
function bfsLevelOrder(root) {
  if (!root) return; // 如果根节点为空,则直接返回

  const queue = [root]; // 使用队列来存储每一层的节点

  while (queue.length > 0) {
    const node = queue.shift(); // 出队列
    console.log(node.val); // 访问当前节点的值

    if (node.left) {
      queue.push(node.left); // 左子节点入队列
    }
    if (node.right) {
      queue.push(node.right); // 右子节点入队列
    }
  }
}

// 示例用法(使用前面定义的树结构)
console.log("BFS 层序遍历结果:");
bfsLevelOrder(root);

BST 实现二叉搜索树

js
class TreeNode {
    constructor(key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }
}

class BST {
    constructor() {
        this.root = null;
    }

    // 插入一个新的 key
    insert(key) {
        const newNode = new TreeNode(key);
        if (this.root === null) {
            this.root = newNode;
        } else {
            this._insertNode(this.root, newNode);
        }
    }

    _insertNode(node, newNode) {
        if (newNode.key < node.key) {
            if (node.left === null) {
                node.left = newNode;
            } else {
                this._insertNode(node.left, newNode);
            }
        } else {
            if (node.right === null) {
                node.right = newNode;
            } else {
                this._insertNode(node.right, newNode);
            }
        }
    }

    // 搜索一个 key
    search(key) {
        return this._searchNode(this.root, key);
    }

    _searchNode(node, key) {
        if (node === null) return false;
        if (key < node.key) return this._searchNode(node.left, key);
        if (key > node.key) return this._searchNode(node.right, key);
        return true; // key === node.key
    }

    // 删除一个 key
    delete(key) {
        this.root = this._deleteNode(this.root, key);
    }

    _deleteNode(node, key) {
        if (node === null) return null;

        if (key < node.key) {
            node.left = this._deleteNode(node.left, key);
            return node;
        } else if (key > node.key) {
            node.right = this._deleteNode(node.right, key);
            return node;
        } else {
            // key === node.key
            // 节点无子节点或只有一个子节点
            if (node.left === null) return node.right;
            if (node.right === null) return node.left;

            // 节点有两个子节点,找到右子树的最小节点
            const minNode = this._findMinNode(node.right);
            node.key = minNode.key;
            node.right = this._deleteNode(node.right, minNode.key);
            return node;
        }
    }

    _findMinNode(node) {
        while (node && node.left !== null) {
            node = node.left;
        }
        return node;
    }

    // 中序遍历
    inorder() {
        this._inorderNode(this.root);
        console.log('');
    }

    _inorderNode(node) {
        if (node !== null) {
            this._inorderNode(node.left);
            process.stdout.write(`${node.key} `);
            this._inorderNode(node.right);
        }
    }
}

// 使用示例
const bst = new BST();
bst.insert(50);
bst.insert(30);
bst.insert(20);
bst.insert(40);
bst.insert(70);
bst.insert(60);
bst.insert(80);

console.log("Inorder traversal of the BST:");
bst.inorder();

console.log("Search for key 40:");
console.log(bst.search(40) ? "Found" : "Not Found");

console.log("Delete key 20:");
bst.delete(20);
bst.inorder();

console.log("Delete key 30:");
bst.delete(30);
bst.inorder();

console.log("Delete key 50:");
bst.delete(50);
bst.inorder();

实现前缀树

js
class TrieNode {
    constructor() {
        this.children = {}; // 存储子节点
        this.isEndOfWord = false; // 标记这个节点是否是一个单词的结束
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    // 插入一个单词到前缀树
    insert(word) {
        let currentNode = this.root;
        for (let i = 0; i < word.length; i++) {
            const char = word[i];
            if (!currentNode.children[char]) {
                currentNode.children[char] = new TrieNode();
            }
            currentNode = currentNode.children[char];
        }
        currentNode.isEndOfWord = true;
    }

    // 检查单词是否在前缀树中
    search(word) {
        let currentNode = this.root;
        for (let i = 0; i < word.length; i++) {
            const char = word[i];
            if (!currentNode.children[char]) {
                return false;
            }
            currentNode = currentNode.children[char];
        }
        return currentNode.isEndOfWord;
    }

    // 检查是否有任何单词以给定前缀开始
    startsWith(prefix) {
        let currentNode = this.root;
        for (let i = 0; i < prefix.length; i++) {
            const char = prefix[i];
            if (!currentNode.children[char]) {
                return false;
            }
            currentNode = currentNode.children[char];
        }
        return true;
    }
}

链表

反转链表

js
function ListNode(val, next) {
    this.val = (val===undefined ? 0 : val)
    this.next = (next===undefined ? null : next)
}
 
let temp = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode)));
js
function ListNode(val, next) {
    this.val = (val===undefined ? 0 : val)
    this.next = (next===undefined ? null : next)
}
 
let head = new ListNode(1, new ListNode(2, new ListNode(3)));
 
 
let reverseList = function(head) {
    if (head == null || head.next == null) {
        return head;  // 如果链表只有一个元素或者是空链表,我们直接返回
    }
    let newHead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}
 
reverseList(head);

链表判断环

遍历链表中的每个节点,并将保存到set,如果下次遇到了遍历过的节点,说明链表中存在环,此时的节点为入环节点。时间复杂度O(n) 空间复杂度O(n)

js
function hasCycle(head) {
    const visited = new Set();
    while (head !== null) {
        if (visited.has(head)) {
            return true;
        } else {
            visited.add(head);
            head = head.next;
        }
    }
    return false;
}

一个走两步,一个走一步,如果链表中有环,必会相遇。

js
function hasCycle(head) {
    let one=head;
    let two=head;
    while(two!=null&&two.next!=null){
        one=one.next;
        two=two.next.next;
        if(one===two){
            return true;
        }
    }
    return false;
}

括号匹配

js
// 括号匹配算法
function isValid(str){
    let strArr = str.split(''),
        left = [];// 空栈
    for(let i=0;i<strArr.length;i++){
        if(strArr[i] == '(' || strArr[i] == '[' || strArr[i] == '{'){
            left.push(strArr[i]) //左括号入栈
        }else{
            if(strArr[i] == ')' && left.pop() != '('){
                return false //结束循环
            }
            if(strArr[i] == ']' && left.pop() != '['){
                return false 
            }
            if(strArr[i] == '}' && left.pop() != '{'){
                return false
            }
        }
    }
    return left.length == 0
}

let test = '{9()32358}'
console.log(isValid(test))

动态规划

最小编辑距离

js
var minDistance = function(word1, word2) {
    const len1 = word1.length;
    const len2 = word2.length;
    const dp = Array.from(Array(len1 + 1), () => Array(len2 + 1))
    dp[0][0] = 0

    for (let i = 1; i <= len1; i++) dp[i][0] = dp[i - 1][0] + 1

    for (let i = 1; i <= len2; i++) dp[0][i] = dp[0][i - 1] + 1

    for (let i = 1; i <= len1; i++) {
        for (let j = 1; j <= len2; j++) {
            if (word1[i - 1] == word2[j - 1]) 
                dp[i][j] = dp[i - 1][j - 1]
            else
                dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j-1]) + 1
        }
    }

    return dp[len1][len2]
};

跳跃游戏4

js
function maxResult(nums: number[], k: number): number {
  const dp: number[] = new Array(nums.length).fill(0);
  dp[0] = nums[0];
  const queue: number[] = [0];
  for (let i = 1; i < nums.length; i++) {
      // 排除区间之外的点
    while(queue.length && queue[0] < Math.max(i - k, 0)) queue.shift();
    dp[i] = dp[queue[0]] + nums[i];
        // 维护队列单调性
    while(queue.length && dp[queue[queue.length - 1]] < dp[i]) queue.pop();
    queue.push(i);
  }
  return dp[nums.length - 1];
};

最大连续子数组和

js
function contMax(array) {
	if (array.length == 0)
		return 0
	var sum = array[0] //保存每组的和
	var maxSum = array[0] //连续子数组最大和
	for (var i = 1; i < array.length; i++) {
		sum = Math.max(sum + array[i], array[i]);
		maxSum = Math.max(sum, maxSum)
	}
	return maxSum
}

console.log(contMax([1, -3, 9, 10, -2, 3, -6, 5])) //20

回溯

全排列

js
function permute(nums){
    let res = []; // 保存结果
    let path = []; //当前所在位置的路径
    let used = [];//记录当前这条路径下第 i 位的数字是否被使用过
    backTracking(nums, [])
    return res;
    function backTracking(nums: number[], used){
        // 结束递归的条件
        if(path.length === nums.length){
            res.push(Array.from(path));
            return;
        }
        for(let i = 0; i < nums.length; i++){
            if(used[i]){
                continue;
            }
            path.push(nums[i]);
            used[i] = true;
            backTracking(nums, used);
            path.pop();
            used[i] = false;

        }
    }
}
console.log(permute([1,2,3]));

输出题

异步

js
new Promise((resolve, reject) => {
  console.log(1);

  resolve(true);

  console.log(2);

  throw new Error('err');

  reject(false);

  console.log(3);
})
  .catch(ex => console.log(ex))
  .then(res => console.log(res));

打印:1, 2, true

this

js
var length = 10;
function fn() {
  return this.length + 1;
}
var obj = {
  length: 5,
  test1: function () {
    return fn();
  },
};
obj.test2 = fn;
//下面代码输出是什么
console.log(obj.test1()); // 11
console.log(fn() === obj.test2()); // false

闭包

js
var a = 0;
var b = 0;
var c = 0;

function fn(a) {
    console.log(a++, c); 
    function fn2(b) {
        console.log(a, b, c); 
    }
    var c = 4; 
    fn = fn2;  
}

console.log(fn(1)); // 1 4
console.log(fn(2)); // 2 2 4

手写布局

三栏布局,右侧自适应

html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Flex 三栏布局</title>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        .container {
            display: flex;
            height: 100vh;
        }
        .left, .middle {
            width: 200px; /* 左侧和中间栏固定宽度 */
            background-color: lightblue;
            padding: 20px;
        }
        .right {
            flex-grow: 1; /* 右侧栏自适应宽度 */
            background-color: lightgreen;
            padding: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="left">左侧栏</div>
        <div class="middle">中间栏</div>
        <div class="right">右侧栏自适应宽度</div>
    </div>
</body>
</html>

过渡消失

js
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点击左侧栏使其消失</title>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        .container {
            display: flex;
            height: 100vh;
        }
        .left {
            width: 200px;
            background-color: lightblue;
            padding: 20px;
            transition: width 0.5s ease, opacity 0.5s ease; /* 添加过渡效果 */
        }
        .middle, .right {
            flex: 1;
            background-color: lightgreen;
            padding: 20px;
        }
        .hidden {
            width: 0;
            opacity: 0; /* 渐变透明度 */
            padding: 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="left" id="left-bar">点击我隐藏左侧栏</div>
        <div class="middle" id="mid-bar">中间栏</div>
        <div class="right">右侧栏</div>
    </div>

    <script>
        // 获取左侧栏元素
        const leftBar = document.getElementById('left-bar');
        const midBar = document.getElementById('mid-bar');

        // 点击事件监听器
        leftBar.addEventListener('click', function() {
            leftBar.classList.add('hidden'); // 添加 'hidden' 类,触发过渡效果
        });

        // 点击中间栏恢复左侧
        midBar.addEventListener('click', ()=>{
          leftBar.classList.remove('hidden');
        })
    </script>
</body>
</html>

技巧

函数柯里化 - 闭包

题目:

js
// 写一个函数,满足:
function add(a , b , c ){
	return a+b+c;
}
let add1 = curry(add, 1);
console.log(add1(2,3)); // 6

console.log(curry(add)(0)(5)(6)); // 11

实现:

js
function curry(func, ...nums){
  if(nums.length >= func.length){
    return func(...nums);
  } else {
    return (...newNums) => {
      return curry(func, ...nums, ...newNums); 
    }
  }
}

防抖与节流 - 闭包

防抖:n 秒后再执行某一事件,若在 n 秒内被再次触发,则重新计时

js
function debounce(func, wait) {
  let timeout;
  return () => {
    let context = this;
    let args = arguments;

    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    },wait);
  }
}

function print() {
  console.log('mouse move!');
}

const debouncedPrint = debounce(print, 1000);
document.addEventListener('mousemove', debouncedPrint);

节流:n 秒内只运行一次,若在 n 秒内重复触发,则只有一次生效

js
function throttle(func, wait){
  let preTime = 0;
  return () => {
    let now = Date.now();
    let context = this;
    let args = arguments;
    if(now - preTime > wait){
      func.apply(context, args);
      preTime = now;
    }
  }
}

function print() {
  console.log('mouse move!');
}

const throttledPrint = throttle(print, 1000);
document.addEventListener('mousemove', throttledPrint);

拍平数组 - 递归

js
let nums = [1, 2, [3, 4, [6, 7]], 5];

function myFlat(arr, depth = 1) {
  // 递归结束条件
  if (depth < 1) {
    return arr;
  }
  let res = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      res = res.concat(myFlat(item, depth - 1)); 
    } else {
      res.push(item);
    }
  });
  return res;
}

console.log(myFlat(nums, 2)); // [1, 2, 3, 4, 6, 7, 5]

判断是否有重复元素

js
Array.prototype.hasDuplicates = function () {
  // this 指向条用这个方法的数组
  let theSet = new Set();
  for(let element of this){ // 不能用 forEach,因为 forEach 不受 return 影响
    if (theSet.has(element)) {
      return true;
    } else {
      theSet.add(element);
    }
  }
  return false;
};

console.log([1, 2, 2].hasDuplicates()); // true

解析 URL

js
let url = "http://www.xxx.com?a=1&b=2&c=3"
js
function parse(url) {
  let obj = {};
  url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .map(item => {
      let [key, val] = item.split("=");
      obj[key] = val;
    });
  return obj;
}

let result = parse(url);
console.log(result);
// { a: '1', b: '2', c: '3' }

手写深拷贝

简单版本:

js
function deepCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;
  // 根据 object 的类型判断是新建一个数组还是对象
  let newObj = Array.isArray(object) ? [] : {};
  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObj[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }
  return newObj;
}

手写 Math.pow()

js
function customPow(base, exponent) {
    let result = 1;
    if (exponent === 0) {
        return result;
    }
    const positiveExponent = Math.abs(exponent);
    for (let i = 0; i < positiveExponent; i++) {
        result *= base;
    }
    return exponent < 0 ? 1 / result : result;
}

优化:快速幂算法,指数分治法

js
function customPow(base, exponent) {
  if (exponent === 0) return 1; // 任何数的 0 次方是 1
  if (exponent < 0) return 1 / customPow(base, -exponent); // 处理负指数

  // 快速幂算法
  let half = customPow(base, Math.floor(exponent / 2));
  if (exponent % 2 === 0) {
      return half * half; // 如果指数是偶数
  } else {
      return half * half * base; // 如果指数是奇数
  }
}

实现 sleep(n)

js
function sleep(sec) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`I sleep for ${sec} seconds💤`);
      resolve();
    }, sec * 1000);
  });
}

sleep(0.5).then(() => sleep(1));

手写 Promise.all()

js
Promise.all = function (promises) {
    let results = [];
    let length = promises.length;
    let promiseCount = 0;
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(res => {
                results[i] = res;
                promiseCount++;
                if (promiseCount === length) {
                    resolve(results);
                }
            }, err => {
                reject(err);
            })
        }
    })
}

// example
let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
let promise3 = Promise.resolve(3);

promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // [1, 2, 3]
  })
  .catch(reason => {
    console.log(reason);
  });

复杂版本:

js
function customPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completedPromises = 0;

    // 如果传入的不是数组,抛出错误
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }

    // 如果传入的是空数组,立即resolve空数组
    if (promises.length === 0) {
      return resolve([]);
    }

    promises.forEach((promise, index) => {
      // 确保每个元素都是一个 Promise
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;
          completedPromises++;

          // 当所有 Promise 都完成时,resolve 最终的结果数组
          if (completedPromises === promises.length) {
            resolve(results);
          }
        })
        .catch(error => {
          // 一旦有一个 Promise 被 reject,立即 reject 整个 customPromiseAll
          reject(error);
        });
    });
  });
}

异步任务控制并发

js
class AsyncPool {
  constructor(maxConcurrency) {
    this.maxConcurrency = maxConcurrency;  // 最大并发数
    this.currentCount = 0;                // 当前正在执行的任务数量
    this.taskQueue = [];                  // 任务队列
  }

  // 执行一个任务
  async run(task, ...args) {
    if (this.currentCount >= this.maxConcurrency) {
      // 如果当前执行任务数达到最大并发数,则等待
      await new Promise(resolve => this.taskQueue.push(resolve));
    }

    this.currentCount++;
    try {
      // 执行实际任务
      return await task(...args);
    } finally {
      this.currentCount--;
      if (this.taskQueue.length > 0) {
        // 如果队列中有等待的任务,释放一个
        const next = this.taskQueue.shift();
        next();
      }
    }
  }
}

手写 instanceOf

js
function myInstanceof(left, right) {
    // 获取右侧构造函数的原型对象
    let prototype = right.prototype;
    // 获取左侧对象的原型
    left = left.__proto__;
    
    // 不断沿着原型链向上查找
    while (true) {
        // 到达原型链的顶端,仍未找到
        if (left === null) return false;
        // 找到相同的原型对象
        if (left === prototype) return true;
        // 继续向上查找
        left = left.__proto__;
    }
}

// 示例
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Array)); // false

hardMan

js
class HardMan {
    constructor(name) {
        this.queue = []; // 初始化一个队列
        console.log(`My name is ${name}`);
        Promise.resolve().then(() => {
            let sequence = Promise.resolve();
            this.queue.forEach((item) => {
                sequence = sequence.then(item);
            });
        })
    }

    study() {
        this.queue.push(() => {
            console.log("I am studying");
        });
        return this; // 返回实例以支持链式调用
    }

    holdOn(sec) {
        return () => {
            return new Promise((resolve) => { 
            // 注意箭头函数中的 return
                setTimeout(() => {
                    console.log(`rest for ${sec} seconds`);
                    resolve(); // 
                }, sec * 1000);
            });
        }
    }

    rest(sec) {
        this.queue.push(this.holdOn(sec));
        return this; // 返回实例以支持链式调用
    }

    restFirst(sec) {
        this.queue.unshift(this.holdOn(sec));
        return this; // 返回实例以支持链式调用
    }
}

// 测试代码
let jiaqi = new HardMan("jiaqi");
jiaqi
    .study()
    .rest(5)
    .study()
    .restFirst(2);

发布订阅模式

js
class Event {
	constructor() {
		// 存储事件的数据对象
		// 为了迅速查找,使用字典
		this.events = {};
	}
	// 绑定事件
	on(eventName, callBack) {
		if (!this.events[eventName]) {
			this.events[eventName] = [];
		}
		this.events[eventName].push(callBack);
	}

	// 派发事件:遍历事件列表,对每个事件执行相应的回调函数
	emit(eventName, ...args) {
		if (this.events[eventName]) {
			this.events[eventName].forEach((callBack) => callBack(...args)); 
		}
	}

	// 取消订阅事件,从事件列表中删除指定的回调函数
	off(eventName, callBack) {
		if (this.events[eventName]) {
			if (callBack) {
				// 如果指定了回调函数,就删除该函数
				this.events[eventName] = this.events[eventName].filter(
					(cb) => cb != callBack
				);
			} else {
				// 如果没指定回调函数,就删除所有绑定的函数
				this.events[eventName] = [];
			}
		}
	}

	// 为事件绑定一个只执行一次的回调函数
	once(eventName, callBack) {
		// 将回调函数与取消订阅事件封装起来,成为一个新的函数
		const func = () => {
			callBack();
			this.off(eventName, func);// 注意这里传入的参数是新定义的 func 而不是 callBack
		};
		this.on(eventName, func);
	}
}

function test() {
	console.log("in test");
}

const test2 = function () {
	console.log("in test2");
};

myEvent = new Event();

myEvent.on("test", test);
myEvent.once("test2", test2);
myEvent.emit("test2");

LRU

js
class Node {
    constructor(key = 0, value = 0) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.dummy = new Node();
        this.dummy.prev = this.dummy;
        this.dummy.next = this.dummy;
        this.keyToNode = new Map(); // 映射 key 与 Node 的 map
    }
    // 如果已经有这个 key 则更新 value
    put(key, value) {
        let node = this.getNode(key);
        if (node) {
            // 如果key存在,更新value
            node.value = value;
            return;
        }
        // 如果 key 不存在,新建键值对
        let newNode = new Node(key, value);
        this.keyToNode.set(key, newNode);
        // 将新 node 放到链表头部
        this.moveToHead(newNode);
        // 当超过 capacity 的时候,去掉最后一个
        if (this.keyToNode.size > this.capacity) {
            const lastNode = this.dummy.prev;
            this.keyToNode.delete(lastNode.key);
            this.removeNode(lastNode);
        }
    }
    get(key) {
        const node = this.getNode(key);
        return node ? node.value : -1;
    }
    // 获取对应 key 的 node
    getNode(key) {
        if (!this.keyToNode.has(key)) {
            return null;
        }
        const node = this.keyToNode.get(key);
        this.removeNode(node);
        this.moveToHead(node);
        return node;
    }
    // 将一个节点放到链表头部
    moveToHead(node) {
        node.prev = this.dummy;
        node.next = this.dummy.next;
        node.prev.next = node;
        node.next.prev = node;
    }
    // 删除一个节点
    removeNode(node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}

数组去重

一维数组:

js
let a = [1,2,2,3];
a = [...new Set(a)];
console.log(a); // [1,2,3]

二维数组:

js
let aa = [[1,2], [2,3], [1,2]];
let obj = {};
aa.forEach((item)=> obj[item]=item);
aa = Object.values(obj);
console.log(aa);

反转键值

js
function inverse(obj){
  var retobj = {};
  for(var key in obj){
    retobj[obj[key]] = key;
  }
  return retobj;
}

事件捕获和事件冒泡

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件冒泡与事件捕获</title>
    <style>
        .outer {
            width: 300px;
            height: 300px;
            background-color: lightblue;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .inner {
            width: 150px;
            height: 150px;
            background-color: lightcoral;
        }
    </style>
</head>
<body>

<div class="outer" id="outer">外层DIV
    <div class="inner" id="inner">内层DIV</div>
</div>

<script>
    // 事件捕获阶段
    document.getElementById('outer').addEventListener('click', function() {
        alert('外层DIV 捕获阶段');
    }, true); // 第三个参数 true 表示捕获阶段

    document.getElementById('inner').addEventListener('click', function() {
        alert('内层DIV 捕获阶段');
    }, true); // 第三个参数 true 表示捕获阶段

    // 事件冒泡阶段
    document.getElementById('outer').addEventListener('click', function() {
        alert('外层DIV 冒泡阶段');
    }, false); // 第三个参数 false 表示冒泡阶段

    document.getElementById('inner').addEventListener('click', function() {
        alert('内层DIV 冒泡阶段');
    }, false); // 第三个参数 false 表示冒泡阶段
</script>

</body>
</html>

图片懒加载

js
// 选择需要懒加载的图片元素
const images = document.querySelectorAll('img[data-src]');

// 创建一个Intersection Observer实例
const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 如果元素进入视口,则加载图片
      const img = entry.target;
      const src = img.dataset.src;
      img.src = src;
      img.onload = () => {
        // 加载完成后,停止观察该元素
        observer.unobserve(img);
      };
    }
  });
});

// 对每个图片元素开始观察
images.forEach(image => {
  observer.observe(image);
});