JavaScript 面试八股文:闭包、this、原型链

把 JS 最容易问的三个难点(闭包引用、this 绑定、原型链查找)用图解讲清楚,附带面试 30 秒速答模板与常见陷阱。

10 分钟阅读
小明

JavaScript 面试八股文:闭包、this、原型链

JS 面试里出现频率最高的三个概念:闭包、this、原型链。

很多人背过标准答案但讲不清楚原理。今天咱们用实际代码把这三个讲透,别光记书上的概念。


1. 闭包:活的外层作用域绑定

常见错误理解

// 你会说"闭包可以访问外层变量"
// 但真正的闭包是什么?

function outer() {
  let count = 0
  return function inner() {
    count++
    return count
  }
}

const fn = outer()
fn() // 1
fn() // 2
fn() // 3

闭包不是"保存了一份变量",而是"持有对外层作用域的活的绑定"。

经典坑:循环中的闭包

人最容易踩的闭包坑就在 for 循环里。 const fns = for (var i = 0; i < 3; i++) { fns.push(() => console.log(i)) } fns.forEach(fn => fn()) // 输出:3, 3, 3

// 为什么?因为 var 是函数作用域,i 被所有 fn 共享

// 解法 1:用 let(块级作用域) for (let i = 0; i < 3; i++) { fns.push(() => console.log(i)) } // 输出:0, 1, 2

// 解法 2:立即执行函数创建新作用域 for (var i = 0; i < 3; i++) { fns.push((() => { const j = i return () => console.log(j) })()) } // 输出:0, 1, 2


### 面试常见延伸题

**题 1.1:内存泄漏怎么发生?**

```ts
// 常见泄漏:闭包保留了大对象
function createClosure() {
  const hugeData = new Array(1000000).fill('...')
  return {
    getData: () => hugeData[0]  // hugeData 永不释放
  }
}

回答点:闭包会延长引用对象的生命期,所以要及时"断开"不需要的引用。

题 1.2:WeakMap vs Map?

闭包漏掉大对象可以用 WeakMap 让垃圾回收及时处理。

const cache = new WeakMap()
function fn(obj) {
  if (cache.has(obj)) return cache.get(obj)
  const result = expensive(obj)
  cache.set(obj, result)  // 对象被 GC 时自动释放
  return result
}

面试速答模板

"闭包是函数访问外层作用域的能力。Loop 中的闭包常见坑:用 let 隔离或 IIFE 包装。内存要注意别让闭包无限期地保活大对象——WeakMap 是个可选方案。"


2. this:不是"对象",而是"调用上下文"

四种 this 绑定方式

// 1. 默认绑定(全局)
function fn() {
  console.log(this)
}
fn() // 严格模式下是 undefined,非严格是 window/global

// 2. 隐式绑定(对象方法)
const obj = { name: '小明', fn }
obj.fn() // this === obj

// 3. 显式绑定(call/apply/bind)
const obj1 = { name: 'obj1' }
const obj2 = { name: 'obj2' }
fn.call(obj1) // this === obj1
fn.apply(obj2) // this === obj2
const boundFn = fn.bind(obj1)
boundFn() // this === obj1

// 4. new 绑定
function Fn() {
  this.name = '新对象'
}
const instance = new Fn() // this === 新创建的对象

this 优先级

new > call/apply/bind > 对象方法 > 全局

箭头函数没有 this(继承外层)

const obj = {
  name: '小明',
  fn: () => {
    console.log(this) // undefined(箭头函数无 this)
  },
  fn2() {
    return () => {
      console.log(this) // 是 obj(继承 fn2 的 this)
    }
  }
}

// 这也是为什么 React 组件方法要用箭头或 bind
class Button {
  handleClick = () => {
    console.log(this)  // 总是 Button 实例
  }
  // vs
  handleClick2() {
    console.log(this)  // 需要 bind 或直接在 JSX 用 () => this.handleClick2()
  }
}

面试常见延伸题

题 2.1:手写 call/apply/bind?

Function.prototype.myCall = function(ctx, ...args) {
  ctx = ctx || globalThis
  ctx._fn = this
  const result = ctx._fn(...args)
  delete ctx._fn
  return result
}

Function.prototype.myBind = function(ctx, ...args1) {
  // 这题还要考虑 new 时不改 this
  return (...args2) => this.call(ctx, ...args1, ...args2)
}

题 2.2:new 干了什么?

function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)
  const result = Constructor.apply(obj, args)
  return result instanceof Object ? result : obj
}

面试速答模板

"this 是 runtime 绑定,优先级:new > call/bind > 对象方法 > 全局。箭头函数无 this,继承外层。手写 call/bind 考查闭包理解,new 考查原型链和 this 变更。"


3. 原型链:像物种分类,子继承父的特征

三个概念区分

// 1. prototype:函数的原型对象
function Person(name) {
  this.name = name
}
Person常见延伸题

**3.1:手写 instanceof?**

```ts
function myInstanceof(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj)
  while (proto) {
    if (proto === Constructor.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
  return false
}

// 注意:null 是特殊的,typeof null === 'object' 但 Object.getPrototypeOf(null) 报错

题 3.2:Class 和 Constructor Function 的区别?

// 本质上 class 只是 constructor 的语法糖
// 但有约束:
// 1. class 必须 new 调用(不能直接调用)
// 2. class 默认严格模式
// 3. class 方法不可枚举

const obj = {}
Object.getOwnPropertyNames(obj.constructor.prototype)  // ['constructor']
// 而老 function 写法的 prototype 方法是枚举的

题 3.3:原型链污染怎么防?

// 恶意代码可能修改 Object.prototype
Object.prototype.isAdmin = true

const user = {}
console.log(user.isAdmin)  // true!!! 灾难

// 防守:用 Object.create(null) 或 Object.hasOwnProperty 检查
const safeObj = Object.create(null)
console.log(safeObj.isAdmin)  // undefined

const user2 = {}
console.log(user2.hasOwnProperty('isAdmin'))  // false (自身不含)

面试速答模板

"原型链是查找链,沿 proto 向上。instanceof 就是查 prototype 是否在原型链上。Class 是 Constructor 的语法糖但多了约束。要小心原型污染,可用 hasOwnProperty 局部检查或 Object.create(null) 隔离

// 2. proto:对象指向构造函数的 prototype const p1 = new Person('小明') console.log(p1.proto === Person.prototype) // true

// 3. constructor:指向构造函数 console.log(p1.constructor === Person) // true

// 原型链查找:沿着 proto 一级级向上 // p1.greet 查找路径: // p1 -> p1.proto(Person.prototype) -> Person.prototype.greet


### 原型链的图例

p1 (实例对象) ↓ proto Person.prototype ↓ proto Object.prototype ↓ proto null


### 常见坑:修改 prototype

```ts
// 错的做法
Person.prototype = { greet() {} }
// 此时 p1.__proto__ 还是指向旧 prototype!

// 对的做法
Person.prototype.greet = function() {}
// 这样所有实例都能访问

面试 30 秒速答

"原型链是 JS 对象继承的机制。每个对象有 proto 指向构造函数的 prototype,属性查找时沿原型链向上查找。instanceof 就是基于原型链判断。"


总结:JS 基础的三驾马车

  • 闭包:对外层作用域的活的绑定
  • this:调用时的上下文
  • 原型链:对象属性继承的查找路径

搞清这三点,JS 基础就不成问题了。