对于编码访谈,我认为最大的问题是遇到陌生的问题(我想这应该是显而易见的),并决定哪种方法在时间限制下最有效。
我在本文中要实现的目标是制定某些问题中发生的模式,以便我们将来处理其衍生性问题时,至少我们不必做太多在我们甚至开始谈论解决方案之前的猜测。
在开始之前,我想提一下,这仅着眼于JavaScript编码问题,而不是UI问题,因为它们可以变化且难以辨别模式。
当我为即将到来的面试做准备时,这里有一个暂定的列表,其中列出了具有概念和可能模式的不同问题类型。
承诺编程
就像标题所暗示的那样,这可以通过重新发明Promise.any
和Promise.all
之类的车轮来测试我们执行异步编程的能力,以便我们知道它如何在引擎盖下工作。
模式:
- 在所有情况下,您都会循环遍历迭代
promiseAny(iterable) // function similar to Promise.any
promiseAll(iterable) // similar to Promise.all
function promiseAll(iterable) {
// ...
iterable.forEach(async (item, index) => {
try {
const value = await item;
iterable[index] = value
// if function is promiseAny instead then resolve(value)
}
catch (err) {
reject(err)
}
})
// ....
}
然后,伪代码如下:
promiseAll(iter) or promiseAny(iter) {
for each element in `iter`, wait for it to load
if you return all promises, transform the input with results fetched from the elements
else resolve the iter as soon as it's fetched
}
重写Array.prototype
方法
这部分的目标确实是让我们习惯该功能的工作流程。
模式:
- 回调函数最多接受四个参数
prev, curr, index, array
-
Array.prototype.filter
和Array.prototype.map
中的回调将返回一个Boolean
,以转换调用方法的数组(称为this
)
Array.prototype.filter = function(callbackFn) {
let copyArr = []
for (let i = 0; i < this.length; i++) {
if (!this[i]) continue;
else if (callbackFn.call(thisArg, this[i], i, this)){
copyArr.push(this[i])
}
}
return copyArr
}
- 对于
Array.prototype.reduce
,回调功能将根据先前的回调结果返回回调结果。
// in Array.prototype.reduce function
Array.prototype.myReduce = function(callbackFn, initVal) {
let startingIndex = ...
for (let i = startingIndex; i < this.length; i++) {
if (!this[i]) continue
result = callbackFn(result, this[i], i, this)
}
return result
}
进行此类问题的一般伪代码将是:
arrayMethod = function (prev, curr, index, array){
declare vars
for each element in this list
- for reduce update `prev` value using the callback function every time taking current `prev`value as input
- for others transform `this` array
}
重写DOM API
即使前端面试问题更少强调数据结构和算法,但使用dom traversal及其之类的诸如我们仍然存在一些此类问题,例如重写getElementsByTagName
和getElementsByClassName
。
模式:
- 对元素及其后代递归使用函数
traverse(element)
检查每个元素的状况getElementsByTagName
和getElementsByClassName
export default function getElementsByTagName(rootElement, tagNameParam) {
const elements = [];
const tagName = tagNameParam.toUpperCase();
function traverse(element) {
if (element == null) return;
if (element.tagName === tagName) elements.push(element);
for (const child of element.children) {
traverse(child);
} // recursing through its children
}
for (const child of rootElement.children) {
traverse(child); // recursing through its root children
}
return elements;
}
function isSubset(a, b) {
return Array.from(a).every((value) => b.contains(value));
}
export default function getElementsByClassName(rootElement, classNames) {
const elements = [];
const classNamesSet = new Set(classNames.trim().split(/\s+/));
function traverse(element) {
if (element == null) {
return;
}
if (isSubset(classNamesSet, element.classList)) {
elements.push(element);
}
for (const child of element.children) {
traverse(child);
}
}
for (const child of rootElement.children) {
traverse(child);
}
return elements;
}
因此,描述两个DOM API之间的共同点的最接近的伪代码将是:
getElementsByAttr(rootElement, tagName = undefined, classNames = undefined) {
declaring empty list of elements
traverse(el) {
check if el satisfies a condition, for ex. its tag name matches or is contained by classNames
if so push it to the new el list
traverse every child of el and its descendants
}
traverse through root elements' offsprings
return updated list of elements
}
功能编程
编码问题将以各种形式和形状出现,但是它们将使用setTimeout
/setInterval
和通过Function.prototype.apply()
/Function.prototype.call()
this
关键字,并使用setTimeout
/setInterval
和调用功能
模式:
为了进行汇总/油门问题,您可能会对测试用例进行图像,以使其看起来像这样。就像您尝试按下电梯按钮时,自上次按下以来的一定时间之后,门终于关闭了。至于节流,您要在要防止愤怒点击时使用它,这意味着您只能单击每x秒一次。就像在Discord的Slowmode聊天中一样。
let i = 0, j = 0;
function increment(val) {
val++;
}
const debouncedIncrement = debounce(increment(i), 100);
const throttledIncrement = throttle(increment(j), 100);
// t = 0
debouncedIncrement(); // i = 0
throttledIncrement(); // j = 1
// t = 50: i is still 0 because 100ms have not passed, throttled remains moot until t=100ms
debouncedIncrement(); // i = 0
throttledIncrement(); // j = 1
// t = 150: Because 100ms have passed since the last debouncedIncrement() at t = 50, increment was invoked and i is now 1
throttledIncrement(); // j = 2
请注意,在两个函数中,可变timeoutId
只能通过debouncedIncrement
或简单地进行评估和修改。闭合将变量隐藏在面纱后面,并防止外部演员玩它,除非他们知道关键字(即哪个功能)可以访问它。
export default function debounce(func, wait) {
var timeoutId = null;
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.call(this, ...args), wait)
}
}
export default function throttle(func, wait) {
let canBeToggledAgain = true;
return (...args) => {
if (canBeToggledAgain) {
func.apply(this, args)
canBeToggledAgain = false
setTimeout(() => canBeToggledAgain = true, wait)
}
}
}
不进一步的ADO,这是上面2个函数
的伪代码
md
function throttle || debounce () {
set timeoutId or toggle boolean
return function if timeout ends ( clear timer if necessary ) or when condition to do so is right
}