原文:https://dev.to/maxpou/typical-javascript-interview-exercises-explained
题目一
下面的代码,我想要打印出hey amy,结果却打印出hey arnold,为什么?
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"
题目二
我想让下面的代码按顺序输出0,1,2,3,但是运行结果却不符合我的预期,为什么以及怎么解决?
for (var i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
问题
我喜欢这道题,因为它有点棘手,并且它涉及到作用域和JavaScript的事件循环。
这里的陷阱是零延迟。setTimeout(callback, 0)并不意味着callback函数会在0毫秒后执行。
下面是事件循环的经过:
- 当前调用栈设为第一个setTimeout()
- window.setTimeout()是一个web api,在经过0毫秒后,回调函数(在这里是匿名函数)会被放到队列中,而不是调用栈中。
- 当调用栈空闲时,for循环会将第二个setTimeout放进来,然后将第二个callback放到队列中。。。直到循环结束。
- 当for循环结束i === 4时,JS就会执行队列里的callback函数了。每一个console.log(i)会将i打印出来,也就是4。
用动画演示整个事件循环的过程:Loupe (try it it's fun!)
还有一个问题是变量i的作用域的问题,我们可以看到4个setTimeout函数都共享着同一个i = 4(并不是第一个setTimeout的i = 0,第二个i = 1等等)
答案
针对以上问题我们有不同的解决方案:
- 使用立即自执行函数(IIFE),这种“包裹”函数会在它被定义时立即执行:
for (let i = 0; i < 4; i++) {
(function (i) {
setTimeout(() => console.log(i), 0)
})(i)
}
- 用let代替var。let作用于块作用域,能让作用域更容易理解。
for (let i = 0; i < 4; i++) {
setTimeout(() => console.log(i), 0)
}
题目三
我想让下面的代码输出doggo,但结果却输出undefined!
let dog = {
name: 'doggo',
sayName () {
console.log(this.name)
}
}
let sayName = dog.sayName
sayName()
答案
上面的代码执行返回undefined,为什么呢?首先,我们定义了一个对象名叫dog,这个对象有两个属性,分别是name和sayName方法。然后我们再定义了一个变量叫做sayName,并将dog对象的sayName方法赋值给它。最后在当前环境也即是全局环境执行sayName方法,这时函数里的this指向的是window,而window对象下面并没有name属性,所以返回undefined.
- 如果我们要访问dog对象的name属性,我们就要将sayName的执行环境变成dog:
sayName.bind(dog)()
// 或者:
dog.sayName.bind(dog)()
- 直接在dog对象上调用sayName方法:
let dog = {
name: 'doggo',
sayName () {
console.log(this.name)
}
}
dog.sayName() // will log "doggo"
题目四
我想执行bark方法,但却报错了,为什么?
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. 上面的代码我们给函数Dog(同时也是构造器)增加一个bark方法。这在JavaScript中是可以的,因为函数也是对象。(译者补充:bark是Dog的静态方法,fido是Dog的实例对象,实例对象无法访问构造器的静态方法)。
- 虽然fido.bark不是一个方法,但Dog.bark是,所以我们可以用之前的方法来处理,就是用function.prototype.bind来改变this的指向:
var boundedBark = Dog.bark.bind(fido)
boundedBark() // "fido says woof"
- 将bark方法放在Dog的原型上:
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"
我们还可以使用ES2015中的class关键字,但其实它是上面代码的语法糖。
class Dog {
constructor (name) {
this.name = name
}
bark () {
console.log(this.name + ' says woof')
}
}
let fido = new Dog('fido')
fido.bark() // "fido says woof"
题目五
为什么下面代码执行的结果是这样的?
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,结果符合我们的预期,返回false。
- 调用isBig([2]),thing == [2],这时发生了什么呢?当比较一个数组和数字时,数组会转化成数字。这是Abstract Equality Comparison Algorithm的一部分,所以[2] == 2。
我们应该避免使用==
// weird results
[] == ![] // true
[] == false // true
// Non transitive relation
"1" == true // true
"01" == true // true
"01" == "1" // false
题目六(彩蛋无答案)
如何让heroes数组不可变?
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
})
你的答案是什么?
网友评论