典型的JavaScript面试题(附答案)

原文: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毫秒后执行。

下面是事件循环的经过:

  1. 当前调用栈设为第一个setTimeout()
  2. window.setTimeout()是一个web api,在经过0毫秒后,回调函数(在这里是匿名函数)会被放到队列中,而不是调用栈中。
  3. 当调用栈空闲时,for循环会将第二个setTimeout放进来,然后将第二个callback放到队列中。。。直到循环结束。
  4. 当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
})

你的答案是什么?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容