当前位置:首页 > 技术 > 正文内容

JavaScript算法实现与编程练习

访客 技术 2026年5月31日 1

排序算法实现

1. 冒泡排序

原理:依次比较相邻元素,每次排序确定一个最大元素的位置

时间复杂度:O(n²),空间复杂度:O(1)

外层循环:每轮确定一个最大元素,比较范围逐渐缩小

内层循环:从前往后比较相邻元素,交换位置


class DataList {
    bubbleSort() {
        const size = this.data.length;
        // 外层循环控制比较范围,每轮确定一个最大元素
        for (let boundary = size - 1; boundary > 0; boundary--) {
            // 内层循环进行相邻元素比较
            for (let index = 0; index < boundary; index++) {
                // 升序排列,如果前一个元素大于后一个元素,则交换
                if (this.data[index] > this.data[index + 1]) {
                    // 使用解构赋值实现元素交换
                    [this.data[index], this.data[index + 1]] = [this.data[index + 1], this.data[index]];
                }
            }
        }
    }
}

2. 选择排序

原理:每次从未排序部分选择最小元素,放到已排序部分的末尾

时间复杂度:O(n²),空间复杂度:O(1)

外层循环:遍历所有元素

内层循环:找到未排序部分的最小元素


class DataList {
    selectionSort() {
        const size = this.data.length;
        // 外层循环,逐个确定最小元素的位置
        for (let current = 0; current < size - 1; current++) {
            // 假设当前元素是最小的
            let minIndex = current;
            // 内层循环,在剩余元素中寻找最小值
            for (let compare = minIndex + 1; compare < size; compare++) {
                if (this.data[minIndex] > this.data[compare]) {
                    // 更新最小元素的索引
                    minIndex = compare;
                }
            }
            // 将找到的最小元素与当前位置交换
            [this.data[minIndex], this.data[current]] = [this.data[current], this.data[minIndex]];
        }
    }
}

3. 插入排序

原理:维护一个有序序列,将未排序元素插入到适当位置

时间复杂度:O(n²),空间复杂度:O(1)

外层循环:从第二个元素开始,依次处理每个元素

内层循环:将当前元素插入到已排序部分的适当位置


class DataList {
    insertSort() {
        const size = this.data.length;
        // 外层循环,从第二个元素开始,依次插入到已排序部分
        for (let i = 1; i < size; i++) {
            // 保存当前待插入元素
            const current = this.data[i];
            let j = i;
            // 将比当前元素大的元素向后移动
            while (j > 0 && this.data[j - 1] > current) {
                this.data[j] = this.data[j - 1];
                j--;
            }
            // 将当前元素插入到正确位置
            this.data[j] = current;
        }
    }
}

4. 希尔排序

原理:通过分组进行插入排序,逐渐减小分组间隔

时间复杂度:O(n^(1.3-2)),空间复杂度:O(1)

外层循环:控制分组间隔,逐渐减小

内层循环:在各分组内进行插入排序


class DataList {
    shellSort() {
        const size = this.data.length;
        // 初始化分组间隔
        let gap = Math.floor(size / 2);
        // 不断减小分组间隔,直到间隔为1
        while (gap >= 1) {
            // 对每个分组进行插入排序
            for (let i = gap; i < size; i++) {
                const current = this.data[i];
                let j = i;
                // 在分组内进行插入排序
                while (j >= gap && this.data[j - gap] > current) {
                    this.data[j] = this.data[j - gap];
                    j -= gap;
                }
                // 插入到正确位置
                this.data[j] = current;
            }
            // 减小分组间隔
            gap = Math.floor(gap / 2);
        }
    }
}

5. 快速排序

原理:使用分治策略,通过基准值将数组分为两部分,递归排序

时间复杂度:平均O(n log n),最坏O(n²),空间复杂度:O(log n)

双指针法:分别从两端向中间遍历,交换不符合条件的元素


function quickSort(arr, left = 0, right = arr.length - 1) {
    // 递归终止条件
    if (left < right) {
        // 选择基准值
        const pivot = arr[left];
        let i = left;
        let j = right;
        
        // 双指针遍历
        while (i < j) {
            // 从右向左寻找比基准小的元素
            while (i < j && arr[j] > pivot) {
                j--;
            }
            // 将找到的元素交换到左侧
            arr[i] = arr[j];
            
            // 从左向右寻找比基准大的元素
            while (i < j && arr[i] < pivot) {
                i++;
            }
            // 将找到的元素交换到右侧
            arr[j] = arr[i];
        }
        
        // 将基准值放到正确位置
        arr[i] = pivot;
        
        // 递归排序基准值左侧和右侧
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    }
    
    return arr;
}

JavaScript手写实现

1. Object.create 实现

创建一个新对象,其原型指向指定的对象


function create(proto) {
    // 创建一个构造函数
    function TempConstructor() {}
    // 设置构造函数的原型
    TempConstructor.prototype = proto;
    // 返回构造函数的实例
    return new TempConstructor();
}

// 测试用例
const nullObject = create(null);
console.log(nullObject);

2. instanceof 实现

判断一个对象是否属于某个构造函数的实例


function myInstanceof(obj, constructor) {
    // 获取对象的原型
    let proto = Object.getPrototypeOf(obj);
    // 获取构造函数的原型
    const prototype = constructor.prototype;
    
    // 向上遍历原型链
    while (proto !== null) {
        if (proto === prototype) {
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
    
    return false;
}

// 测试用例
const array = [1, 2, 3];
console.log(myInstanceof(array, Array)); // true

3. new 操作符实现

模拟 new 操作符的行为,创建实例并初始化


function newOperator(constructor, ...args) {
    // 创建一个新对象
    const obj = {};
    // 设置对象的原型
    Object.setPrototypeOf(obj, constructor.prototype);
    // 执行构造函数,绑定 this
    const result = constructor.apply(obj, args);
    // 如果构造函数返回对象,则返回该对象,否则返回新创建的对象
    return (result && typeof result === 'object') ? result : obj;
}

// 测试用例
function Person(name, age) {
    this.name = name;
    this.age = age;
    return { name: '张三', age: 18 };
}

const person = newOperator(Person, 'Hugh', 24);
console.log(person);

4. Promise.all 实现

接收多个 Promise 实例,返回一个新的 Promise,所有 Promise 都成功时才成功


function all(promises) {
    return new Promise((resolve, reject) => {
        // 参数检查
        if (!Array.isArray(promises)) {
            throw new Error('参数必须是一个数组');
        }
        
        const results = [];
        const count = promises.length;
        let completed = 0;
        
        // 遍历所有 Promise
        for (let i = 0; i < count; i++) {
            // 处理每个 Promise
            Promise.resolve(promises[i]).then(
                // 成功回调
                value => {
                    completed++;
                    results[i] = value;
                    // 所有 Promise 都完成时, resolve 结果数组
                    if (completed === count) {
                        resolve(results);
                    }
                },
                // 失败回调
                error => {
                    reject(error);
                }
            );
        }
    });
}

// 测试用例
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 2000));

all([p1, p2]).then(results => {
    console.log(results); // [1, 2]
}).catch(error => {
    console.error(error);
});

5. Promise.race 实现

接收多个 Promise 实例,返回一个新的 Promise,最先完成的 Promise 的结果作为结果


Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        // 遍历所有 Promise
        for (let i = 0; i < promises.length; i++) {
            // 处理每个 Promise,一旦有完成就 resolve 或 reject
            promises[i].then(resolve, reject);
        }
    });
};

// 测试用例
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 2000));

Promise.race([p1, p2]).then(result => {
    console.log(result); // 1
}).catch(error => {
    console.error(error);
});

6. 防抖函数实现

函数在触发后等待一段时间再执行,如果在等待时间内再次触发,则重新计时


function debounce(func, delay) {
    let timer = null;
    
    return function(...args) {
        const context = this;
        
        // 清除之前的定时器
        if (timer) {
            clearTimeout(timer);
        }
        
        // 设置新的定时器
        timer = setTimeout(() => {
            func.apply(context, args);
            timer = null;
        }, delay);
    };
}

7. 节流函数实现

函数在触发后立即执行,并在指定时间内不再执行,直到时间结束


// 使用时间戳实现,会立即执行
function throttle(func, limit) {
    let lastTime = 0;
    
    return function(...args) {
        const context = this;
        const now = Date.now();
        
        if (now - lastTime >= limit) {
            func.apply(context, args);
            lastTime = now;
        }
    };
}

// 使用定时器实现,不会立即执行,但会在最后一次触发后执行
function throttleWithTimer(func, limit) {
    let timer = null;
    
    return function(...args) {
        const context = this;
        
        if (!timer) {
            timer = setTimeout(() => {
                func.apply(context, args);
                timer = null;
            }, limit);
        }
    };
}

8. 类型判断函数

准确判断 JavaScript 数据类型


function getType(value) {
    // 处理 null 值
    if (value === null) {
        return 'null';
    }
    
    // 处理基本类型
    if (typeof value !== 'object') {
        return typeof value;
    }
    
    // 处理对象类型
    const typeString = Object.prototype.toString.call(value);
    // 提取类型名称
    return typeString.slice(8, -1).toLowerCase();
}

// 测试用例
console.log(getType(null)); // 'null'
console.log(getType(undefined)); // 'undefined'
console.log(getType(123)); // 'number'
console.log(getType('abc')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType({})); // 'object'
console.log(getType([])); // 'array'
console.log(getType(function() {})); // 'function'

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。