前言
最近在技术面试和代码审查中,我发现很多开发者对ES6+的新特性停留在"会用"层面,但问到"为什么这样设计"就答不上来了。
比如问到Class继承,能说出extends和super的用法,但追问"Class继承和原型继承在底层有什么区别",很多人就卡壳了。
这就是典型的"API工程师"现象:知道怎么用,不懂为什么这样设计。
今天这8道ES6+新特性题目,每道题我都会从三个维度解析:
- 语言设计思想:这个特性解决了什么问题
- 底层实现原理:语法糖背后的工作机制
- 实际应用场景:在项目中如何正确使用
每题都配"速记公式"和"标准答案",帮你从"会用"到"用好"。
准备好了吗?让我们开始这场ES6+的深度探索之旅!
1. ES6有哪些新特性?箭头函数与普通函数的区别?
速记公式:ES6特性多,箭头函数无this无arguments
- ES6核心特性:let/const、箭头函数、模板字符串、解构赋值、模块化、Promise等
- 箭头函数:无自己的this、无arguments、不能作为构造函数、无prototype
- 适用场景:回调函数、需要继承外层this的场景
标准答案
ES6重要新特性:
- 变量声明:let/const 块级作用域
- 箭头函数:
() => {}简洁语法 - 模板字符串:
`Hello ${name}`支持插值 - 解构赋值:
const {name, age} = obj - 模块化:import/export
- Promise:异步编程解决方案
- Class:类语法糖
- 默认参数:
function(a = 1) {} - 扩展运算符:
...arr - Symbol:唯一值类型
- 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
}
数据结构对比:
| 特性 | 数组 | 对象 | Set | Map |
|---|---|---|---|---|
| 键类型 | 数字索引 | 字符串/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的发展方向。从基础语法到函数式编程,从新数据类型到设计模式,掌握这些内容说明你已经具备了现代前端开发的核心能力。
学习建议:
- 理解设计思想:每个特性解决什么问题
- 实践应用:在真实项目中尝试使用
- 关注演进:关注TC39提案和语言发展方向
ES6+的特性让JavaScript从脚本语言成长为强大的编程语言,理解这些特性让你在技术选型和架构设计时更有把握。
思考题: 在这些新特性中,哪个特性对你的编程思维改变最大?是函数式编程、模块化,还是异步编程的改进?
在评论区分享你的想法,我们一起探讨吧!