# 手写常见的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不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。
- NaN在===中是不相等的,而在Object.is中是相等的
- +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);