🎯 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]
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
但这是基本的实现,假设我们的 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
➰ 事件循环
理解异步行为的一个非常重要的主题。
我不会在这里提供不成熟的解释,如果你还没有看过这个视频,我建议你先看一看:
🔒 闭包 ❗ 重要
❓ 解释,输出 Q,优点,缺点
您可能已经使用过它,甚至没有意识到。
本节会有很多复杂的词汇,请耐心听我说完。我们会逐一讲解。
函数与其词法环境捆绑在一起形成闭包
好的,什么是词汇环境?
它本质上是周围的状态——本地记忆以及其父级的词汇环境。
什么?🤯 我知道这有点难。我们举个例子来理解一下。
function x() {
var a = 7
function y() {
console.log(a)
}
return y
}
var z = x()
console.log(z) // [Function: y]
z()
当 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)
- 数据隐藏/封装
假设你想创建一个计数器应用程序。每次调用它,计数都会加 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
缺点😅
- 可能会发生内存过度消耗或内存泄漏。
例如,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))
注意: resolve
和reject
只是约定俗成的名字。如果你愿意,可以叫它披萨🍕
then/catch
我们也可以使用async/await
async function asyncCall() {
const result = await promise
console.log(result)
}
asyncCall()
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
这一切都被称为prototype chain
我们也可以对对象和函数做同样的事情。
我们总能找到Object.prototype
幕后真相。所以你可能听说过——JS 里的一切都不过是对象而已 🤯
原型继承
let object = {
name: 'Rajat',
city: 'Delhi',
getIntro: function () {
console.log(`${this.name}, ${this.city}`)
},
}
let object2 = {
name: 'Aditya',
}
注意:请勿以这种方式修改原型。这只是为了方便理解。正确的做法:https://javascript.plainenglish.io/how-prototypal-inheritance-works-in-javascript-and-how-to-convert-it-to-class-based-inheritance-632e31e6350d
object2.__proto__ = object
这样,object2 就可以访问该对象的属性了。所以,现在我们可以这样做:
console.log(object2.city)
这就是原型继承。
回到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
⚡性能优化
- 去抖动
- 节流
- 缓存
- 代码拆分
- 捆绑、压缩
- 服务器端渲染
注意:一般不会问新生👶但知道这一点很好📝
让我们来讨论一下去抖动和节流。
⛹️♂️ 去抖动
另一个受面试官喜爱的话题。
让我们通过创建搜索栏来理解它。
演示:
创建一个简单的输入字段index.html
<input type='text' id='text' />
现在,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))
首先,我们选中输入并添加一个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)
}
你能解决这个问题吗?用了其他方法吗?告诉我你的解决方案。
🛑 节流
我们再举个例子。假设每次窗口调整大小事件发生时,我们都会调用一个开销很大的函数。现在,我们希望这个开销很大的函数在给定的时间间隔内只执行一次。这就是节流。
使用以下代码创建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)
与去抖动几乎相同。关键区别在于变量flag
。只有当它为 true 时,我们才会调用回调函数。并且它被设置为true
。setTimeout
因此,该值true
仅在所需的时间限制之后才会生效。
那么,防抖动和节流之间有什么区别?
让我们以上面的搜索栏🔍为例。当我们对输入字段进行去抖动时,我们规定只有当两个keyup
事件之间的差异至少为 300 毫秒时才获取数据。
在节流的情况下,我们仅在一段时间后才进行函数调用。假设您正在搜索栏中搜索百科全书。假设第一次调用是在 ,e
我们花了 300 毫秒才到达p
。下一次调用将仅在那时进行。其间的所有事件都将被忽略。
总结一下,去抖动是指两个keyup
事件之间的时间间隔为 300 毫秒,而节流是指两个函数调用之间的时间间隔为 300 毫秒。简单来说,函数调用是在一定的时间间隔后进行的。
我们就完成了🏁
希望这篇文章对您有所帮助。欢迎点赞、分享和评论👇
直到下一次👋
🗃️参考文献:
- MDN 文档:https://developer.mozilla.org/en-US/
- 阿克谢·赛尼:https://www.youtube.com/channel/UC3N9i_KvKZYP4F84FPIzgPQ
- 编码爱好者:https://www.youtube.com/channel/UCMZFwxv5l-XtKi693qMJptA