# 手写常见的JS面试题 21-30

# 21、写版本号排序的方法

题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。 现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

arr.sort((a, b) => {
    let i = 0;
    const arr1 = a.split(".");
    const arr2 = b.split(".");

    while (true) {
        const s1 = arr1[i];
        const s2 = arr2[i];
        i++;
        if (s1 === undefined || s2 === undefined) {
            return arr2.length - arr1.length;
        }

        if (s1 === s2) continue;

        return s2 - s1;
    }
});

// 简洁
arr.sort().reverse();
console.log(arr);

# 22、LRU算法

get、如果存在缓存中,则获取密钥的值,否则返回负一 put、如果密钥已经存在,则变更其数据值,如果不存在,则插入。当缓存达到上线,它应该在写入新数据源

class LRUcache {
    constructor(capacity) {
        this.cache = new Map();
        this.capacity = capacity;
    }

    get(key) {
        if (this.cache.has(key)) {
            const value = this.cache.get(key);
            this.cache.delete(key);
            this.cache.set(key, value);
            return value;
        } else {
            return -1;
        }
    }

    put(key, value) {
        // key存在,仅修改值
        if (this.secretKey.has(key)) {
            this.secretKey.delete(key);
            this.secretKey.set(key, value);
        }
        // key不存在,cache未满
        else if (this.secretKey.size < this.capacity) {
            this.secretKey.set(key, value);
        }
        // 添加新key,删除旧key
        else {
            this.secretKey.set(key, value);
            // 删除map的第一个元素,即为最长未使用的 借用了map存储方式队列的方式。
            this.secretKey.delete(this.secretKey.keys().next().value);
        }
    }
}

# 23、Promise 以及相关方法的实现

class Mypromise {
    constructor(fn) {
        // 表示状态
        this.state = "pending";
        // 表示then注册的成功函数
        this.successFun = [];
        // 表示then注册的失败函数
        this.failFun = [];

        let resolve = (val) => {
            // 保持状态改变不可变(resolve和reject只准触发一种)
            if (this.state !== "pending") return;

            // 成功触发时机  改变状态 同时执行在then注册的回调事件
            this.state = "success";
            // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
            setTimeout(() => {
                // 执行当前事件里面所有的注册函数
                this.successFun.forEach((item) => item.call(this, val));
            });
        };

        let reject = (err) => {
            if (this.state !== "pending") return;
            // 失败触发时机  改变状态 同时执行在then注册的回调事件
            this.state = "fail";
            // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
            setTimeout(() => {
                this.failFun.forEach((item) => item.call(this, err));
            });
        };
        // 调用函数
        try {
            fn(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    // 实例方法 then

    then(resolveCallback, rejectCallback) {
        // 判断回调是否是函数
        resolveCallback =
            typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
        rejectCallback =
            typeof rejectCallback !== "function"
                ? (err) => {
                    throw err;
                }
                : rejectCallback;
        // 为了保持链式调用  继续返回promise
        return new Mypromise((resolve, reject) => {
            // 将回调注册到successFun事件集合里面去
            this.successFun.push((val) => {
                try {
                    //    执行回调函数
                    let x = resolveCallback(val);
                    //(最难的一点)
                    // 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用  如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
                    x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
                } catch (error) {
                    reject(error);
                }
            });

            this.failFun.push((val) => {
                try {
                    //    执行回调函数
                    let x = rejectCallback(val);
                    x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
                } catch (error) {
                    reject(error);
                }
            });
        });
    }

    //静态方法
    static all(promiseArr) {
        let result = [];
        //声明一个计数器 每一个promise返回就加一
        let count = 0;
        return new Mypromise((resolve, reject) => {
            for (let i = 0; i < promiseArr.length; i++) {
                //这里用 Promise.resolve包装一下 防止不是Promise类型传进来
                Promise.resolve(promiseArr[i]).then(
                    (res) => {
                        //这里不能直接push数组  因为要控制顺序一一对应(感谢评论区指正)
                        result[i] = res;
                        count++;
                        //只有全部的promise执行成功之后才resolve出去
                        if (count === promiseArr.length) {
                            resolve(result);
                        }
                    },
                    (err) => {
                        reject(err);
                    }
                );
            }
        });
    }

    //静态方法
    static race(promiseArr) {
        return new Mypromise((resolve, reject) => {
            for (let i = 0; i < promiseArr.length; i++) {
                Promise.resolve(promiseArr[i]).then(
                    (res) => {
                        //promise数组只要有任何一个promise 状态变更  就可以返回
                        resolve(res);
                    },
                    (err) => {
                        reject(err);
                    }
                );
            }
        });
    }
}

// 使用
// let promise1 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(123);
//   }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(1234);
//   }, 1000);
// });

// Mypromise.all([promise1,promise2]).then(res=>{
//   console.log(res);
// })

// Mypromise.race([promise1, promise2]).then(res => {
//   console.log(res);
// });

// promise1
//   .then(
//     res => {
//       console.log(res); //过两秒输出123
//       return new Mypromise((resolve, reject) => {
//         setTimeout(() => {
//           resolve("success");
//         }, 1000);
//       });
//     },
//     err => {
//       console.log(err);
//     }
//   )
//   .then(
//     res => {
//       console.log(res); //再过一秒输出success
//     },
//     err => {
//       console.log(err);
//     }
//   );

# 24、实现一个add方法

题目描述:实现一个 add 方法 使计算结果能够满足如下预期: add(1)(2)(3)()=6 add(1,2,3)(4)()=10

其实就是考函数柯里化

function add(...args) {
    let allArgs = [...args];

    function fn() {
        allArgs = [...allArgs, ...arguments];
        return fn;
    }

    fn.toString = () => {
        if (!allArgs.length) {
            return;
        }
        return allArgs.reduce((sum, cur) => sum + cur);
    }
    return fn;
}

# 25、动态规划求解硬币找零的问题。

题目描述:给定不同面额的硬币 coins 和一个总金额 amount。 编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

示例2:
输入: coins = [2], amount = 3
输出: -1
function coninChange(coins, amount) {

}

# 26、请实现DOM2JSON一个函数,可以把一个DOM节点输出JSON格式。

<div>
    <span>
        <a></a>
    </span>
    <span>
        <a></a>
        <a></a>
    </span>
</div>

把上诉dom结构转成下面的JSON格式

{
    tag: 'DIV',
        children: [
    {
        tag: 'SPAN',
        children: [
            {tag: 'A', children: []}
        ]
    },
    {
        tag: 'SPAN',
        children: [
            {tag: 'A', children: []},
            {tag: 'A', children: []}
        ]
    }
]
}

function dom2JSON(domtree) {
    let obj = {};
    obj.name = domtree.tagName;
    obj.children = [];
    obj.children.forEach(child => obj.children.push(domtree(child)));
    return obj;
}

# 27、类数组转化为数组的方法

题目描述:类数组拥有 length 属性 可以使用下标来访问元素 但是不能使用数组的方法 如何把类数组转化为数组?

const arrayLike = document.querySelectorAll('div');
// 1.扩展运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)

# 28、Object.is 实现

Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。

  1. NaN在===中是不相等的,而在Object.is中是相等的
  2. +0和-0在===中是相等的,而在Object.is中是不相等的
Object.is = function (x, y) {
    if (x === y) {
        // 当前情况下,只有一种情况是特殊的,即 +0 -0
        // 如果 x !== 0,则返回true
        // 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
        return x !== 0 || 1 / x === 1 / y;
    }

    // x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
    // x和y同时为NaN时,返回true
    return x !== x && y !== y;
};

# 29、AJAX

题目描述:利用 XMLHttpRequest 手写 AJAX 实现

实现代码如下:

const getJSON = function (url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Content-Type', "application/json");
        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(xhr.responseText));
            }
        }
        xhr.send();
    })
}

# 30、分片思想解决大数据渲染问题

题目描述:渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染

let url = document.getElementById("container");
let total = 1000000;
let once = 20;
let page = total / once;
let index = 1;

function loop(curTotal, curIndex) {
    if (curTotal <= 0) return;
    let pageCount = Math.min(curTotal, once);
    const fragment = document.createDocumentFragment();
    window.requestIdleCallback(function () {
        for (let i = 0; i < pageCount; i++) {
            let li = document.createElement('li');
            li.innerText = curIndex + i + ":" + ~~Math.random() * total;
            fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        loop(curTotal - pageCount, curIndex + pageCount);
    })
}

loop(total, index);
Last Updated: 5/21/2022, 10:29:47 AM