前端常见手写题总结,助力每一个面试的小伙伴。
需获取更良好的代码高亮阅读体验,请前往我的博客原文地址JS手写题大汇总 | 希亚的西红柿のBlog (refrain.cf)。
JS 基础篇
深入了解 JS 这门语言的运行逻辑与机制,尝试实现其内部的方法。
ES5 实现继承
使用 ES5 的语法实现继承,注意静态方法的继承,好好体会下面两个例子。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.say = () => {
console.log("I'm a person!");
};
function Man(name, age, address) {
Person.call(this, name, age);
this.address = address;
}
Man.speak = () => {
console.log("I'm a man!");
};
Object.setPrototypeOf(Man, Person);
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
const person = new Person("person", 10);
const man = new Man("man", 11, "Tokyo");
Man.say();
Man.speak();
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static say = () => {
console.log("I'm a person!");
};
}
class Man extends Person {
constructor(name, age, address) {
super(name, age);
this.address = address;
}
static speak = () => {
console.log("I'm a man!");
};
}
const person = new Person("person", 10);
const man = new Man("man", 11, "Tokyo");
console.log(person, man);
console.log(Object.getPrototypeOf(Man) === Person);
Man.say();
Man.speak();
实现 new 函数
基本功无需多言,注意原型设置与执行结果的返回情况。
首先对问题进行分析,分析
new关键字到底干了什么。
- 创建一个空对象将该对象的原型设置为函数的
prototype。- 执行函数,
this指向指向该对象。- 若函数返回结果不为空且为对象,则返回该对象,否则返回先前生成的对象。
const myNew = (func, ...params) => {
const newObj = Object.create(func.prototype);
const result = func.apply(newObj, params);
return typeof result === "object" && result !== null ? result : newObj;
};
手写 instanceof
纯粹的基本功,考察对原型链的理解深度。
// 手写一个 instanceof 函数
const myInstanceOf = (left, right) => {
if (typeof left !== "object" || left === null) return false;
const proto = right.prototype;
let temp = Object.getPrototypeOf(left);
while (temp) {
if (temp === proto) return true;
temp = Object.getPrototypeOf(temp);
}
return false;
};
const result = myInstanceOf(null, {});
console.log(result);
手写数据类型判断函数
Object.prototype.toString调用与正则,结合代码理解
const getTypeOf = (target) => {
return Object.prototype.toString
.call(target)
.match(/^\[object (\w+)\]$/)[1]
.toLowerCase();
};
手写 promisify 函数
注意
promisify需要返回一个函数,这个函数返回一个promise对象。 注意剩余参数与展开语法的异同
/**
* @param {(...args) => void} func
* @returns {(...args) => Promise<any}
*/
const promisify = (func) => {
return function (...params) {
return new Promise((resolve, reject) => {
func.call(this, ...params, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
};
手写 async 方法
注意
next的位置,以及两个return语句的使用。
const asyncFunc = () => {
spawn(function* () {});
};
const spawn = (genF) => {
return new Promise((resolve, reject) => {
const gen = genF();
const step = (nextF) => {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
(v) => step(() => gen.next(v)),
(r) => step(() => gen.throw(r))
);
};
step(() => gen.next(undefined));
});
};
手写 EventEmitter 函数
- 注意
emit回调函数的this指向以及参数的问题。 - 注意
once函数可以采用闭包变量执行一次,或者采用闭包变量储存函数名,执行后移除。
class EventEmitter {
constructor() {
this.callbacks = {};
}
addListener(type, callback) {
if (!this.callbacks[type]) {
this.callbacks[type] = [];
}
this.callbacks[type].push(callback);
}
prependListener(type, callback) {
if (!this.callbacks[type]) {
this.callbacks[type] = [];
}
this.callbacks[type].unshift(callback);
}
on(type, callback) {
this.addListener(type, callback);
}
removeListener(type, callback) {
const index = this.callbacks[type].findIndex((item) => item === callback);
this.callbacks[type].splice(index, 1);
}
off(type, callback) {
this.removeListener(type, callback);
}
emit(type, ...args) {
if (this.callbacks[type]) {
this.callbacks[type].forEach((callback) => callback.apply(this, args));
}
}
once(type, callback) {
const _onceWrap = (func) => {
const wrapFunc = function (...params) {
func.apply(this, params);
this.removeListener(type, wrapFunc);
};
return wrapFunc;
};
this.addListener(type, _onceWrap(callback));
}
}
const ee = new EventEmitter();
// 注册所有事件
ee.once("wakeUp", (name) => {
console.log(`${name} 1`);
});
ee.on("eat", (name) => {
console.log(`${name} 2`);
});
ee.on("eat", (name) => {
console.log(`${name} 3`);
});
const meetingFn = (name) => {
console.log(`${name} 4`);
};
ee.on("work", meetingFn);
ee.on("work", (name) => {
console.log(`${name} 5`);
});
ee.emit("wakeUp", "xx");
ee.emit("wakeUp", "xx"); // 第二次没有触发
ee.emit("eat", "xx");
ee.emit("work", "xx");
ee.off("work", meetingFn); // 移除事件
ee.emit("work", "xx"); // 再次工作
手写 call,apply,bind
super关键字可以作为构造函数在构造函数里面调用super关键字还做作为对象调用父对象上的方法或者静态方法,但在普通方法里面只能调用普通方法而不能调用静态方法,但在子类的静态方法中则可以都可以调用。
注意自己实现需要实现利用对象的方法隐式绑定做到的,注意bind关键字作为new方法调用的时候对this指向做出特殊的。
Function.prototype.myCall = function (obj, ...params) {
const key = Symbol("key");
obj[key] = this;
const result = obj[key](...params);
delete obj[key];
return result;
};
Function.prototype.myApply = function (obj, params) {
const key = Symbol("key");
obj[key] = this;
const result = obj[key](...params);
delete obj[key];
return result;
};
Function.prototype.myBind = function (obj, ...params) {
const func = this;
const bound = function (...args) {
const context = Boolean(new.target) ? this : obj;
func.myApply(context, [...params, ...args]);
};
bound.prototype = Object.create(func.prototype);
bound.prototype.constructor = bound;
return bound;
};
function say(arg1, arg2) {
console.log(this.age, arg1, arg2);
}
let person = {
age: 3,
};
let bindSay = say.myBind(person, "我叫", "nova");
bindSay();
new bindSay();
手写 Object.create
利用new关键字
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
手写数组 push
const push = (arr, ...params) => {
for (let i = 0; i < params.length; i++) {
arr[arr.length] = params[i];
}
return arr.length;
};
const a = [1, 2, 3];
const b = push(a, 1, 2, 3);
console.log(a, b);
手写字符串 repeat
实现字符串的repeat方法,注意递归的核心为保证字符串不变
const repeat = (str, n) => {
return new Array(n + 1).join(str);
};
// 核心为保证 str 不变
const repeat0 = (str, n) => {
return n > 1 ? str.concat(repeat0(str, n - 1)) : str;
};
const a = repeat("sasda", 3);
console.log(a);
手写 Object.assign
注意对参数为剩余参数,同时
Object强制转换对象会返回对象本身。
const objectAssign = (obj, ...sources) => {
if (!obj) {
throw new TypeError(
"TypeError: Cannot convert undefined or null to object"
);
}
const ret = Object(obj);
sources.forEach((source) => {
if (source)
Object.keys(source).forEach((key) => {
ret[key] = source[key];
});
});
return ret;
};
const a = { a: 1 };
const b = { b: 2 };
const c = objectAssign(a, b);
console.log(a, b, c);
深入 JS 的使用
柯里化
经典无需多言
/**
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
*/
const curry = (fn) => {
return function curryInner(...params) {
if (params.length < fn.length)
return function (...args) {
return curryInner(...params, ...args);
};
else return fn.apply(this, params);
};
};
/**
* @param { (...args: any[]) => any } fn
* @returns { (...args: any[]) => any }
*/
const curry = (fn) => {
return function curryInner(...params) {
if (params.length < fn.length || params.includes(curry.placeholder)) {
return function (...args) {
params = params.map((item) => {
if (item === curry.placeholder) {
return args.shift();
} else {
return item;
}
});
return curryInner(...params, ...args);
};
} else {
return fn.apply(this, params);
}
};
};
curry.placeholder = Symbol();
深拷贝
Descriptor来全给你拷了。
-
采用
ES6之后的方法进行,原型链与属性修饰符const copy = (obj) => { const newObj = Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) ); }; -
采用
for循环实现深拷贝const cloneDeep = (data) => { // your code here const map = new WeakMap(); const clone = (obj) => { if (typeof obj !== "object" || obj === null) return obj; if (map.has(obj)) return map.get(obj); const newObj = new obj.constructor(); map.set(obj, newObj); Reflect.ownKeys(obj).forEach((key) => { newObj[key] = clone(obj[key]); }); return newObj; }; return clone(data); };
防抖与节流
简单复杂两种全部实现,结合代码理解,无需多言。
// 防抖
const debounce = (func, wait) => {
let timer = null;
return function (...params) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, params);
timer = null;
}, wait);
};
};
const debounce = (func, wait, options = { leading: false, trailing: true }) => {
let timer = null;
return function (...params) {
let isInvoked = false;
if (timer) clearTimeout(timer);
else if (options.leading) {
func.apply(this, params);
isInvoked = true;
}
timer = setTimeout(() => {
if (options.trailing && !isInvoked) {
func.apply(this, params);
}
timer = null;
}, wait);
};
};
// 节流
const throttle = (func, wait) => {
let timer = null;
let lastArgs = null;
const run = () => {
timer = null;
if (lastArgs) {
func.apply(this, lastArgs);
lastArgs = null;
timer = setTimeout(run, wait);
}
};
return function (...params) {
if (!timer) {
func.apply(this, params);
timer = setTimeout(run, wait);
} else {
lastArgs = params;
}
};
};
/**
* @param {(...args: any[]) => any} func
* @param {number} wait
* @param {boolean} option.leading
* @param {boolean} option.trailing
* @returns {(...args: any[]) => any}
*/
const throttle = (func, wait, option = { leading: true, trailing: true }) => {
let timer = null;
let lastArgs = null;
const run = () => {
if (lastArgs && option.trailing) {
func.apply(this, lastArgs);
lastArgs = null;
timer = setTimeout(run, wait);
} else timer = null;
};
return function (...params) {
if (!timer) {
if (option.leading) {
func.apply(this, params);
}
timer = setTimeout(run, wait);
} else if (option.trailing) {
lastArgs = params;
}
};
};
实现数组的 flat 方法
核心思路为递归,使用数组的reduce方法能减少不少代码量。
function flat(arr, depth = 1) {
const results = [];
const run = (items, count = 0) => {
if (count > depth) return results.push(items);
for (const item of items) {
if (!Array.isArray(item)) {
results.push(item);
} else {
run(item, count + 1);
}
}
};
run(arr);
return results;
}
// 简化版,运行了数组的 reduce方法
function flat(arr, depth = 1) {
return depth > 0
? arr.reduce(
(pre, cur) =>
pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : [cur]),
[]
)
: arr;
}
并发控制
-
并发池
async function asyncPool(poolLimit, array, iteratorFn) { const ret = []; const executing = []; for (const item of array) { const p = Promise.resolve(iteratorFn(item)); ret.push(p); if (poolLimit < array.length) { const e = p.finally(() => executing.splice(executing.indexOf(e), 1)); executing.push(e); if (executing.length >= poolLimit) { await Promise.race(executing); } } } return Promise.all(ret); } -
递归版本
/** * @param {() => Promise<any>} func * @param {number} max * @return {Promise} */ // 注意 resolve 的时机以及位置 function throttlePromises(funcs, max) { return new Promise((resolve, reject) => { const queue = [...funcs]; const results = []; let count = 0; const run = () => { while (queue.length && count < max) { const p = queue.shift()(); count++; p.then( (v) => { results.push(v); count--; run(); }, (r) => { reject(r); } ); } if (funcs.length === results.length) { resolve(results); } }; run();. }); } -
调度器版本
注意
add方法的返回值需要是一个promise对象,并且这个promise对象的状态应该由传入函数来决定。自动调度的核心机制还是递归。class Scheduler { constructor(size = 2) { this.isRunning = 0; this.queue = []; this.size = size; } add(promiseCreator) { return new Promise((resolve, reject) => { this.queue.push(() => promiseCreator().then(resolve, reject)); this.executeNext(); }); } executeNext() { if (this.queue.length && this.isRunning < this.size) { this.isRunning++; const p = this.queue.shift(); p().finally(() => { this.isRunning--; this.executeNext(); }); } } } const timeout = (time) => new Promise((resolve) => { setTimeout(resolve, time); }); const timeout0 = (time) => new Promise((resolve, reject) => { setTimeout(reject, time); }).catch((e) => e); const scheduler = new Scheduler(); const addTask = (time, order) => { scheduler.add(() => timeout(time)).then(() => console.log(order)); }; const addTask0 = (time, order) => { scheduler.add(() => timeout0(time)).then(() => console.log(order)); }; addTask(1000, "1"); addTask0(500, "2"); addTask(300, "3"); addTask(400, "4"); // output: 2 3 1 4 // 一开始,1、2两个任务进入队列 // 500ms时,2完成,输出2,任务3进队 // 800ms时,3完成,输出3,任务4进队 // 1000ms时,1完成,输出1 // 1200ms时,4完成,输出4
实现 sleep 函数
利用迭代器来使
async与await来阻塞当前函数的执行线程。
const sleep = (delay) =>
new Promise((resolve) => {
setTimeout(resolve, delay);
});
async function test() {
console.log(1);
await sleep(1000);
console.log("Stop for 1s!");
await sleep(2000);
console.log("Stop for 2s!");
await sleep(3000);
console.log("Stop for 3s!");
}
test();
业务场景题
-
使用
promise封装一个ajax请求<body> <button>发送ajax请求</button> <script> //1.获取DOM元素对象 let btn = document.querySelector("button"); //2.绑定事件 btn.onclick = function () { //3.创建promise实例对象 const p = new Promise((resolve, reject) => { //4.创建ajax实例对象 const xhr = new XMLHttpRequest(); //5.打开请求 xhr.open( "get", "https://www.yiketianqi.com/free/day?appid=82294778&appsecret=4PKVFula&unescape=1" ); //6.发送请求 xhr.send(); //7.利用onreadystatechange事件 xhr.onreadystatechange = function () { //8.判断 if (xhr.readyState == 4) { if (xhr.status == 200) { resolve(xhr.responseText); } else { reject(xhr.response); } } }; }); p.then( (value) => { console.log(JSON.parse(value)); }, (reason) => { console.log("获取信息失败"); } ); }; </script> </body> -
实现日期格式化函数
const dateFormat = (dateString, format) => { const date = new Date(dateString); const day = date.getDay(); const month = date.getMonth() + 1; const year = date.getFullYear(); format = format.replace(/yyyy/, year); format = format.replace(/MM/, month); format = format.replace(/dd/, day); return format; }; dateFormat("2020-12-01", "yyyy/MM/dd"); // 2020/12/01 dateFormat("2020-04-01", "yyyy/MM/dd"); // 2020/04/01 dateFormat("2020-04-01", "yyyy年MM月dd日"); // 2020年04月01日 -
交换
a, b的值不能用临时变量b = a + b; a = b - a; b = b - a; -
注意
ajax一定需要send方法function getJSON(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { return resolve(xhr.responseText); } } reject(xhr.statusText); }; xhr.send(); }); return p; } -
对数字千分位的格式化。
/** * @param {number} num * @return {string} */ // naive solution function addComma(num) { const sign = num < 0 ? -1 : 1; if (sign < 0) { num *= -1; } const str = num.toString(); const [integer, fraction] = str.split("."); const arr = []; const digits = [...integer]; for (let i = 0; i < digits.length; i++) { arr.push(digits[i]); // add extra commas // care for the 0 const countOfRest = digits.length - (i + 1); if (countOfRest % 3 === 0 && countOfRest !== 0) { arr.push(","); } } const newInteger = (sign < 0 ? "-" : "") + arr.join(""); if (fraction === undefined) return newInteger; return newInteger + "." + fraction; } // regular expression 1 function addComma(num) { const str = num.toString(); let [integer, fraction] = str.split("."); while (true) { const next = integer.replace(/(\d+)(\d{3})/, "$1,$2"); // 12345,678 // 12,345,678 if (next === integer) { break; } integer = next; } if (fraction === undefined) return integer; return integer + "." + fraction; } // regular expressiong global, // (?= ) function addComma(num) { const str = num.toString(); let [integer, fraction] = str.split("."); integer = integer.replace(/(\d)(?=(\d{3})+$)/g, "$1,"); if (fraction === undefined) return integer; return integer + "." + fraction; } const addComma = (num) => { let [former, latter] = String(num).split("."); const regex = /(\d)(?=(\d{3})+$)/g; former = former.replace(regex, "$1,"); if (!latter) return former; return former + "." + latter; }; -
数组的乱序输出
const a = [1, 2, 3, 4, 5]; const randomOutput = (arr) => { for (let i = 0; i < arr.length; i++) { const index = Math.floor(Math.random() * (arr.length - i)) + i; [arr[index], arr[i]] = [arr[i], arr[index]]; console.log(arr[i]); } }; const randomOutput0 = (arr) => { let length = arr.length; while (length) { const index = Math.floor(Math.random() * length--); [arr[index], arr[length]] = [arr[length], arr[index]]; console.log(arr[length]); } }; randomOutput(a); console.log(a); -
实现数组的斜向打印
function printMatrix(arr) { const m = arr.length, n = arr[0].length; const result = []; for (let i = 0; i < n; i++) { for (let j = 0, k = i; k >= 0 && j < m; j++, k--) { result.push(arr[j][k]); } } for (let i = 1; i < m; i++) { for (let j = n - 1, k = i; j > 0 && k < m; k++, j--) { result.push(arr[k][j]); } } return result; } console.log( printMatrix([ [1, 2, 3, 4], [4, 5, 6, 4], [7, 8, 9, 4], ]) ); -
电话号码的打印:
const changeNum = (str) => { return str.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$2"); }; const changeNum0 = (str) => { const arr = Array.from(str); arr.splice(3, 4, "****"); return arr.join(""); }; const changeNum1 = (str) => { return str.replace(str.slice(3, 7), "****"); }; console.log(changeNum1("15727709770")); -
循环打印方案
const task = (light, delay, callback) => { setTimeout(() => { if (light === "red") { console.log("yellow"); } else if (light === "yellow") { console.log("green"); } else if (light === "green") { console.log("red"); } callback(); }, delay); }; const step = () => { task("green", 1000, () => task("red", 1000, () => task("yellow", 1000, () => step())) ); }; step(); // 利用 promise 包装对象链式递归调用 const task = (delay, light) => { return new Promise((resolve, reject) => { setTimeout(() => { if (light === "red") { console.log("red"); } else if (light === "yellow") { console.log("yellow"); } else if (light === "green") { console.log("green"); } resolve(); }, delay); }); }; const step = () => { const p = new Promise((resolve, reject) => { resolve(task(1000, "red")); }); p.then(() => task(1000, "green")) .then(() => task(1000, "yellow")) .then(step); }; step(); const taskMaker = () => { let run = true; const stop = () => (run = false); const execute = async () => { while (run) { await task(1000, "red"); await task(1000, "yellow"); await task(1000, "green"); } }; return { stop, execute }; }; const { stop, execute } = taskMaker(); execute(); console.time("Test"); setTimeout(() => { console.log("stop"); stop(); console.timeEnd("Test"); }, 4000); -
丢手帕问题
const Josephu = (num, count) => { const circle = []; for (let i = 0; i < num; i++) { circle[i] = i + 1; } let counter = 0; let out = 0; for (let i = 0; i < circle.length; i++) { if (out >= circle.length - 1) break; if (circle[i]) { counter++; if (counter === count) { circle[i] = 0; counter = 0; out++; } } if (i === circle.length - 1) i = -1; } for (let i = 0; i < circle.length; i++) { if (circle[i]) return circle[i]; } }; function childNum(num, count) { let allplayer = []; for (let i = 0; i < num; i++) { allplayer[i] = i + 1; } let exitCount = 0; // 离开人数 let counter = 0; // 记录报数 let curIndex = 0; // 当前下标 while (exitCount < num - 1) { if (allplayer[curIndex] !== 0) counter++; if (counter == count) { allplayer[curIndex] = 0; counter = 0; exitCount++; } curIndex++; if (curIndex == num) { curIndex = 0; } } for (i = 0; i < num; i++) { if (allplayer[i] !== 0) { return allplayer[i]; } } } console.log(Josephu(39, 6)); console.log(childNum(39, 6)); -
查找文章中出现频率最高的单词
const findMostWord = (article) => { if (!article) { console.error("Argument cannot be undefined or null!"); } const str = article.trim().toLowerCase(); const words = str.match(/[a-z]+/g); let maxCount = 0, maxStr = "", set = new Set(); words.forEach((item) => { if (!set.has(item)) { set.add(item); const count = str.match(new RegExp(`\\b${item}\\b`, "g")).length; if (count > maxCount) { maxStr = item; maxCount = count; } } }); return maxStr + ":" + maxCount; }; const findMostWord0 = (article) => { // 合法性判断 if (!article) return; // 参数处理 article = article.trim().toLowerCase(); let wordList = article.match(/[a-z]+/g), visited = [], maxNum = 0, maxWord = ""; article = " " + wordList.join(" ") + " "; // 遍历判断单词出现次数 wordList.forEach(function (item) { if (visited.indexOf(item) < 0) { // 加入 visited visited.push(item); let word = new RegExp(" " + item + " ", "g"), num = article.match(word).length; if (num > maxNum) { maxNum = num; maxWord = item; } } }); return maxWord + " " + maxNum; }; console.log(findMostWord0("a a a a a a bbb bbb bbb b b b b b b b b b b b")); -
setTimeout模仿setIntervalconst mySetInterval = (fn, wait) => { const timer = { flag: true }; const step = () => { if (timer.flag) { fn(); setTimeout(step, wait); } }; step(); return timer; }; const timer = mySetInterval(() => console.log(10), 1000); setTimeout(() => (timer.flag = false), 5000); -
判断对象中是否存在循环引用
const isCircle = (target) => { const set = new Set(); const step = (obj) => { if (typeof obj !== "object" || obj === null) return false; if (set.has(obj)) return true; set.add(obj); for (const key of Reflect.ownKeys(obj)) { const result = step(obj[key]); if (result) return true; } }; return step(target); }; const a = { a: 1 }; a.b = a; console.log(a); console.log(isCircle(a.b)); -
手写一个
nullToUndefined函数/** * @param {any} arg * @returns any */ const undefinedToNull = (arg) => { if (arg === undefined) return null; if (typeof arg !== "object" || arg === null) return arg; for (const key in arg) { arg[key] = undefinedToNull(arg[key]); } return arg; }; -
判断字符串的有效数字
/** * @param {string} str * @returns {boolean} */ const validateNumberString = (str) => { return str !== "" && !isNaN(str); }; const validateNumberString = (str) => { return /^[+-]?(\d+(\.\d*)?|\d*\.\d+)(e[+-]?\d+)?$/i.test(str); }; -
实现一个累加器
/** * @param {number} num */ const sum = (count) => { function sumInner(number) { return sum(count + number); } sumInner.valueOf = () => count; return sumInner; }; -
nullToUndefined简单递归/** * @param {any} arg * @returns any */ const undefinedToNull = (arg) => { if (arg === undefined) return null; if (typeof arg !== "object" || arg === null) return arg; for (const key in arg) { arg[key] = undefinedToNull(arg[key]); } return arg; }; -
counterfunction自执行包裹const count = (() => { let num = 0; function func() { return ++num; } func.reset = () => { num = 0; }; return func; })(); -
counter对象,简单的数据代理/** * @returns { {count: number}} */ const createCounter = () => { let count = 0; return Object.defineProperty({}, "count", { get() { return count++; }, set() { console.log("it cannot be altered"); }, }); }; -
失败后自动发起请求,超时后停止。
/** * @param {() => Promise<any>} fetcher * @param {number} maximumRetryCount * @return {Promise<any>} */ function fetchWithAutoRetry(fetcher, maximumRetryCount) { // your code here return new Promise((resolve, reject) => { let count = 0; const run = () => { fetcher().then(resolve, (r) => { console.log(r); count++; if (count > maximumRetryCount) return reject(r); run(); }); }; run(); }); } -
实现斐波那契数列的两种方式。
const fib = (n, a = 0, b = 1) => { if (n < 1) return a; return fib(n - 1, b, a + b); }; const fib = (n) => { if (n === 0) return 0; if (n === 1) return 1; return fib(n - 1) + fib(n - 2); }; fib(10); // 55 fib(1000); // timeout -
创建一个
LazyMan函数// interface Laziness { // sleep: (time: number) => Laziness // sleepFirst: (time: number) => Laziness // eat: (food: string) => Laziness // } /** * @param {string} name * @param {(log: string) => void} logFn * @returns {Laziness} */ const LazyMan = (name, logFn) => { const tasks = [() => logFn(`Hi, I'm ${name}.`)]; const eat = (food) => logFn(`Eat ${food}.`); const sleep = (time) => new Promise((resolve) => setTimeout(() => resolve(), 1000 * time)).then( () => logFn(`Wake up after ${time} second${time > 1 ? "s" : ""}.`) ); setTimeout(async () => { for (const func of tasks) { await func(); } }); return { eat(food) { tasks.push(() => eat(food)); return this; }, sleep(time) { tasks.push(() => sleep(time)); return this; }, sleepFirst(time) { tasks.unshift(() => sleep(time)); return this; }, }; }; -
JSON文件转化为树结构的问题,关键在于利用对象的引用和map结构来进行操作。面试的时候这么简单的题居然没做出来,顶级 🤡,
DFS入脑了看啥都想DFS,其实只要对每个对象建立一个Map,然后遍历对象进行赋值就好了,主要还是利用了对象本身的唯一性。const arr = [ { id: 1, name: "部门1", pid: 0 }, { id: 2, name: "部门2", pid: 1 }, { id: 3, name: "部门3", pid: 1 }, { id: 4, name: "部门4", pid: 3 }, { id: 5, name: "部门5", pid: 4 }, ]; const target = [ { id: 1, name: "部门1", pid: 0, children: [ { id: 2, name: "部门2", pid: 1, children: [], }, { id: 3, name: "部门3", pid: 1, children: [], }, ], }, ]; const convertToTree = (arr) => { const map = new Map(); arr.forEach((item) => { map.set(item.id, item); }); arr.forEach((item) => { if (map.has(item.pid)) { const parent = map.get(item.pid); if (parent.children || (parent.children = [])) { parent.children.push(item); } } }); return [arr[0]]; }; console.log(convertToTree(arr)); -
封装一个
fetch请求class HTTPRequestUtil { async get(url) { const res = await fetch(url); const data = await res.json(); return data; } async post(url, data) { const res = await fetch(url, { method: "POST", headers: { "Content-type": "application/json", }, body: JSON.stringify(data), }); const result = await res.json(); return result; } async put(url, data) { const res = await fetch(url, { method: "PUT", headers: { "Content-type": "application/json", }, body: JSON.stringify(data), }); const result = await res.json(); return result; } async delete(url, data) { const res = await fetch(url, { method: "DELETE", headers: { "Content-type": "application/json", }, body: JSON.stringify(data), }); const result = await res.json(); return result; } }