开篇提醒:不是手写防抖、节流,手写call、apply、bind等内容!!!!!
阿sir的一个朋友的朋友面试了字节,遇到了两个算法题,朋友的朋友面试完以后,来找朋友讨论,朋友再来找阿sir讨论。阿sir于是乎便整理了一下这些面试中容易出现的手写算法题,并且也自己手动实现了一遍,大家可以参考一下,如果有不同见解欢迎在评论区中指出,不胜感激!
话不多说,先上题目,难度不尽相同,有兴趣的朋友可以先自己尝试做一遍。
题目:
1. 判断子对象是否存在父对象中(字节原题)
注意:子对象中的属性必须存在父对象中,并且层级要对应(并非一定层级相同)
// 父对象
const father = {
a: 1,
b: 'name',
c: {
d: 1,
e: '100',
f: {
g: 22,
h: 23,
j: 'xc',
i: {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
k: 22
},
},
b: {
a: 'xxxxx',
},
}
}
// 子对象
const child1 = {
a: 1
}
const child2 = {
a: 1,
b: 'name',
}
const child3 = {
a: 1,
b: 'name',
c: 100,
}
const child4 = {
i: {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
k: 22
},
}
const chil5 = {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
}
const child6 = {
f: {
g: 22,
h: 23,
j: 'xc',
i: {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
k: 22
},
},
b: {
a: 'xxxxx',
},
}
isCheck(child1, father) // true
isCheck(child2, father) // true
isCheck(child3, father) // false
isCheck(child4, father) // true
isCheck(child5, father) // true
isCheck(child6, father) // true
2. 对象扁平化 (字节原题)
const obj = {
a: 0,
b: [1, 2, { e: 3 }, 5],
c: {
e: 2,
i: 1
},
g: null
}
// 实现一函数能将数据转换为
// {
// 'a': 0,
// 'b[0]':1,
// 'b[1]':2,
// 'b[2].e':3,
// 'b[3]':5,
// 'c.e':2,
// 'c.i':1,
// }
3. 红绿灯问题
模拟实现红绿灯,从0开始,第3秒输出红灯,第5秒输出绿灯,第6秒输出黄灯,第9秒输出红灯...
4. 异步任务队列控制
实现一个Scheduler,满足如下要求
const scheduler = new Scheduler(2) // 传入并发最大限制
scheduler.addTask(1, '1'); // 1s后输出'1'
scheduler.addTask(2, '2'); // 2s后输出'2'
scheduler.addTask(3, '3'); // 4s后输出'3'
scheduler.addTask(4, '4'); // 6s后输出'4'
scheduler.start();
5. 洗牌算法,如何验证这个洗牌算法可以把牌洗得足够乱
经典洗牌算法,给定一个[1,2,3,4,5,6,7,8,9,10],随机打乱
6. 实现 ob 和 watch 方法,希望当方法传入 watch 函数时会执行一次,之后每次修改 data 上的属性时,会触发对应的 console
会vue的同学应该是手拿把掐
const data = ob({ count: 0, foo: 'test' });
watch(() => {
console.log('watch-count', data.count);
});
watch(() => {
console.log('watch-foo', data.foo);
});
data.count += 1;
console.log('showcount', data.count);
delete data.count;
data.foo = 'test2';
7. 构建树
其中根id为0
let 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},
]
输出结果
[
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
// 结果 ,,,
]
}
]
}
]
8. vue模板替换功能实现
const data = {
name: '222',
age: 18,
happy: {
ball: '333',
game: '33333'
},
arr: ['444444'],
flag: true,
sayHello: function (msg) {
return msg
}
}
let temStr = 'hello my name is {{ name }}, age is {{ age }}, my happy is {{ happy.ball }} and {{ happy.game }}, i like {{ arr[0] }}, end {{ flag ? sayHello("232") : sayHello("2323") }}'
9. 解析Url中的请求参数
随便放一个百度的url
10. 使用requestAnimation实现setInterVal
不错,大家没有看错,不再是setTimeout模拟了,setTimeout模拟已经是人人必备的一项技能了,所以我们需要掌握使用requestAnimation实现setInterVal。
11. 实现Promise.all、Promise.race、Promise.allSettled
如果对这几个方法不是很了解的童鞋,可以先看看MDN(developer.mozilla.org/zh-CN/docs/…)
解析:
以下的代码,均是sir根据自己的理解写的,当中可能会有一些不太合理的地方,欢迎各位童鞋提出其他看法,或者指出其中存在的问题。
1. 判断子对象是否存在父对象中
const father = {
a: 1,
b: 'sdad',
c: {
d: 1,
e: '100',
f: {
g: 22,
h: 23,
j: 'xc',
i: {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
k: 22
},
},
b: {
a: 'xxxxx',
},
}
}
const child = {
c: [1,2,4, [1,2,3], {
name: 'xxx'
}],
}
const isCheck = (obj1, obj2) => {
// 先判断obj1和obj2的类型
if (obj1 instanceof Object && obj2 instanceof Object) { // 如果两个都是对象
const aKeys = Object.keys(obj1);
const bKeys = Object.keys(obj2);
let flag = true; // 表示正常执行完while
let currResult = true; // 记录当前层级是否匹配,默认是true
while (aKeys.length) {
const aKey = aKeys.pop()
if (bKeys.includes(aKey)) { // 如果是包含关系
//先判断类型
if (typeof obj1[aKey] !== typeof obj2[aKey]) { // 如果两个类型不一致,那父对象当前层级一定不满足
flag = false;
break; // 跳出当前循环
}
if (obj1[aKey] instanceof Object && obj2[aKey] instanceof Object) { // 如果类型是相同的,.并且都是对象
// 递归比对该键对应的值
currResult = currResult && isCheck(obj1[aKey], obj2[aKey]); // 只有当下一个层级是包含关系,返回true,
} else { // 不都是对象,那么直接判断是否相等
if (obj1[aKey] !== obj2[aKey]) { // 如果该键对应的值不相同,那么说明父对象当前层级一定不满足,直接跳出(同一级对象不能拥有相同的键值)
flag = false
break;
}
// 相同不需要做任何处理
}
} else { // 如果不是包含关系
flag = false;
break
}
}
if (!flag) { // flag为false表示是通过break跳出的while循环
let result = false;
const bObjectKeys = getObjectKeys(obj2); // 当层对象肯定不满足,找到所有值为object类型的键,每个都去比对是否相同
for (let key of bObjectKeys) { // 只要找到一个包含关系,那么就说明是包含
result = result || isCheck(obj1, obj2[key]);
}
return result
} else { // 正常while遍历完成
// 直接返回currResult
return currResult
}
} else { // 如果两个不都是对象,那么直接返回是否相等
return obj1 === obj2
}
}
const getObjectKeys = (obj) => {
return Object.keys(obj).reduce((curr, key) => {
if (obj[key] instanceof Object) {
curr.push(key)
}
return curr
}, [])
}
console.log(isCheck(child, father))
2. 对象扁平化
const obj = {
a: 0,
b: [1, 2, { e: 3 }, 5, [1]],
c: {
e: 2,
i: 1
},
g: null,
d: {
dsad: 'xxx',
sds: 'vc',
dsd: [1, 2, 3, 2, [2, 3, [3, 3, 5, {
dsd: 'vcv',
arr: [1, 2, 3]
}]]]
}
}
const result = {};
const func = (obj, str) => {
for (let key in obj) {
if (obj[key] === null || obj[key] === undefined) continue
let nextStr = str.length === 0 ? `${key}` : Array.isArray(obj) ? `${str}[${key}]` : `${str}.${key}`;
obj[key] instanceof Object ? func(obj[key], nextStr) : result[nextStr] = obj[key]
}
}
func(obj, '')
console.log(result)
3. 红绿灯问题
// 解法1
function red() {
console.log('red');
}
function green() {
console.log('green')
}
function yellow() {
console.log('yellow')
}
const task = (name, wait) => new Promise((resolve) => {
setTimeout(() => {
console.log(name)
resolve();
}, wait);
})
const loop = async () => {
await task("红灯", 3000)
await task("绿灯", 2000)
await task("黄灯", 1000)
loop();
}
loop()
// 解法2
const func = (msg, time) => {
const child = (resolve) => {
setTimeout(() => {
console.log(msg)
resolve(child)
}, time);
}
return child
}
const red = func('红灯', 3000)
const green = func('绿灯', 2000)
const yellow = func('黄灯', 1000)
const func2 = (arr) => {
new Promise(resolve => {
const curr = arr.shift();
curr(resolve)
}).then(fun => {
arr.push(fun);
func2(arr);
})
}
const arr2 = [red, green, yellow]
func2(arr2)
4. 异步任务队列控制
class Scheduler {
constructor(limit) {
this.limit = limit
this.number = 0
this.queue = []
}
addTask(timeout, str) {
this.queue.push([timeout, str])
}
start() {
if (this.number < this.limit&&this.queue.length) {
var [timeout, str] = this.queue.shift()
this.number++
setTimeout(() => {
console.log(str)
this.number--
this.start()
}, timeout * 1000);
this.start()
}
}
}
const scheduler = new Scheduler(2)
scheduler.addTask(1, '1'); // 1s后输出'1'
scheduler.addTask(2, '2'); // 2s后输出'2'
scheduler.addTask(3, '3'); // 4s后输出'3'
scheduler.addTask(4, '4'); // 6s后输出'4'
scheduler.start();
5. 洗牌算法
// 简单的洗牌算法
const arr = [1,2,3,4,5,6,7,8,9,10]
arr.sort((a,b) => {
const num = Math.floor(Math.random() * 10) + 1; // 生成1 到 10
if(num > 5) {
return a - b
}else {
return b - a
}
})
console.log(arr)
力扣中有一道打乱数组的题目,leetcode-cn.com/problems/sh…, 上面的答案并不能通过全部的测试用例,得使用下面这种
function shuffle(nums) {
for(let i = 0; i < nums.length; i++) {
const j = Math.floor(Math.random() * (nums.length - i)) + i;
[nums[i], nums[j]] = [nums[j], nums[i]]
}
return nums
};
// 原理很简单,就是从后面得数字中随机抽一个作为当前的数字
至于如何验证是否洗的足够乱,这个sir也没有想到啥很好的办法,目前考虑的主要是从数组中每个元素被打乱的概率是否完全相同。如果说对于初始时候是有序数组,可以考虑使用最长递增子序列来判断一下是否有规律(sir的臆想),有思路的朋友欢迎在评论区中指出。
6. 实现 ob 和 watch 方法
// 根据vue3中的实现原理,写的稍微全面一点
let globalFunction = null;
const globalWekMap = new WeakMap();
function observer(params) {
if(typeof params !== 'object') {
throw new Error('function obj params is must be object!')
}
const handler = {
get: function(object, key, descriptor) {
if(globalFunction) {
if(globalWekMap.has(object)) {
const map = globalWekMap.get(object);
if(map.has(key)) {
const tem = map.get(key);
tem.push(globalFunction);
map.set(key, tem);
}else {
map.set(key, [globalFunction]);
}
}else {
const map = new Map();
map.set(key, [globalFunction]);
globalWekMap.set(object, map);
}
}
return Reflect.get(object, key, descriptor)
},
set: function(object, key, value, descriptor) {
Reflect.set(object, key, value, descriptor)
if(globalWekMap.has(object)) {
const map = globalWekMap.get(object)
if(map.has(key)) {
const arr = map.get(key)
arr.map(func => {
func();
})
}
}
},
deleteProperty(object, key, descriptor) {
if(Reflect.has(object, key)) {
if(globalWekMap.has(object)) {
const map = globalWekMap.get(object)
if(map.has(key)) {
map.delete(key)
}
}
Reflect.deleteProperty(object, key, descriptor);
}
}
}
const object = new Proxy(params, handler)
return object;
}
function watch(fn) {
globalFunction = fn;
fn();
globalFunction = null;
}
const data = observer({ count: 0, foo: 'test' });
watch(() => {
console.log('watch-count', data.count);
});
watch(() => {
console.log('watch-foo', data.foo);
});
data.count += 1;
console.log('showcount', data.count);
delete data.count;
data.foo = 'test2';
7. 构建树
let 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 result = [
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
// 结果 ,,,
]
}
]
}
]
const map = new Map();
map.set(0, []);
const func = (arr) => {
const result = [];
for(let i = 0; i < arr.length; i++) {
const obj = arr[i];
obj.children = [];
if(map.has(obj.pid)) {
const child = map.get(obj.pid);
child.push(obj);
}
map.set(obj.id, obj.children);
}
const temArr = map.get(0);
result.push(...temArr)
return result
}
console.log(func(arr))
8. vue模板替换功能实现
// 这里sir偷懒了,有兴趣的朋友可以尝试一下常规的解法
const data = {
name: '222',
age: 18,
happy: {
ball: '333',
game: '33333'
},
arr: ['444444'],
flag: true,
sayHello: function (msg) {
return msg
}
}
let temStr = 'hello my name is {{ name }}, age is {{ age }}, my happy is {{ happy.ball }} and {{ happy.game }}, i like {{ arr[0] }}, end {{ flag ? sayHello("232") : sayHello("2323") }}'
function complie(str) {
const reg = /{{.+?}}/g;
const result = str.replace(reg, regStr => {
regStr = regStr.slice(2, regStr.length - 2);
regStr = regStr.trim();
return geStrForTrueValue(regStr)
})
return result
}
function geStrForTrueValue(str) {
with (data) {
return eval(str);
}
}
console.log(complie(temStr))
9. 解析Url中的请求参数
const str = 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E6%8E%98%E9%87%91&fenlei=256&oq=%25E5%258A%259B%25E6%2589%25A3&rsv_pq=e47284980005215c&rsv_t=2e8cCZQGOTWslcWtfyhbQZh4iZECjg5OOPN6Lqdsmlqr5InmPlS6tnX2iEg&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_btype=t&rsv_sug3=27&rsv_sug1=27&rsv_sug7=100&rsv_sug2=0&inputT=2044&rsv_sug4=2045&rsv_sug=1'
// 解法1
// 创建一个URLSearchParams实例
const urlSearchParams = new URLSearchParams(str);
// 把键值对列表转换为一个对象
const params = Object.fromEntries(urlSearchParams.entries());
console.log(params)
// 解法2
function getParams(url) {
const res = {}
if (url.includes('?')) {
const str = url.split('?')[1]
const arr = str.split('&')
arr.forEach(item => {
const key = item.split('=')[0]
const val = item.split('=')[1]
res[key] = decodeURIComponent(val) // 解码
})
}
return res
}
console.log(getParams(str))
10. 使用requestAnimation实现setInterVal
function mySetInterVal(callback, timer) {
let timerId = null;
let preDate = new Date();
const func = () => {
const nowDate = new Date();
if(nowDate - preDate >= timer) {
preDate = nowDate;
callback();
}
timerId = window.requestAnimationFrame(func);
}
func();
return () => {
cancelAnimationFrame(timerId);
}
}
function test(){
console.log('xxxxxxxxxxx')
}
const callback = mySetInterVal(test, 2000)
// callBakc() 停止定时器
11. 实现Promise.all、Promise.race、Promise.allSettled
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'foo');
});
const promise4 = () => {
return 'xxxxxxxxx'
}
Promise.MyAll = function (promises) {
return new Promise(async (resolve, reject) => {
const callback = [];
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof Promise) {
await promises[i].then(res => {
callback[i] = res;
}).catch(err => {
reject(err);
})
} else {
callback[i] = promises[i]
}
}
resolve(callback)
})
}
// 模拟实现
Promise.MyAll([promise1, promise2, promise3, promise4()]).then((values) => {
console.log(values);
}).catch(err => {
console.log(err)
});
// 原版
Promise.all([promise1, promise2, promise3, promise4()]).then((values) => {
console.log(values);
}).catch(err => {
console.log(err)
});
实现了Promise.all以后,在实现Promise.race和Promise.allSettled就简单很多了,sir就不再写这两个了,大家可以自行尝试一下。
结语
文中的题目或者解析可能存在某些不严谨的地方,欢迎大家在评论区提出自己的看法和建议,最后祝各位童鞋元旦快乐!