“面试官到底想听什么?”:8 个 ES6+ 核心概念的高分解析,让你秒杀 90% 的竞争者

136 阅读5分钟

前言

最近在技术面试和代码审查中,我发现很多开发者对ES6+的新特性停留在"会用"层面,但问到"为什么这样设计"就答不上来了。

比如问到Class继承,能说出extends和super的用法,但追问"Class继承和原型继承在底层有什么区别",很多人就卡壳了。

这就是典型的"API工程师"现象:知道怎么用,不懂为什么这样设计。

今天这8道ES6+新特性题目,每道题我都会从三个维度解析:

  • 语言设计思想:这个特性解决了什么问题
  • 底层实现原理:语法糖背后的工作机制
  • 实际应用场景:在项目中如何正确使用

每题都配"速记公式"和"标准答案",帮你从"会用"到"用好"。

准备好了吗?让我们开始这场ES6+的深度探索之旅!

1. ES6有哪些新特性?箭头函数与普通函数的区别?

速记公式:ES6特性多,箭头函数无this无arguments

  • ES6核心特性:let/const、箭头函数、模板字符串、解构赋值、模块化、Promise等
  • 箭头函数:无自己的this、无arguments、不能作为构造函数、无prototype
  • 适用场景:回调函数、需要继承外层this的场景

标准答案

ES6重要新特性:

  1. 变量声明:let/const 块级作用域
  2. 箭头函数() => {} 简洁语法
  3. 模板字符串`Hello ${name}` 支持插值
  4. 解构赋值const {name, age} = obj
  5. 模块化:import/export
  6. Promise:异步编程解决方案
  7. Class:类语法糖
  8. 默认参数function(a = 1) {}
  9. 扩展运算符...arr
  10. Symbol:唯一值类型
  11. Set/Map:新的数据结构

箭头函数与普通函数区别:

// 1. this指向
const obj = {
  name: 'Tom',
  normal: function() {
    console.log(this.name) // 'Tom' - 指向obj
  },
  arrow: () => {
    console.log(this.name) // undefined - 指向外层作用域
  }
}

// 2. 构造函数
function Person(name) {
  this.name = name
}
const ArrowPerson = (name) => {
  this.name = name // 错误,不能作为构造函数
}
// new ArrowPerson() // TypeError

// 3. arguments对象
function normal() {
  console.log(arguments) // Arguments(3) [1, 2, 3]
}
const arrow = () => {
  console.log(arguments) // ReferenceError
}
// normal(1, 2, 3)
// arrow(1, 2, 3)

// 4. prototype属性
console.log(normal.prototype) // {constructor: ƒ}
console.log(arrow.prototype)  // undefined

面试官真正想听什么

这题考察你对ES6的掌握程度和实际应用经验。

很多人只知道箭头函数语法简洁,但说不清this指向的根本区别。面试官想看你是否理解函数执行机制。

加分回答

"在React项目中,箭头函数解决了this绑定问题:

class MyComponent extends React.Component {
  state = {count: 0}
  
  // 普通函数需要bind
  handleClick() {
    this.setState({count: this.state.count + 1}) // this可能为undefined
  }
  
  // 箭头函数自动绑定this
  handleArrowClick = () => {
    this.setState({count: this.state.count + 1}) // this总是指向组件实例
  }
  
  render() {
    return (
      <button onClick={this.handleArrowClick}>点击</button>
    )
  }
}

我的使用原则:

  • 需要动态this时用普通函数(如事件处理函数)
  • 需要固定this时用箭头函数(如React类方法、定时器回调)
  • 对象方法用普通函数,需要访问对象属性时
  • 回调函数用箭头函数,避免this丢失

理解这些区别让我在代码重构时更有信心。"

2. ES6的模块化?import和require的区别?

速记公式:ES6静编译,CommonJS动态行,import需顶层,require可任意

  • ES6模块:静态编译,输出值的引用,import/export
  • CommonJS:动态执行,输出值的拷贝,require/module.exports
  • 关键区别:编译时vs运行时、引用vs拷贝、异步vs同步

标准答案

ES6模块化特点:

  • 使用import/export语法
  • 静态编译,在编译阶段确定依赖关系
  • 输出值的引用(动态绑定)
  • 支持异步加载
  • 严格模式默认开启

CommonJS模块化特点:

  • 使用require/module.exports
  • 动态加载,在运行时确定依赖
  • 输出值的拷贝
  • 同步加载
  • 主要用于Node.js
// ES6模块
// math.js
export const PI = 3.14
export function add(a, b) { return a + b }

// main.js
import { PI, add } from './math.js'
import * as math from './math.js' // 整体导入

// CommonJS模块
// math.js
const PI = 3.14
function add(a, b) { return a + b }
module.exports = { PI, add }

// main.js
const { PI, add } = require('./math.js')
const math = require('./math.js') // 整体导入

核心区别:

// ES6 - 值的引用
// counter.js
export let count = 0
export function increment() { count++ }

// main.js
import { count, increment } from './counter.js'
console.log(count) // 0
increment()
console.log(count) // 1 - 值被更新

// CommonJS - 值的拷贝
// counter.js
let count = 0
function increment() { count++ }
module.exports = { count, increment }

// main.js
const { count, increment } = require('./counter.js')
console.log(count) // 0
increment()
console.log(count) // 0 - 值不变(拷贝的副本)

面试官真正想听什么

这题考察你对模块化演进的理解和工程化实践能力。

模块化是现代前端开发的基石,能说清区别说明你有扎实的构建工具使用经验。

加分回答

"在Webpack项目中,两种模块可以混用,但要注意区别:

// ES6导入CommonJS
import React from 'react' // 实际上react是CommonJS模块

// CommonJS导入ES6
const React = require('react') // Webpack会处理这种混用

Tree Shaking依赖ES6的静态结构:

// utils.js
export function usedFunction() { /* 被使用 */ }
export function unusedFunction() { /* 未使用 */ }

// main.js  
import { usedFunction } from './utils.js'
// unusedFunction会被Tree Shaking移除

动态导入实现代码分割:

// 静态导入
import { heavyModule } from './heavyModule.js'

// 动态导入 - 按需加载
const loadHeavyModule = async () => {
  const { heavyModule } = await import('./heavyModule.js')
  heavyModule.doWork()
}

// React懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'))

理解模块机制让我在性能优化时更有针对性。"

3. 什么是模板字符串?Symbol类型的作用?

速记公式:模板字符串可插值,Symbol类型唯一值

  • 模板字符串:支持多行字符串、变量插值、标签模板
  • Symbol:唯一且不可变的值,适合做对象键名、定义常量
  • 应用场景:字符串拼接、防止属性名冲突、定义私有属性

标准答案

模板字符串特性:

// 1. 基本用法
const name = 'Tom'
const age = 25
const greeting = `Hello, I'm ${name} and I'm ${age} years old.`

// 2. 多行字符串
const multiLine = `
  This is 
  a multi-line
  string
`

// 3. 表达式运算
const a = 5, b = 10
const calculation = `The sum is ${a + b}` // "The sum is 15"

// 4. 标签模板
function tag(strings, ...values) {
  console.log(strings) // ["Hello ", "!"]
  console.log(values)  // ["Tom"]
  return 'Processed string'
}
const result = tag`Hello ${name}!`

Symbol类型特性:

// 1. 创建唯一值
const sym1 = Symbol()
const sym2 = Symbol()
console.log(sym1 === sym2) // false

const sym3 = Symbol('description') // 可选的描述符
const sym4 = Symbol('description')
console.log(sym3 === sym4) // false - 即使描述相同也不相等

// 2. 作为对象键名
const obj = {
  [Symbol('key')]: 'value',
  normalKey: 'normal value'
}

// 3. 防止属性名冲突
// 自定义迭代器
const myIterable = {
  [Symbol.iterator]: function* () {
    yield 1
    yield 2
    yield 3
  }
}

// 4. 定义常量
const LOG_LEVEL = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  ERROR: Symbol('error')
}

面试官真正想听什么

这题考察你对ES6新特性的理解深度和实际应用能力。

模板字符串和Symbol看似简单,但在特定场景下非常有用。面试官想看你是否真的理解它们的价值。

加分回答

"模板字符串在React项目中很有用:

// 动态className
const Button = ({ primary, size }) => {
  const className = `
    btn 
    ${primary ? 'btn-primary' : 'btn-secondary'}
    ${size === 'large' ? 'btn-lg' : ''}
  `
  return <button className={className}>Click</button>
}

// SQL查询构建(Node.js)
const buildQuery = (table, conditions) => {
  const whereClause = conditions.map(cond => `${cond.field} = ${cond.value}`).join(' AND ')
  return `SELECT * FROM ${table} WHERE ${whereClause}`
}

Symbol在避免属性冲突中的应用:

// 为第三方库添加元数据而不影响原有属性
const myLib = {
  // 库的原有方法
  method1() { /* ... */ },
  method2() { /* ... */ }
}

// 添加元数据而不污染原有接口
const METADATA = Symbol('metadata')
myLib[METADATA] = {
  version: '1.0.0',
  author: 'My Team'
}

// 定义"私有"属性(不是真正的私有,但不会意外访问)
const _privateData = Symbol('privateData')
class MyClass {
  constructor() {
    this[_privateData] = 'secret'
  }
  
  getSecret() {
    return this[_privateData]
  }
}

这些特性让代码更安全、更易维护。"

4. Set和Map的使用场景?与数组和对象的区别?

速记公式:Set值唯一,Map键任意,数组有序可重,对象字符键

  • Set:值唯一的集合,快速查找、去重
  • Map:键值对集合,键可以是任意类型
  • 对比:Set vs 数组,Map vs 对象

标准答案

Set的使用场景:

// 1. 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5]
const unique = [...new Set(numbers)] // [1, 2, 3, 4, 5]

// 2. 成员存在性检查(比数组快)
const tags = new Set(['javascript', 'html', 'css'])
console.log(tags.has('javascript')) // true - O(1)时间复杂度

// 3. 集合运算
const setA = new Set([1, 2, 3])
const setB = new Set([2, 3, 4])

// 并集
const union = new Set([...setA, ...setB]) // {1, 2, 3, 4}

// 交集  
const intersection = new Set([...setA].filter(x => setB.has(x))) // {2, 3}

// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))) // {1}

Map的使用场景:

// 1. 键可以是任意类型
const map = new Map()
const keyObj = {}
const keyFunc = function() {}

map.set(keyObj, 'object value')
map.set(keyFunc, 'function value')
map.set(1, 'number value')

// 2. 维护顺序(插入顺序)
const orderedMap = new Map()
orderedMap.set('z', 1)
orderedMap.set('a', 2)
console.log([...orderedMap.keys()]) // ['z', 'a'] - 保持插入顺序

// 3. 对象元数据存储
const user = {id: 1, name: 'Tom'}
const metadata = new Map()
metadata.set(user, {createdAt: new Date(), version: 2})

// 4. 缓存实现
const cache = new Map()
function getData(key) {
  if (cache.has(key)) {
    return cache.get(key)
  }
  const data = fetchData(key)
  cache.set(key, data)
  return data
}

数据结构对比:

特性数组对象SetMap
键类型数字索引字符串/Symbol值本身任意类型
顺序有序无序(ES6后有序)插入顺序插入顺序
去重允许重复键唯一值唯一键唯一
查找性能O(n)O(1)O(1)O(1)

面试官真正想听什么

这题考察你对不同数据结构的理解和选择能力。

选择合适的结构能显著提升代码性能和可读性。面试官想看你是否有数据结构思维。

加分回答

"在实际项目中,我根据需求选择数据结构:

用Set处理标签系统:

class TagSystem {
  constructor() {
    this.tags = new Set()
  }
  
  addTag(tag) {
    this.tags.add(tag.toLowerCase())
  }
  
  hasTag(tag) {
    return this.tags.has(tag.toLowerCase())
  }
  
  removeTag(tag) {
    this.tags.delete(tag.toLowerCase())
  }
  
  getTags() {
    return [...this.tags]
  }
}

用Map实现LRU缓存:

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity
    this.cache = new Map()
  }
  
  get(key) {
    if (!this.cache.has(key)) return -1
    
    const value = this.cache.get(key)
    // 刷新为最新使用
    this.cache.delete(key)
    this.cache.set(key, value)
    return value
  }
  
  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    } else if (this.cache.size >= this.capacity) {
      // 删除最久未使用的
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
}

理解这些数据结构的特性,让我在解决复杂问题时更有把握。"

5. 什么是装饰器模式?代理模式的作用?

速记公式:装饰器增强功能,代理控制访问

  • 装饰器模式:动态给对象添加功能,不改变原对象
  • 代理模式:控制对原对象的访问,添加额外逻辑
  • 应用场景:AOP、缓存、权限控制、日志记录

标准答案

装饰器模式: 在不改变原对象的基础上,动态地给对象添加职责。

// 简单的装饰器实现
function withLogging(fn) {
  return function(...args) {
    console.log(`调用函数: ${fn.name}, 参数: ${args}`)
    const result = fn.apply(this, args)
    console.log(`函数返回: ${result}`)
    return result
  }
}

// 使用装饰器
function add(a, b) {
  return a + b
}

const addWithLogging = withLogging(add)
addWithLogging(2, 3) 
// 输出: 调用函数: add, 参数: 2,3
// 输出: 函数返回: 5

ES7装饰器语法:

// 类装饰器
@log
class MyClass {
  @readonly
  method() { }
  
  @deprecate
  oldMethod() { }
}

代理模式: 为其他对象提供一种代理以控制对这个对象的访问。

// 保护代理 - 权限控制
const protectedObject = {
  sensitiveData: 'secret',
  publicData: 'open'
}

const protectionProxy = new Proxy(protectedObject, {
  get(target, property) {
    if (property === 'sensitiveData') {
      throw new Error('Access denied')
    }
    return target[property]
  },
  
  set(target, property, value) {
    if (property === 'sensitiveData') {
      throw new Error('Cannot modify sensitive data')
    }
    target[property] = value
    return true
  }
})

console.log(protectionProxy.publicData) // 'open'
// console.log(protectionProxy.sensitiveData) // Error: Access denied

面试官真正想听什么

这题考察你对设计模式的理解和在实际项目中的应用能力。

设计模式体现了编程的抽象思维,能熟练使用说明你有良好的架构设计能力。

加分回答

"在React项目中,我使用高阶组件(HOC)实现装饰器模式:

// 权限检查装饰器
const withAuth = (WrappedComponent) => {
  return class extends React.Component {
    componentDidMount() {
      if (!this.props.isAuthenticated) {
        this.props.history.push('/login')
      }
    }
    
    render() {
      return this.props.isAuthenticated ? 
        <WrappedComponent {...this.props} /> : 
        <div>Loading...</div>
    }
  }
}

// 使用
const ProtectedPage = withAuth(MyPage)

代理模式在缓存中的应用:

比如我在电商项目中用 缓存代理 优化商品搜索:

const searchProxy = new Proxy(searchAPI, {
  apply(target, thisArg, args) {
    const [keyword] = args
    const cacheKey = `search_${keyword}`
    
    if (cache.has(cacheKey)) {
      return Promise.resolve(cache.get(cacheKey))
    }
    
    return target.apply(thisArg, args).then(result => {
      cache.set(cacheKey, result)
      return result
    })
  }
})

这些模式让代码更可维护、更易扩展。"

6. 什么是高阶函数?JavaScript中的函数式编程?

速记公式:高阶函数接函数,函数式编程纯函数

  • 高阶函数:接收函数作为参数或返回函数的函数
  • 函数式编程:纯函数、不可变性、函数组合、避免副作用
  • 核心概念:map/filter/reduce、柯里化、函数组合

标准答案

高阶函数: 接收函数作为参数,或返回函数作为结果的函数。

// 1. 接收函数作为参数
function arrayOperation(arr, operation) {
  const result = []
  for (let i = 0; i < arr.length; i++) {
    result.push(operation(arr[i]))
  }
  return result
}

const numbers = [1, 2, 3, 4]
const doubled = arrayOperation(numbers, x => x * 2) // [2, 4, 6, 8]

// 2. 返回函数
function createMultiplier(multiplier) {
  return function(x) {
    return x * multiplier
  }
}

const double = createMultiplier(2)
const triple = createMultiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15

// 3. 内置高阶函数
const processed = numbers
  .map(x => x * 2)      // 映射
  .filter(x => x > 4)   // 过滤
  .reduce((a, b) => a + b) // 归并

函数式编程核心原则:

// 1. 纯函数 - 相同输入总是得到相同输出,无副作用
// 不纯的函数
let counter = 0
function impureIncrement() {
  counter++ // 修改外部状态
  return counter
}

// 纯函数
function pureIncrement(num) {
  return num + 1 // 不修改外部状态
}

// 2. 不可变性 - 不修改原数据,返回新数据
const original = [1, 2, 3]
// 不推荐:修改原数组
// original.push(4)

// 推荐:返回新数组
const updated = [...original, 4]

// 3. 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x)

const add5 = x => x + 5
const multiply2 = x => x * 2
const subtract3 = x => x - 3

const complexOperation = compose(subtract3, multiply2, add5)
console.log(complexOperation(10)) // (10 + 5) * 2 - 3 = 27

面试官真正想听什么

这题考察你对编程范式的理解和函数式思维的应用。

函数式编程能写出更可预测、更易测试的代码。面试官想看你是否有现代编程思维。

加分回答

"在React项目中,我大量使用函数式编程思想:

使用纯组件:

// 纯函数组件 - 同样的props总是渲染同样的结果
const UserProfile = ({ user, onUpdate }) => {
  return (
    <div>
      <h1>{user.name}</h1>
      <button onClick={() => onUpdate(user.id)}>Update</button>
    </div>
  )
}

Redux中的纯函数reducer:

// 纯函数 - 不修改原state,返回新state
function todoReducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      }
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload 
            ? {...todo, completed: !todo.completed}
            : todo
        )
      }
    default:
      return state
  }
}

函数组合处理数据流:

// 数据处理管道
const processUserData = compose(
  formatForDisplay,
  addCalculatedFields,
  filterInactiveUsers,
  normalizeData
)

const displayData = processUserData(rawApiResponse)

这种编程风格让代码更易推理、更易测试。"

7. 什么是柯里化?如何实现?

速记公式:柯里化分参数,返回函数链

  • 柯里化:将多参数函数转换为一系列单参数函数
  • 实现方式:递归、闭包、参数收集
  • 应用场景:参数复用、延迟计算、函数组合

标准答案

柯里化概念: 将接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下参数的新函数。

// 普通函数
function add(a, b, c) {
  return a + b + c
}

// 柯里化版本
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c
    }
  }
}

console.log(add(1, 2, 3))       // 6
console.log(curriedAdd(1)(2)(3)) // 6

// 部分应用
const add5 = curriedAdd(5)
const add5And10 = add5(10)
console.log(add5And10(15)) // 30

通用柯里化实现:

// 方法1:固定参数数量
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args)
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2))
      }
    }
  }
}

// 方法2:支持占位符
function curryWithPlaceholder(fn) {
  return function curried(...args) {
    // 检查参数是否足够且没有占位符
    const complete = args.length >= fn.length && 
      !args.slice(0, fn.length).includes(curry.placeholder)
    
    if (complete) {
      return fn.apply(this, args)
    }
    
    return function(...newArgs) {
      // 合并参数,处理占位符
      const merged = []
      let argsIndex = 0, newArgsIndex = 0
      
      while (argsIndex < args.length && newArgsIndex < newArgs.length) {
        if (args[argsIndex] === curry.placeholder) {
          merged.push(newArgs[newArgsIndex++])
          argsIndex++
        } else {
          merged.push(args[argsIndex++])
        }
      }
      
      // 添加剩余参数
      while (argsIndex < args.length) {
        merged.push(args[argsIndex++])
      }
      while (newArgsIndex < newArgs.length) {
        merged.push(newArgs[newArgsIndex++])
      }
      
      return curried.apply(this, merged)
    }
  }
}

curry.placeholder = Symbol('placeholder')

实际应用示例:

// 1. 参数复用
const match = curry((what, str) => str.includes(what))
const hasSpace = match(' ')
const hasNumber = match(/\d/)

console.log(hasSpace('hello world')) // true
console.log(hasNumber('abc123'))     // true

// 2. 延迟计算
const fetchData = curry((url, params, callback) => {
  // 模拟API调用
  setTimeout(() => callback(`Data from ${url} with ${JSON.stringify(params)}`), 100)
})

const fetchUser = fetchData('/api/users')
const fetchUserWithLimit = fetchUser({limit: 10})

fetchUserWithLimit(console.log) // 延迟执行

// 3. 函数组合
const map = curry((fn, array) => array.map(fn))
const filter = curry((fn, array) => array.filter(fn))

const doubleAll = map(x => x * 2)
const getEven = filter(x => x % 2 === 0)

const processNumbers = compose(doubleAll, getEven)
console.log(processNumbers([1, 2, 3, 4, 5])) // [4, 8]

面试官真正想听什么

这题考察你对函数式编程高级技巧的掌握程度。

柯里化是函数式编程的重要技术,能理解并应用说明你有扎实的编程功底。

加分回答

"在真实项目中,柯里化让代码更灵活:

配置化函数创建:

// 创建日志函数
const createLogger = curry((prefix, level, message) => {
  console.log(`[${prefix}] ${level}: ${message}`)
})

const appLogger = createLogger('MyApp')
const appError = appLogger('ERROR')
const appDebug = appLogger('DEBUG')

appError('Something went wrong') // [MyApp] ERROR: Something went wrong
appDebug('User logged in')       // [MyApp] DEBUG: User logged in

事件处理函数复用:

// React中的事件处理
const handleEvent = curry((action, data, event) => {
  event.preventDefault()
  action(data)
})

const handleClick = handleEvent(apiCall)
const handleSubmit = handleEvent(formSubmit)

// 在组件中使用
<button onClick={handleClick(userId)}>Delete</button>
<form onSubmit={handleSubmit(formData)}>Submit</form>

使用lodash/fp的柯里化:

import { curry, map, filter, flow } from 'lodash/fp'

// 自动柯里化
const processUsers = flow(
  filter(user => user.active),
  map(user => ({...user, name: user.name.toUpperCase()})),
  map(user => ({...user, score: user.points * 10}))
)

const activeUsersWithScore = processUsers(users)

柯里化让函数变得更通用、更易组合。"

8. ES6中Class继承与原型继承有什么区别?

速记公式:Class语法糖,继承更清晰,本质仍原型

  • Class继承:ES6语法糖,extends/super关键字,更接近传统面向对象
  • 原型继承:ES5及之前的方式,直接操作prototype和__proto__
  • 本质相同:Class底层仍基于原型链机制

标准答案

Class继承语法:

// ES6 Class 继承
class Animal {
  constructor(name) {
    this.name = name
    this.type = 'animal'
  }
  
  speak() {
    console.log(`${this.name} makes a sound`)
  }
  
  static isAnimal(obj) {
    return obj instanceof Animal
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name) // 必须调用super
    this.breed = breed
  }
  
  speak() {
    super.speak() // 调用父类方法
    console.log(`${this.name} barks`)
  }
  
  fetch() {
    console.log(`${this.name} fetches the ball`)
  }
}

const myDog = new Dog('Buddy', 'Golden Retriever')
myDog.speak() 
// Buddy makes a sound
// Buddy barks
console.log(myDog instanceof Dog)    // true
console.log(myDog instanceof Animal) // true

原型继承语法:

// ES5 原型继承
function Animal(name) {
  this.name = name
  this.type = 'animal'
}

Animal.prototype.speak = function() {
  console.log(this.name + ' makes a sound')
}

Animal.isAnimal = function(obj) {
  return obj instanceof Animal
}

function Dog(name, breed) {
  Animal.call(this, name) // 继承实例属性
  this.breed = breed
}

// 继承原型方法
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

Dog.prototype.speak = function() {
  Animal.prototype.speak.call(this) // 调用父类方法
  console.log(this.name + ' barks')
}

Dog.prototype.fetch = function() {
  console.log(this.name + ' fetches the ball')
}

var myDog = new Dog('Buddy', 'Golden Retriever')
myDog.speak()
// Buddy makes a sound  
// Buddy barks

核心区别对比

特性Class继承原型继承
语法简洁,extends/super关键字繁琐,手动设置prototype
构造函数必须调用super()使用Parent.call(this)
静态方法支持static关键字手动添加到构造函数
方法定义直接在类中定义通过prototype添加
继承完整性自动处理完整原型链需手动处理constructor指向
私有字段支持#私有字段(ES2022)无原生支持
可读性高,结构清晰低,代码分散
底层机制基于原型链的语法糖直接操作原型链
// 验证底层机制相同
class A {}
class B extends A {}

function C() {}
function D() {}
D.prototype = Object.create(C.prototype)

console.log(B.prototype.__proto__ === A.prototype) // true
console.log(D.prototype.__proto__ === C.prototype) // true

const b = new B()
const d = new D()
console.log(b.__proto__.__proto__ === A.prototype) // true  
console.log(d.__proto__.__proto__ === C.prototype) // true

面试官真正想听什么

这题考察你对JavaScript面向对象演进的理解深度。

很多人认为Class是全新的继承机制,其实它只是语法糖。面试官想看你是否理解JavaScript的原型本质。

加分回答

"在大型项目中,Class让代码更易维护:

React组件中的Class继承:

class BaseComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { loading: false }
  }
  
  // 公共方法
  setLoading = (loading) => {
    this.setState({ loading })
  }
  
  // 错误处理
  handleError = (error) => {
    console.error('Component error:', error)
    this.setLoading(false)
  }
}

class UserList extends BaseComponent {
  constructor(props) {
    super(props)
    this.state = {
      ...this.state, // 继承基类state
      users: []
    }
  }
  
  async fetchUsers() {
    this.setLoading(true)
    try {
      const users = await api.getUsers()
      this.setState({ users })
    } catch (error) {
      this.handleError(error) // 使用基类方法
    } finally {
      this.setLoading(false)
    }
  }
}

理解super的关键作用:

class Parent {
  constructor(name) {
    this.name = name
    this.initialized = true
  }
}

class Child extends Parent {
  constructor(name, age) {
    // 不调用super的后果
    // ReferenceError: Must call super constructor in derived class before accessing 'this'
    // this.age = age 
    
    super(name) // 创建this上下文
    this.age = age // 现在可以安全使用this
  }
}

静态方法和属性的继承:

class Database {
  static connectionCount = 0
  
  constructor() {
    Database.connectionCount++
  }
  
  static getConnectionCount() {
    return this.connectionCount
  }
}

class MySQL extends Database {
  static type = 'MySQL'
  
  static getInfo() {
    return `${this.type} - Connections: ${super.getConnectionCount()}`
  }
}

new MySQL()
new MySQL()
console.log(MySQL.getInfo()) // "MySQL - Connections: 2"
console.log(MySQL.getConnectionCount()) // 2 - 继承静态方法

混入模式(Mixin)的实现:

// 使用Class实现混入
const Loggable = (Base) => class extends Base {
  log(message) {
    console.log(`[${this.constructor.name}] ${message}`)
  }
}

const Serializable = (Base) => class extends Base {
  serialize() {
    return JSON.stringify(this)
  }
}

class User {
  constructor(name) {
    this.name = name
  }
}

class EnhancedUser extends Loggable(Serializable(User)) {
  constructor(name, role) {
    super(name)
    this.role = role
  }
}

const user = new EnhancedUser('Tom', 'admin')
user.log('User created') // [EnhancedUser] User created
console.log(user.serialize()) // {"name":"Tom","role":"admin"}

Class语法让面向对象编程在JavaScript中更自然、更易理解。"

减分回答

❌ "Class是完全新的继承机制"(不理解原型本质)

❌ "Class性能比原型继承好"(底层机制相同,性能无差异)

❌ "super就是调用父类构造函数"(理解不全面,super也用于调用父类方法)

常见追问

Q: Class中的箭头函数和普通方法有什么区别?

A: 箭头函数自动绑定this到实例,普通方法需要手动绑定;箭头函数在原型上,普通方法在实例上。

Q: 如何实现多重继承?

A: JavaScript不支持多重继承,但可以通过混入(Mixin)模式模拟,或者使用组合代替继承。

Q: 什么时候该用Class,什么时候用原型?

A: 新项目推荐用Class,语法清晰;维护老代码或需要更细粒度控制时用原型。

总结

这8道ES6+新特性题目,展现了现代JavaScript的发展方向。从基础语法到函数式编程,从新数据类型到设计模式,掌握这些内容说明你已经具备了现代前端开发的核心能力。

学习建议:

  1. 理解设计思想:每个特性解决什么问题
  2. 实践应用:在真实项目中尝试使用
  3. 关注演进:关注TC39提案和语言发展方向

ES6+的特性让JavaScript从脚本语言成长为强大的编程语言,理解这些特性让你在技术选型和架构设计时更有把握。

思考题: 在这些新特性中,哪个特性对你的编程思维改变最大?是函数式编程、模块化,还是异步编程的改进?

在评论区分享你的想法,我们一起探讨吧!