常见手撕
基础题
大数相加
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 != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
思路:
- 首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sum 判断是否满足为 0,满足则添加进结果集
- 如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环
- 如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
- 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
- 当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
- 时间复杂度: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);
});