交互式 JavaScript 面试练习

执行上下文面试实验室

使用 DeepJS 执行可视化器练习真实的 JavaScript 面试题,先预测输出结果,再一步步追踪结果究竟是如何产生的。

共 31 题,显示 31 题
  1. 为什么函数中的局部变量输出 undefined?

    声明实例化基础

    函数内的 var 声明会在赋值语句执行前创建局部绑定。

    开始解题
  2. 当形参与局部 var 变量同名时会发生什么?

    词法作用域基础

    与已存在的形参同名的 var 声明不会创建新绑定,而是复用形参的绑定。

    开始解题
  3. 函数读取的是调用者的变量,还是定义时所在作用域的变量?

    词法作用域进阶

    JavaScript 采用词法(静态)作用域,函数的作用域链在定义时就已确定,与谁调用它无关。

    开始解题
  4. 全局代码能读取函数内部声明的变量吗?

    词法作用域基础

    函数内部的 var 绑定对外层全局环境不可见。

    开始解题
  5. 为什么 x 开始是函数,后来变成了数字?

    声明实例化进阶

    声明实例化只创建一个绑定,后续赋值会替换其中保存的值。

    开始解题
  6. 为什么 var 会离开 if 代码块?

    声明实例化基础

    var 属于函数或脚本作用域,而不属于 if 代码块。

    开始解题
  7. 未声明的赋值何时会创建全局变量?

    全局绑定进阶

    非严格脚本对无法解析名称的赋值可能在全局对象上创建属性。

    开始解题
  8. 哪些赋值修改参数、局部变量或全局变量?

    词法作用域进阶

    参数 a 和局部 b 遮蔽全局变量,而 c 会解析到外层绑定。

    开始解题
  9. 函数绑定为什么会变成数字?

    函数调用进阶

    函数声明先初始化 a,随后 var 初始化器又用 2 覆盖同一个绑定。

    开始解题
  10. 修改参数会改变外层的基本类型变量吗?

    函数调用基础

    参数接收数字值的副本,并保存在独立的函数绑定中。

    开始解题
  11. 闭包如何保存不断变化的状态?

    闭包进阶

    fn 返回后,返回的函数仍然可以访问 fn 的 n 绑定。

    开始解题
  12. 为什么三个 setTimeout 回调都打印 3,而不是 0、1、2?

    闭包高级

    var 在整个循环中只创建一个共享绑定,所有回调最终都会读到同一个最终值。

    开始解题
  13. 为什么把 var 换成 let 就能修复这个 setTimeout 循环?

    闭包高级

    let 会在每次迭代时创建一个全新的 i 绑定,因此每个回调闭包捕获的都是自己独立的值。

    开始解题
  14. 不用 let,用 IIFE 能修复这个 setTimeout 循环吗?

    闭包高级

    立即调用一个函数并把 i 作为参数传入,会让每次迭代都拥有自己独立的参数绑定。

    开始解题
  15. 同名的局部变量会遮蔽 this.message 吗?

    this 绑定基础

    局部变量和通过 this 读取的属性,本来就活在两个完全不同的命名空间里。

    开始解题
  16. 把方法传给 setTimeout 之后,this 会发生什么?

    this 绑定基础

    把 object.method 当作裸函数引用传出去,会让它和原来的接收者对象脱钩。

    开始解题
  17. 为什么调用同一个函数的两种方式会打印出不同的值?

    this 绑定基础

    this 完全由调用点的语法形式决定,而不是由函数定义在哪里决定。

    开始解题
  18. 为什么箭头函数属性读不到对象上的值?

    this 绑定进阶

    和简写方法不同,箭头函数永远不会把自己的 this 绑定到持有它的对象上。

    开始解题
  19. 为什么最后一行什么都没打印?

    this 绑定进阶

    call 和 apply 会立即以指定的 this 调用函数;bind 只是返回一个新函数。

    开始解题
  20. 把 obj.foo 传进另一个函数,会把 obj 一起带过去吗?

    this 绑定进阶

    函数实参只是一个值——它原来的接收者从来都不是这个值的一部分。

    开始解题
  21. 为什么解构出来的 getName 仍然认得实例?

    this 绑定高级

    在构造函数内部创建的箭头函数,会永久捕获那次构造调用的 this。

    开始解题
  22. filter 的第二个参数如何改变回调里的 this?

    this 绑定进阶

    部分数组遍历方法接受一个可选的 thisArg 参数,用来覆盖回调函数的 this。

    开始解题
  23. 链式调用末尾的 .call() 绑定的是外层调用还是内层调用?

    this 绑定高级

    call 只会附着在它直接调用的那个具体函数值上,且只在那一次调用生效。

    开始解题
  24. 闭包捕获的变量和 this 在这里是如何各自独立演化的?

    闭包高级

    IIFE 的参数 num 被闭包捕获后独立演化,而每一次 this 都是在各自调用点重新解析的——包括全局的 num 绑定本身。

    开始解题
  25. 为什么调用 arguments[0]() 会让 this.length 读到 3?

    this 绑定高级

    通过成员表达式调用一个值时,this 会绑定到这个表达式是从哪个对象上读出来的——哪怕那个对象是当前调用自己的 arguments 对象。

    开始解题
  26. 为什么这些输出是立即执行的,而不是延迟之后才执行?

    this 绑定高级

    函数实参会在接收它们的那次调用真正运行之前先被求值——因此写成实参的 .call() 会立刻执行,而不是等到之后。

    开始解题
  27. 给成员表达式加上括号会改变 this 吗?

    this 绑定进阶

    给属性引用加上括号并不会剥离它——效果和直接调用属性完全一样。

    开始解题
  28. 这四种 this 里,.call() 到底能覆盖哪几种?

    this 绑定高级

    普通方法、对象字面量里的箭头属性、以及两个"返回函数的函数",各自解析 this 的方式都不一样——其中一个甚至从来没有真正执行过 console.log。

    开始解题
  29. 在构造函数里挂载的方法,还遵循调用点规则吗?

    this 绑定高级

    在构造函数内部写 this.foo = function(){...},只是改变了这个函数挂在哪里,并不会改变它自己的 this 之后如何被解析——调用点规则仍然说了算。

    开始解题
  30. 把对象嵌套多一层,会改变 this 的解析方式吗?

    this 绑定高级

    通过 person1.obj.foo1() 这样的路径调用到的方法,this 依然是从调用点解析出来的——多一层属性访问并不会自带一个接收者。

    开始解题
  31. 当四条 this 规则同时出现,到底谁说了算?

    this 绑定高级

    默认绑定、隐式绑定,以及全部三种显式绑定形式——call、apply、bind——再加上 new 和一个箭头函数,全部串在一条链路上,让每条规则"覆盖上一条"的过程在同一个 trace 里清清楚楚地展示出来。

    开始解题