备忘录进行前端访谈
#javascript #前端 #career #面试

对于编码访谈,我认为最大的问题是遇到陌生的问题(我想这应该是显而易见的),并决定哪种方法在时间限制下最有效。

我在本文中要实现的目标是制定某些问题中发生的模式,以便我们将来处理其衍生性问题时,至少我们不必做太多在我们甚至开始谈论解决方案之前的猜测。

在开始之前,我想提一下,这仅着眼于JavaScript编码问题,而不是UI问题,因为它们可以变化且难以辨别模式。

当我为即将到来的面试做准备时,这里有一个暂定的列表,其中列出了具有概念和可能模式的不同问题类型。

承诺编程

就像标题所暗示的那样,这可以通过重新发明Promise.anyPromise.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.filterArray.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及其之类的诸如我们仍然存在一些此类问题,例如重写getElementsByTagNamegetElementsByClassName

模式:

  • 对元素及其后代递归使用函数traverse(element)检查每个元素的状况 getElementsByTagNamegetElementsByClassName
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
}