🎯 JS 面试清单 - 第二部分(高级)

2025-06-10

🎯 JS 面试清单 - 第二部分(高级)

您已掌握基础知识✅

现在,您已准备好用一些高级概念给面试官留下深刻印象👨‍🔬

让我们开始吧。

如果你还没有阅读第一部分:https://dev.to/rajatetc/js-interview-checklist-part-1-basics-10k6

📚 Polyfills

❓ 最常问的问题:map、bind

polyfill 是一段代码(通常是 Web 上的 JavaScript),用于在原本不支持现代功能的旧版浏览器上提供现代功能。

  • 让我们来实现它map
// this - array
// this[i] - current value
Array.prototype.myMap = function (cb) {
  var arr = []
  for (var i = 0; i < this.length; i++) {
    arr.push(cb(this[i], i, this))
  }
  return arr
}

const arr = [1, 2, 3]
console.log(arr.myMap((a) => a * 2)) // [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode
  • bind
let name = {
  first: 'Rajat',
  last: 'Gupta',
}
let display = function () {
  console.log(`${this.first} ${this.last}`)
}

Function.prototype.myBind = function (...args) {
  // this -> display
  let obj = this
  return function () {
    obj.call(args[0])
  }
}

// let displayMe = display.bind(name)
let displayMe = display.myBind(name)

displayMe() // Rajat Gupta
Enter fullscreen mode Exit fullscreen mode

但这是基本的实现,假设我们的 display 和 displayMe 函数中有参数

let display = function (city, country) {
  console.log(`${this.first} ${this.last} from ${city}, ${country}`)
}

Function.prototype.myBind = function (...args) {
  let obj = this
  // get the args except the first one
  params = args.slice(1)
  return function (...args2) {
    obj.apply(args[0], [...params, ...args2])
  }
}

let displayMe = display.myBind(name, 'Delhi')
displayMe('India') // Rajat Gupta from Delhi, India
Enter fullscreen mode Exit fullscreen mode

➰ 事件循环

理解异步行为的一个非常重要的主题。

我不会在这里提供不成熟的解释,如果你还没有看过这个视频,我建议你先看一看:

🔒 闭包 ❗ 重要

❓ 解释,输出 Q,优点,缺点

您可能已经使用过它,甚至没有意识到。

本节会有很多复杂的词汇,请耐心听我说完。我们会逐一讲解。

函数与其词法环境捆绑在一起形成闭包

好的,什么是词汇环境?

它本质上是周围的状态——本地记忆以及其父级的词汇环境。

什么?🤯 我知道这有点难。我们举个例子来理解一下。

function x() {
  var a = 7
  function y() {
    console.log(a)
  }
  return y
}

var z = x()
console.log(z) // [Function: y]
z()
Enter fullscreen mode Exit fullscreen mode

当 x 被调用时,y 会被返回。现在,y 正在等待执行。就像一把上了膛的枪,等待着被射击!🔫

所以,当我们最终调用 z 时,y 也被调用了。现在,y 必须记录日志,所以它首先尝试在本地内存a中查找 🔍,但它不在那里。它转到它的父函数。它在那里找到了。a

瞧❗就是这样——这就是结局。

即使函数返回(在上面的例子中是 y),它们仍然记得它们的词法范围(它来自哪里)

完全不相关的引言,只是为了好玩👻:

他们可能会忘记你说过的话 - 但他们永远不会忘记你给他们的感受 - Carl W. Buehner

我发誓这篇文章的其余部分都是合法的🤞请继续阅读。

优点😎

  • 柯里化
let add = function (x) {
  return function (y) {
    console.log(x + y)
  }
}

let addByTwo = add(2)
addByTwo(3)
Enter fullscreen mode Exit fullscreen mode
  • 数据隐藏/封装

假设你想创建一个计数器应用程序。每次调用它,计数都会加 1。但你不想将变量暴露到函数外部。该怎么做?

你猜对了——关闭❗

function Counter() {
  var count = 0
  this.incrementCount = function () {
    count++
    console.log(count)
  }
}

console.log(count) // Error: count is not defined
var adder = new Counter()
adder.incrementCount() // 1
Enter fullscreen mode Exit fullscreen mode

缺点😅

  • 可能会发生内存过度消耗或内存泄漏。

例如,closed-over-variable 不会被垃圾回收。因为即使外部函数已经运行,返回的内部函数仍然拥有对该 closed-over-variable 的引用。

注意:垃圾收集器基本上会自动从内存中删除未使用的变量。

🤝承诺❗重要

❓ 语法、解释、输出 Q

再次强调,这是一个非常 非常重要的主题。

Promise 对象表示异步操作的最终完成(或失败)及其结果值。

它处于以下三种状态之一:

  • pending:初始状态,既未完成也未拒绝
  • 已完成:操作已成功完成
  • 拒绝:操作失败
const promise = new Promise((resolve, reject) => {
  let value = true
  if (value) {
    resolve('hey value is true')
  } else {
    reject('there was an error, value is false')
  }
})

promise
  .then((x) => {
    console.log(x)
  })
  .catch((err) => console.log(err))
Enter fullscreen mode Exit fullscreen mode

注意: resolvereject只是约定俗成的名字。如果你愿意,可以叫它披萨🍕

then/catch我们也可以使用async/await

async function asyncCall() {
  const result = await promise
  console.log(result)
}

asyncCall()
Enter fullscreen mode Exit fullscreen mode

Promise 的优点之一是它的语法更简洁。之前,它简直就是一个回调地狱

Promises 远不止这些。深入探讨需要一篇单独的博客文章。如果你想了解,请在评论区留言。

👪 原型、原型继承

❓ 语法、解释

prototype您可能已经注意到polyfill 示例中的关键字。

从表情符号来看,你可能会认为这类似于其他语言中的继承C++。但事实并非如此。

每当我们在 JS 中创建任何东西(对象、函数)时,JS 引擎都会自动为该东西附加一些属性和方法

这一切都来自于prototypes

__proto__是 JS 放置所有内容的对象

举几个例子吧。启动你的控制台!

let arr = ['Rajat', 'Raj']
console.log(arr.__proto__.forEach)
console.log(arr.__proto__) // same as Array.prototype
console.log(arr.__proto__.__proto__) // same as Object.prototype
console.log(arr.__proto__.__proto__.__proto__) // null
Enter fullscreen mode Exit fullscreen mode

这一切都被称为prototype chain

我们也可以对对象和函数做同样的事情。

我们总能找到Object.prototype幕后真相。所以你可能听说过——JS 里的一切都不过是对象而已 🤯

原型继承

let object = {
  name: 'Rajat',
  city: 'Delhi',
  getIntro: function () {
    console.log(`${this.name}, ${this.city}`)
  },
}

let object2 = {
  name: 'Aditya',
}
Enter fullscreen mode Exit fullscreen mode

注意:请勿以这种方式修改原型。这只是为了方便理解。正确的做法:https://javascript.plainenglish.io/how-prototypal-inheritance-works-in-javascript-and-how-to-convert-it-to-class-based-inheritance-632e31e6350d

object2.__proto__ = object
Enter fullscreen mode Exit fullscreen mode

这样,object2 就可以访问该对象的属性了。所以,现在我们可以这样做:

console.log(object2.city)
Enter fullscreen mode Exit fullscreen mode

这就是原型继承

回到bind之前的 polyfill:

Function.prototype.myBind = function () {
  // code
}
// now we can access this method whenever we create a new function
function a() {
  // code
}

console.log(a.__proto__) // will have myBind method
Enter fullscreen mode Exit fullscreen mode

⚡性能优化

  • 去抖动
  • 节流
  • 缓存
  • 代码拆分
  • 捆绑、压缩
  • 服务器端渲染

注意:一般不会问新生👶但知道这一点很好📝

让我们来讨论一下去抖动和节流。

⛹️‍♂️ 去抖动

另一个受面试官喜爱的话题。

让我们通过创建搜索栏来理解它。

演示:

创建一个简单的输入字段index.html

<input type='text' id='text' />
Enter fullscreen mode Exit fullscreen mode

现在,index.js别忘了把它添加到index.html

const getData = (e) => {
  console.log(e.target.value)
}
const inputField = document.getElementById('text')

const debounce = function (fn, delay) {
  let timer
  return function () {
    let context = this
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(context, arguments)
    }, delay)
  }
}

inputField.addEventListener('keyup', debounce(getData, 300))
Enter fullscreen mode Exit fullscreen mode

首先,我们选中输入并添加一个event listener。然后我们创建了一个 debounce 函数,它接受一个回调函数和一个延迟时间。

现在,我们在 debounce 函数中使用 创建一个计时器setTimeout。这个计时器的作用是确保下一次调用getData仅在 300 毫秒后发生。这就是 debounce 的原理。

另外,我们用clearTimeout它来删除它。不想让太多文件占用内存空间!

呼!理论知识真多。我们来做个有趣的挑战吧。你肯定见过游戏开始前的倒计时(就像10、9、8……中间有一段延迟)。试着写个程序来实现它。

回答

let count = 10

for (let i = 0; i < 10; i++) {
  function timer(i) {
    setTimeout(() => {
      console.log(count)
      count--
    }, i * 500)
  }
  timer(i)
}
Enter fullscreen mode Exit fullscreen mode

你能解决这个问题吗?用了其他方法吗?告诉我你的解决方案。

🛑 节流

我们再举个例子。假设每次窗口调整大小事件发生时,我们都会调用一个开销很大的函数。现在,我们希望这个开销很大的函数在给定的时间间隔内只执行一次。这就是节流。

使用以下代码创建index.html和:index.js

const expensive = () => {
  console.log('expensive')
}

const throttle = (fn, limit) => {
  let context = this
  let flag = true
  return function () {
    if (flag) {
      fn.apply(context, arguments)
      flag = false
    }
    setTimeout(() => {
      flag = true
    }, limit)
  }
}
const func = throttle(expensive, 2000)
window.addEventListener('resize', func)
Enter fullscreen mode Exit fullscreen mode

与去抖动几乎相同。关键区别在于变量flag。只有当它为 true 时,我们才会调用回调函数。并且它被设置为truesetTimeout因此,该值true仅在所需的时间限制之后才会生效。

那么,防抖动和节流之间有什么区别?

让我们以上面的搜索栏🔍为例。当我们对输入字段进行去抖动时,我们规定只有当两个keyup事件之间的差异至少为 300 毫秒时才获取数据。

在节流的情况下,我们仅在一段时间后才进行函数调用。假设您正在搜索栏中搜索百科全书。假设第一次调用是在 ,e我们花了 300 毫秒才到达p。下一次调用将仅在那时进行。其间的所有事件都将被忽略。

总结一下,去抖动是指两个keyup事件之间的时间间隔为 300 毫秒,而节流是指两个函数调用之间的时间间隔为 300 毫秒。简单来说,函数调用是在一定的时间间隔后进行的。

我们就完成了🏁

希望这篇文章对您有所帮助。欢迎点赞、分享和评论👇

直到下一次👋

🗃️参考文献:

鏂囩珷鏉ユ簮锛�https://dev.to/rajatetc/js-interview-checklist-part-2-advanced-359h
PREV
认识 Om 启动框架:🚀 你的全栈忍者瑞士军刀 ✨
NEXT
JavaScript中的作用域链第三章:作用域链