手写 字符串格式化
暂时没遇到具体要求
这个
formatString
函数接受三个参数:template
是包含占位符的字符串模板,data
是一个对象,包含要替换占位符的数据,options
是一个包含可选参数的对象,包括delimiter
(占位符的分隔符)、defaultValue
(默认值)和formatSpec
(格式化规范)。
formatSpec
是一个对象,其中键是占位符的名称,值是格式化规范。支持的规范包括'uppercase'
(转换为大写)、'lowercase'
(转换为小写)以及自定义的函数。这个函数的优点在于它具有灵活的定制性,你可以根据需要添加更多的格式化规范,也可以轻松地自定义默认值和占位符分隔符。
function formatString(template, data, options = {}) {
const { delimiter = ['{{', '}}'], defaultValue = '', formatSpec = {} } = options;
const [startDelimiter, endDelimiter] = delimiter;
return template.replace(new RegExp(`${startDelimiter}(.*?)${endDelimiter}`, 'g'), (match, key) => {
let value = data[key.trim()];
const spec = formatSpec[key.trim()];
if (value === undefined || value === null) {
value = defaultValue;
}
if (spec) {
value = applyFormatSpec(value, spec);
}
return value;
});
}
function applyFormatSpec(value, spec) {
if (typeof spec === 'function') {
return spec(value);
} else if (typeof spec === 'string') {
switch (spec) {
case 'uppercase':
return String(value).toUpperCase();
case 'lowercase':
return String(value).toLowerCase();
// 添加更多格式化规范
default:
return value;
}
} else {
return value;
}
}
// 示例用法:
const template = "Hello, {{name}}! You have {{count}} new messages.";
const data = { name: "Alice", count: 3 };
const result = formatString(template, data, {
delimiter: ['{{', '}}'], // 自定义占位符分隔符
defaultValue: 'N/A', // 默认值
formatSpec: {
name: 'uppercase', // 字符串转换为大写
count: count => count < 10 ? `0${count}` : count // 添加前导零
}
});
console.log(result);
手写 字符串转对象
function stringToObject(str) {
var obj = {};
var pairs = str.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
var key = decodeURIComponent(pair[0]);
var value = decodeURIComponent(pair[1] || '');
obj[key] = value;
}
return obj;
}
// 示例用法:
var queryString = "key1=value1&key2=value2&key3=value3";
var obj = stringToObject(queryString);
console.log(obj);
手写 获取页面节点数量
1. 使用 document.getElementsByTagName('*')
:
function countNodes() {
return document.getElementsByTagName('*').length;
}
2. 使用 document.querySelectorAll('*')
:
function countNodes() {
return document.querySelectorAll('*').length;
}
3. 递归遍历 DOM 树:
function countNodes(node) {
if (!node) return 0;
var count = 1;
for (var i = 0; i < node.childNodes.length; i++) {
count += countNodes(node.childNodes[i]);
}
return count;
}
// 调用方式:
var totalNodes = countNodes(document.documentElement); // 传入根节点
4. 使用深度优先搜索遍历 DOM 树:
function countNodes(node) {
var count = 1; // 节点本身
if (node.hasChildNodes()) {
var children = node.childNodes;
for (var i = 0; i < children.length; i++) {
count += countNodes(children[i]);
}
}
return count;
}
// 调用方式:
var totalNodes = countNodes(document.documentElement); // 传入根节点
5. 使用广度优先搜索遍历 DOM 树:
function countNodes(node) {
var queue = [node];
var count = 0;
while (queue.length > 0) {
var current = queue.shift();
count++;
for (var i = 0; i < current.children.length; i++) {
queue.push(current.children[i]);
}
}
return count;
}
// 调用方式:
var totalNodes = countNodes(document.documentElement); // 传入根节点
手写 tooltip组件 html + css
这段代码创建了一个简单的 Tooltip 组件。当用户将鼠标悬停在按钮上时,工具提示会显示出来。这个示例中,我们使用了
position: absolute;
来使工具提示相对于父元素进行定位,使用visibility
和opacity
来控制工具提示的可见性和透明度。CSS 中的::after
伪元素用于绘制工具提示的三角形箭头。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tooltip Component</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="tooltip-container">
<button class="tooltip-trigger" aria-describedby="tooltip1">Hover over me</button>
<div id="tooltip1" class="tooltip" role="tooltip" aria-hidden="true">
This is a tooltip!
</div>
</div>
</body>
</html>
.tooltip-container {
position: relative;
display: inline-block;
}
.tooltip-trigger {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
.tooltip {
position: absolute;
background-color: #333;
color: #fff;
padding: 5px 10px;
border-radius: 5px;
width: 150px;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s linear;
z-index: 1;
}
.tooltip-container:hover .tooltip {
visibility: visible;
opacity: 1;
}
.tooltip::after {
content: '';
position: absolute;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #333 transparent transparent transparent;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}
手写 全排列
这个
permute
函数接受一个数组作为参数,并返回一个包含数组所有可能排列的数组。在
generatePermutations
函数中,它使用递归方式生成排列。每次迭代中,它从剩余的元素中选择一个,将其添加到当前排列中,并继续递归地生成剩余元素的排列,直到没有剩余元素为止。需要注意的是,这个方法可能不适用于大型数组,因为它的时间复杂度是阶乘级别的。
function permute(arr) {
let result = [];
function generatePermutations(current, remaining) {
if (remaining.length === 0) {
result.push(current);
return;
}
for (let i = 0; i < remaining.length; i++) {
let next = current.concat(remaining[i]);
let remainingArray = remaining.slice(0, i).concat(remaining.slice(i + 1));
generatePermutations(next, remainingArray);
}
}
generatePermutations([], arr);
return result;
}
// 示例用法:
let array = [1, 2, 3];
let permutations = permute(array);
console.log(permutations);
手写 归并排序
这段代码首先定义了
mergeSort
函数,它接受一个数组作为输入,并返回排序后的数组。如果输入数组的长度小于等于 1,它会直接返回原数组。否则,它将数组分为两半,然后递归地对左半部分和右半部分进行归并排序,最后将两个有序的子数组合并起来。
merge
函数用于合并两个有序数组。它从左到右逐个比较两个数组的元素,并将较小的元素依次放入结果数组中。最后,如果某个数组还有剩余元素,它将剩余的元素全部追加到结果数组的末尾。归并排序的时间复杂度为 O(n log n),其中 n 为数组的长度。
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
// 将剩余的元素追加到结果数组中
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}
// 示例用法:
const arr = [3, 1, 4, 1, 5, 9, 2, 6];
const sortedArray = mergeSort(arr);
console.log(sortedArray);
手写 线程池
实现一个简单的线程池需要考虑以下几个方面:
- 管理任务队列:线程池需要一个任务队列,用于存放需要执行的任务。
- 线程管理:线程池需要管理一定数量的线程,以便执行任务。
- 任务执行:线程池需要能够将任务分配给空闲的线程执行。
这个简单的线程池由一个
ThreadPool
类和一个WorkerThread
类组成。
ThreadPool
类负责管理任务队列和线程池的工作线程。它有一个tasks
属性存储任务队列,一个workers
属性存储空闲的工作线程。createWorker
方法用于创建工作线程,addTask
方法用于将任务加入队列,并调用dispatchTask
方法分配任务给空闲的工作线程。dispatchTask
方法从任务队列中取出任务并将其分配给一个空闲的工作线程执行。
WorkerThread
类表示线程池中的工作线程。它的execute
方法用于执行任务,执行完毕后通过回调通知线程池任务已完成。这个实现比较简单,仅作为概念性示例,可能不适用于生产环境。在实际应用中,需要考虑更多的因素,比如错误处理、线程池的大小调整等。
class ThreadPool {
constructor(size) {
this.size = size;
this.tasks = [];
this.workers = [];
// 创建指定数量的线程
for (let i = 0; i < this.size; i++) {
this.createWorker();
}
}
createWorker() {
const worker = new WorkerThread(this);
this.workers.push(worker);
}
addTask(task) {
if (this.workers.length === 0) {
throw new Error('No workers available');
}
if (typeof task !== 'function') {
throw new Error('Task must be a function');
}
// 将任务加入队列
this.tasks.push(task);
this.dispatchTask();
}
dispatchTask() {
if (this.tasks.length > 0 && this.workers.length > 0) {
const task = this.tasks.shift();
const worker = this.workers.pop();
worker.execute(task);
}
}
taskCompleted(worker) {
// 将完成任务的线程放回线程池
this.workers.push(worker);
this.dispatchTask();
}
}
class WorkerThread {
constructor(pool) {
this.pool = pool;
}
execute(task) {
setTimeout(() => {
task();
this.pool.taskCompleted(this);
}, 0);
}
}
// 使用示例:
const pool = new ThreadPool(5); // 创建线程池,指定线程数量为5
function taskFunction() {
console.log('Task executed');
}
// 添加任务到线程池
pool.addTask(taskFunction);
手写 sleep
在 JavaScript 中,由于语言的异步特性,没有内置的
sleep
函数来暂停代码的执行。但可以利用setTimeout
或Promise
来模拟实现一个sleep
函数。这个
sleep
函数返回一个 Promise,它会在指定的时间后调用resolve
,从而实现暂停的效果。使用async/await
可以使调用代码更加清晰。
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 使用示例:
async function example() {
console.log('Before sleep');
await sleep(2000); // 暂停 2000 毫秒(即2秒)
console.log('After sleep');
}
example();
如果你想要使用回调函数而不是 async/await,也可以这样实现:
这个
sleep
函数接受一个毫秒数和一个回调函数作为参数,然后在指定的时间后调用回调函数。
function sleep(ms, callback) {
setTimeout(callback, ms);
}
// 使用示例:
console.log('Before sleep');
sleep(2000, function() {
console.log('After sleep');
});
手写代码,实现图片加载失败后,显示另一个地址内容
在这个示例中,当原始图片加载失败时,
replaceImage
函数会被调用,它会将<img>
元素的src
属性替换为另一个地址(这里是 "fallback-image.jpg"),以显示另一张图片作为备用。你也可以将
replaceImage
函数改写为加载其他类型的内容,比如文本、动态内容等,方法类似。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Loading Error Handling</title>
</head>
<body>
<img src="image-that-may-fail.jpg" onerror="replaceImage(this)" alt="Fallback Image">
<script>
function replaceImage(img) {
img.src = "fallback-image.jpg";
img.alt = "Fallback Image";
}
</script>
</body>
</html>
手写一个 Promise 的并发控制
这个
PromisePool
类可以限制同时执行的 Promise 数量。当添加新的 Promise 时,它会将 Promise 及其对应的resolve
和reject
函数存储在pendingPromises
数组中。然后,通过runPending
方法来执行等待的 Promise,同时限制执行的数量不超过concurrency
。在示例中,我们创建了一个
PromisePool
实例,最多同时执行两个 Promise。然后我们定义了一个task
函数,它会返回一个 Promise,模拟一个耗时任务。最后,我们循环添加了 5 个任务到PromisePool
中,并打印任务执行结果。
class PromisePool {
constructor(concurrency) {
this.concurrency = concurrency; // 同时执行的 Promise 数量
this.pendingPromises = [];
this.activeCount = 0;
}
add(promiseGenerator) {
return new Promise((resolve, reject) => {
this.pendingPromises.push({ promiseGenerator, resolve, reject });
this.runPending();
});
}
runPending() {
while (this.pendingPromises.length > 0 && this.activeCount < this.concurrency) {
const { promiseGenerator, resolve, reject } = this.pendingPromises.shift();
this.activeCount++;
const promise = promiseGenerator();
promise.then((result) => {
resolve(result);
}).catch((error) => {
reject(error);
}).finally(() => {
this.activeCount--;
this.runPending();
});
}
}
}
// 示例用法:
const promisePool = new PromisePool(2); // 最多同时执行两个 Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function task(i) {
return function() {
console.log(`Task ${i} started`);
return delay(1000).then(() => {
console.log(`Task ${i} completed`);
return i;
});
};
}
for (let i = 1; i <= 5; i++) {
promisePool.add(task(i)).then(result => {
console.log(`Result of task ${result}`);
}).catch(error => {
console.error(`Task failed: ${error}`);
});
}
用Promise实现图片的异步加载
在这个示例中,
loadImage
函数接受一个图片的 URL 作为参数,并返回一个 Promise。在 Promise 的执行器函数中,创建一个新的Image
对象,并设置其onload
和onerror
事件处理程序。当图片加载成功时,调用resolve
函数并将图片元素传递给它;当加载失败时,调用reject
函数并传递一个 Error 对象。然后,我们可以通过
.then()
方法来处理图片加载成功的情况,以及通过.catch()
方法来处理加载失败的情况。
function loadImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Failed to load image'));
};
image.src = url;
});
}
// 示例用法:
const imageUrl = 'https://example.com/image.jpg';
loadImage(imageUrl)
.then(image => {
// 图片加载成功后的处理
console.log('Image loaded successfully:', image);
document.body.appendChild(image); // 将图片添加到页面中
})
.catch(error => {
// 图片加载失败后的处理
console.error('Error loading image:', error);
});
算法:求n个2-32的不重复的随机数算法:一个数组打乱顺序,要求不在原本的位置 时间复杂度O(N)左侧固定,右侧自适应
这个
generateRandomNonRepeatingNumbers
函数接受三个参数:n
表示需要生成的随机数的个数,min
和max
分别表示随机数的最小值和最大值。函数首先创建一个包含了
min
到max
之间所有整数的数组arr
。然后,它使用 Fisher-Yates 洗牌算法的变种来生成不重复的随机数。在每次迭代中,它从arr
数组中随机选择一个元素,将其添加到结果数组result
中,并将该元素从arr
中移除。这样,随机数就不会在其原始位置上。最后,返回结果数组。这种实现的时间复杂度为 O(n)。
function generateRandomNonRepeatingNumbers(n, min, max) {
// 初始化数组,包含 n 个数字,从 min 到 max
const arr = Array.from({length: max - min + 1}, (_, index) => min + index);
const result = new Array(n);
// Fisher-Yates Shuffle Algorithm 的变种
for (let i = 0; i < n; i++) {
const randomIndex = Math.floor(Math.random() * arr.length);
result[i] = arr[randomIndex];
arr[randomIndex] = arr[arr.length - 1];
arr.pop();
}
return result;
}
// 示例用法:
const n = 10; // 需要生成的随机数的个数
const min = 2; // 随机数的最小值
const max = 32; // 随机数的最大值
const randomNumbers = generateRandomNonRepeatingNumbers(n, min, max);
console.log(randomNumbers);
leetcode 原题编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行如下三种操作:
(1) 插入一个字符;
(2) 删除一个字符;(3) 替换一个字符;
在这个问题中,我们可以定义一个二维数组
dp
,其中dp[i][j]
表示将word1
的前i
个字符转换为word2
的前j
个字符所需要的最少操作数。根据题目要求,我们可以将转换过程分为三种情况:
插入一个字符:
dp[i][j] = dp[i][j - 1] + 1
,即在word1
的前i
个字符后插入一个字符,使其与word2
的前j
个字符相同。删除一个字符:
dp[i][j] = dp[i - 1][j] + 1
,即删除word1
的第i
个字符,使其与word2
的前j
个字符相同。替换一个字符:
dp[i][j] = dp[i - 1][j - 1] + 1
,即将word1
的第i
个字符替换为word2
的第j
个字符。基于以上分析,我们可以得到状态转移方程:
dp[i][j] = min(dp[i][j - 1] + 1, dp[i - 1][j] + 1, dp[i - 1][j - 1] + (word1[i - 1] === word2[j - 1] ? 0 : 1))
function minDistance(word1, word2) {
const m = word1.length;
const n = word2.length;
const dp = Array.from({ length: m + 1 }, () => Array.from({ length: n + 1 }, () => 0));
// 初始化第一行和第一列
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i][j - 1] + 1, dp[i - 1][j] + 1, dp[i - 1][j - 1] + 1);
}
}
}
return dp[m][n];
}
// 示例用法:
const word1 = "horse";
const word2 = "ros";
console.log(minDistance(word1, word2)); // 输出 3