最近面了一些大小厂,虽然结果不尽人意,但还是想写篇总结发出来回馈一下社区,毕竟之前也看了很多大佬的面经分享,对我还是很有帮助。
某哈游-协同文档
深度克隆
javascript
function cloneDeep(value) {
const map = new Map();
function _cloneDeep(value) {
const isObject = typeof value === 'object' && value !== null;
if (!isObject) return value;
if (map.has(value)) {
return map.get(value);
}
const clone = Array.isArray(value) ? [] : {};
for (const [key, v] of value.entries()) {
clone[key] = _cloneDeep(v);
}
map.set(value, clone);
return clone;
}
return _cloneDeep(value);
}
点评
直接用 structuredClone。
JSON.parse(JSON.stringify(value))
的问题:
JSON.stringify
不支持循环引用, 字符串没法表示无限递归JSON.stringify
不能处理JSON
支持的类型以外的类型,JSON
只支持object
,array
,number
,string
,boolean
,null
。例如Date
,Set
,Map
- 性能不如
structuredClone
字节跳动-恰饭平台
实现 transform
函数:
javascript
transform({
'A': 1,
'B.C': 2,
'B.D.E': 3,
'CC.DD.EE': 4,
});
得到:
javascript
const result = {
A: 1,
B: {
C: 2,
D: {
E: 3,
},
},
CC: {
DD: {
EE: 4,
},
},
};
实现:
javascript
function set(obj, keyPath, value) {
let i = 0;
while (i < keyPath.length - 1) {
const key = keyPath[i];
const current = obj[key];
if (current === undefined) {
obj[key] = {};
}
obj = obj[key];
i++;
}
obj[keyPath[i]] = value;
}
function transform(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
set(result, key.split('.'), value);
}
return result;
}
点评
估计本意是想考察实现 lodash.set。
我写的时候有点紧张,那个在线平台第一次用,不太熟悉,不知道咋跑测试用例,写完代码后,面试官说讲下思路就行,就说了下思路,他说没问题。
一面挂了,我发现面试官面完后还 follow 了我 github,本来印象还挺好的。挂了就挂了,本来没放心上,结果也不知道这面试官面评写了啥,导致别的部门 hr 直接不给面试机会了。3 年前应届面试的时候,最后 leader 面面完之后也是没下文,感觉当时表现也还行。以后就算还会面字节也不会考虑这个部门了。
北京XX设计
爬楼梯
爬楼梯,拿羽毛球,青蛙跳,以及斐波那契数列,这几个题的思路和代码都长一样。
点评
但凡你刷过 leetcode,就会做。
面试官一开始说最后做到动态规划题吧,当时我心头一惊,草,我最怕的就是动态规划了,结果还好是到简单题。
这家公司我也挺无语的,hr 在 boss 上主动约我面试。一面面完我问面试结果,它和我说过了,下周会安排二面,结果过了两周也没安排二面。也不知道是过年太忙了,还是没 hc 了,还是说有没有可能他被裁了?
某电商大厂
实现 Array.prototype.reduce
javascript
Array.prototype.reduce = function (callback, init) {
let array = this;
let acc = init;
if (init === undefined) {
acc = array[0];
array = array.slice(1);
}
for (const [index, item] of array.entries()) {
acc = callback(acc, item, init === undefined ? index + 1 : index);
}
return acc;
};
点评
这种简单题我感觉更多是考察一个开发的代码风格怎么样。
他们那个在线代码平台是在我的 chrome 上打开老是崩溃,浪费挺多时间,应该是和某个 chrome 插件有关系,关掉大部分插件后就不崩溃了。
当时我写的时候没处理不传初始值的情况,因为我平时写代码一直都是写初始值的,当然面试官也问了这个问题,我就说了下没传初始值就是用第一个元素当初始值。
Monkey
实现 Monkey 函数,运行后得到下面的输出。
javascript
Monkey('Alan').eat('Banana').sleep(4).eat('Apple').sleep(5).eat('Pear');
// my name is Alan
// I eat Banana
// 等待 4s
// I eat Apple
// 等待 5s
// I eat Pear
好像是腾讯校招最近特别喜欢考的题,怀疑面试官是腾讯跳槽过去的。这道题我之前在牛客上看过,但是记不清了。
临时发挥写的有点问题:
javascript
function Monkey(name) {
console.log(`my name is ${name}`);
let waiting = 0;
const result = {
eat(food) {
if (waiting === 0) {
console.log(`I eat ${food}`);
} else {
setTimeout(() => {
console.log(`I eat ${food}`);
}, waiting);
}
return result;
},
sleep(seconds) {
console.log(`等待 ${seconds}s`);
waiting += seconds;
return result;
},
};
return result;
}
// my name is Alan
// I eat Banana
// 等待 4s
// 等待 5s
// I eat Apple
// I eat Pear
面试完想了下:
javascript
function Monkey(name) {
console.log(`my name is ${name}`);
const queue = [];
let waiting = 0;
let running = false;
const result = {
eat(food) {
const sleepTime = waiting;
if (waiting !== 0) {
waiting = 0;
}
const task = () => {
running = true;
if (sleepTime !== 0) {
console.log(`等待 ${sleepTime}s`);
}
setTimeout(() => {
console.log(`I eat ${food}`);
running = false;
if (queue.length > 0) {
const frontTask = queue.shift();
frontTask();
}
}, sleepTime * 1000);
};
if (running === false) {
task();
} else {
queue.push(task);
}
return result;
},
sleep(seconds) {
waiting += seconds;
return result;
},
};
return result;
}
插曲
面试官:vue data 中的数组是怎样监听它的修改? 我:覆写数组方法 面试官:怎么覆写的呢? 我:由于刚做过 Array.prototype.reduce
,没想太多,回答:直接改原型上的方法呗
其实回答的时候我就感觉不太对劲,我刚想改说应该是直接增加实例属性,并且用 Object.defineProperty
修改了 enumeration
为 false
,但是主要面试官也没反问说这有问题就问下一个问题了。
面完研究了下,应该是在原型链上增加一个中间原型来实现的:
javascript
// 获取数组的原型
const arrayProto = Array.prototype;
// 基于数组的原型创建一个新的对象
const middleProto = Object.create(arrayProto);
// 一个要被重写的方法列表
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
for (const method of methodsToPatch) {
// 缓存原始方法
const original = arrayProto[method];
// 定义重写的方法
Object.defineProperty(middleProto, method, {
value: function mutator(...args) {
// 调用原始方法
const result = original.apply(this, args);
// 获取数组对象的 ob 对象,它是一个 Observer 实例
const ob = this.__ob__;
// 通知变更
ob.dep.notify();
return result;
},
enumerable: false,
writable: true,
configurable: true,
});
}
// 假设 arr 是 Vue 管理的数组
// 将 arr 的原型指向重写了方法的对象
Reflect.setPrototypeOf(arr, middleProto);
某电商
TypeScript 体操
实现一个函数,返回值的类型和参数类型相同:
typescript
const identity = <T>(a: T): T => {
// ...
};
点评
考察泛型的理解和使用。
应该是看我简历上写了熟悉类型体操,本来还想说终于可以在面试的时候表演一波体操,但这完全体现不出我的实力呀。
异步加法
要求:
javascript
// 已知异步方法
function add(a, b, callback) {
return setTimeout(() => {
callback(a + b);
});
}
// 实现...
async function sum(...args) {}
// 效果
sum(1, 2, 3).then((value) => console.log(value)); // => 6
答案:
javascript
const _add = (a, b) => {
return new Promise((resolve) => {
add(a, b, (result) => resolve(result));
});
};
async function sum(...args) {
let result = 0;
for (const arg of args) {
result = await _add(result, arg);
}
return result;
}
sum(1, 2, 3).then((value) => console.log(value)); // => 6
点评
考察对 JavaScript
异步的理解和处理。
使用栈实现队列
这是二面的题目,和 leetcode 原题不一样的是多了个时间复杂度要求为常数级 的 printMin
方法:
javascript
class Queue {
put() {}
take() {}
size() {}
}
class Stack {
constructor() {
this.queue = new Queue();
this.min = Infinity;
}
push(value) {
this.queue.put(value);
if (value < this.min) {
this.min = value;
}
}
pop() {
let top;
let min = Infinity;
for (let i = 0, len = this.queue.size(), last = len - 1; i < len; i++) {
const front = this.queue.take();
if (i < last) {
if (front < min) {
min = front;
}
this.queue.put(front);
} else {
top = front;
}
}
this.min = min;
return top;
}
// 要求常数级
printMin() {
if (this.queue.size() === 0) {
throw new Error('Stack is empty!');
}
return this.min;
}
}
总结
题目都不难,做到:
- 不要太紧张
- 多刷刷题保持手感
就没太大问题。
目前还在找工作,如果你对我感兴趣,这是我的在线简历,也欢迎私信或者邮件互加微信交流找工作的事情。