典型的 JavaScript 面试练习(解释)
几周前,我在推特上发现了一篇非常有趣的博文:《最佳前端 JavaScript 面试题(由前端工程师撰写)》,作者是 Boris Cherny。
正如你可能猜到的那样,作者展示了一些在面试中可以问的有趣问题。这些问题分为四个部分:概念、编码、调试和系统设计。这里,我将重点介绍调试部分。
我真的很喜欢这些问题,因为它们涉及 JavaScript 的特殊性:对象比较、事件循环、范围、this、原型继承以及与抽象相等比较算法相结合的相等运算符。
在阅读解决方案之前,我建议您自己寻找答案。
练习 1
我希望此代码注销“嘿艾米”,但它注销“嘿阿诺德” - 为什么?
function greet (person) {
if (person == { name: 'amy' }) {
return 'hey amy'
} else {
return 'hey arnold'
}
}
greet({ name: 'amy' })
回答
问题如下:{ name: 'amy' } != { name: 'amy' }
当比较两个对象是否相等或严格相等时,JavaScript 会比较相关的内部引用。此时,这两个对象具有相同的属性和相同的值。但在内存中,这是两个不同的对象。
这里的解决方案可能是:
function greet (person) {
if (person.name === 'amy') {
return 'hey amy'
}
return 'hey arnold'
}
greet({ name: 'amy' }) // "hey amy"
练习 2
我希望此代码按顺序输出数字 0、1、2、3,但它并没有按照我的预期执行(这是一个你偶尔会遇到的错误,有些人喜欢在面试中询问它)。
for (var i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
问题
我喜欢这个,因为它有点棘手,它处理范围和 JavaScript 事件循环。
这里的典型陷阱是零延迟。setTimeout(callback, 0)
并不意味着回调将在零毫秒后触发。
以下是事件循环方面发生的情况:
- 当前调用堆栈设置为第一个 setTimeout()。
- windows.setTimeout() 被视为 Web API(为了实现更好的非阻塞 I/O)。因此,调用栈会将这部分代码发送到正确的 Web API。0 毫秒后,回调(此处为匿名函数)将被发送到队列(而不是调用栈)。
- 由于调用堆栈是空闲的,for 循环可以继续到第二个 setTimeout...(在满足此条件 i < 4 后重复)...
- 现在循环结束了
i === 4
。JS 现在可以逐个执行回调队列了。每次 console.log(i) 都会打印 4。
你是否感到迷茫?希望这个动画能更好地帮助你!
用 Loupe 制作的动画(试试看很有趣!)
第二个问题与作用域有关。setTimeout 函数的 4 个实例共享同一个 实例i
。
var foo = 'bim'
// â–¼ this is a reference to variable foo, not his associated value ('bim')
var getFoo = function () { return foo }
foo = 'boum'
getFoo() // 'boum'
回答
因此,有几种可用的解决方案:
- 使用立即调用函数表达式( IIFE)。“包装函数”定义后会立即运行。
for (let i = 0; i < 4; i++) {
(function (i) {
setTimeout(() => console.log(i), 0)
})(i)
}
- 切换到
let
关键字(而不是var
)。这个(新的?)关键字使作用域更容易理解。
for (let i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
练习 3
我希望此代码注销“doggo”,但它注销未定义!
let dog = {
name: 'doggo',
sayName () {
console.log(this.name)
}
}
let sayName = dog.sayName
sayName()
回答
前面的代码返回undefined
。为什么?因为在第一个 let 条件中,我们定义了一个具有两个属性(name 和 sayName() 函数)的对象。然后在第二个 let 中,我们将属性 sayName(一个函数)复制到另一个变量中。然后,我们在它的上下文之外(全局变量中)调用这个变量。sayName() 函数将返回 window.name(如果环境是 Node,则返回全局变量)。然后typeof window.name === "undefined"
。
- ðŸ'Ž(脏的那个)。如果我们想保留 sayName 变量,那么我们需要将 dog 的上下文绑定到它上面:
sayName.bind(dog)()
// or:
dog.sayName.bind(dog)()
这很脏吧? ðŸ¤
- 直接在原始上下文中调用该函数
let dog = {
name: 'doggo',
sayName () {
console.log(this.name)
}
}
dog.sayName() // will log "doggo"
练习 4
我想让我的狗叫()但结果却报错。为什么?
function Dog (name) {
this.name = name
}
Dog.bark = function () {
console.log(this.name + ' says woof')
}
let fido = new Dog('fido')
fido.bark()
回答
我们得到了以下错误TypeError: fido.bark is not a function。在前面的代码中,我们将 bark 函数赋值给了另一个函数 ( Dog()
),而这个函数也是一个构造函数。这有可能吗?因为在 JavaScript 中,函数就是对象。
2个解决方案:
- ðŸ'Ž(脏的那个)。fido.bark 不是一个函数,但
Dog.bark
它是函数。所以让我们使用这个,并this
使用 function.prototype.bind() 来解决这个问题,就像上面的练习一样:
var boundedBark = Dog.bark.bind(fido)
boundedBark() // "fido says woof"
但从我的角度来看,使用 function.prototype.bind() (几乎总是)会导致混乱。
- ðŸ' 在 Dog 的原型上设置 bark()
function Dog (name) {
this.name = name
}
Dog.prototype.bark = function () {
console.log(this.name + ' says woof')
}
let fido = new Dog('fido')
fido.bark() // "fido says woof"
我们还可以使用 class 关键字(ES2015),它只是前面代码的语法糖。
class Dog {
constructor (name) {
this.name = name
}
bark () {
console.log(this.name + ' says woof')
}
}
let fido = new Dog('fido')
fido.bark() // "fido says woof"
练习 5
为什么此代码会返回这样的结果?
function isBig (thing) {
if (thing == 0 || thing == 1 || thing == 2) {
return false
}
return true
}
isBig(1) // false
isBig([2]) // false
isBig([3]) // true
回答
这里我们使用简单的相等运算符(例如==),而不是严格的比较运算符(例如===)。使用此运算符,不必比较相同类型。
isBig(1)
按预期满足条件thing == 1
。isBig([2])
将满足条件thing == 2
。当将数组与数字进行比较时,数组将转换为数字。这是抽象相等比较算法的一部分。根据此算法,如果我们将数字与对象(提醒:在 JS 中数组是对象)进行比较,则此数组将转换为数组。这里,所以里面只有一个项[2] == 2
。
因为这个算法对于大多数普通开发人员来说是晦涩难懂的,所以我们应该避免使用这个运算符(ESLint eqeqeq 规则是你的朋友ðŸ')。
// weird results
[] == ![] // true
[] == false // true
// Non transitive relation
"1" == true // true
"01" == true // true
"01" == "1" // false
练习 6(奖励)
如何保持我的英雄列表的不变性?
const heroes = [
{ name: 'Wolverine', family: 'Marvel', isEvil: false },
{ name: 'Deadpool', family: 'Marvel', isEvil: false },
{ name: 'Magneto', family: 'Marvel', isEvil: true },
{ name: 'Charles Xavier', family: 'Marvel', isEvil: false },
{ name: 'Batman', family: 'DC Comics', isEvil: false },
{ name: 'Harley Quinn', family: 'DC Comics', isEvil: true },
{ name: 'Legolas', family: 'Tolkien', isEvil: false },
{ name: 'Gandalf', family: 'Tolkien', isEvil: false },
{ name: 'Saruman', family: 'Tolkien', isEvil: true }
]
const newHeroes = heroes.map(h => {
h.name = h.name.toUpperCase()
return h
})
你有什么想法吗?🙂
文章来源:https://dev.to/maxpou/典型-javascript-interview-exercises-explained