前端知识学习24.3.17

手写 字符串格式化

暂时没遇到具体要求

这个 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; 来使工具提示相对于父元素进行定位,使用 visibilityopacity 来控制工具提示的可见性和透明度。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);

手写 线程池

实现一个简单的线程池需要考虑以下几个方面:

  1. 管理任务队列:线程池需要一个任务队列,用于存放需要执行的任务。
  2. 线程管理:线程池需要管理一定数量的线程,以便执行任务。
  3. 任务执行:线程池需要能够将任务分配给空闲的线程执行。

这个简单的线程池由一个 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 函数来暂停代码的执行。但可以利用 setTimeoutPromise 来模拟实现一个 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 及其对应的 resolvereject 函数存储在 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 对象,并设置其 onloadonerror 事件处理程序。当图片加载成功时,调用 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 表示需要生成的随机数的个数,minmax 分别表示随机数的最小值和最大值。

函数首先创建一个包含了 minmax 之间所有整数的数组 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 个字符所需要的最少操作数。

根据题目要求,我们可以将转换过程分为三种情况:

  1. 插入一个字符:dp[i][j] = dp[i][j - 1] + 1,即在 word1 的前 i 个字符后插入一个字符,使其与 word2 的前 j 个字符相同。

  2. 删除一个字符:dp[i][j] = dp[i - 1][j] + 1,即删除 word1 的第 i 个字符,使其与 word2 的前 j 个字符相同。

  3. 替换一个字符: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

 

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿online

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值