核心概念✅
下面代码中 a 在什么情况下会打印 1 ?
const a = {
// 实现 a 对象
}
// 使得此处等式成立
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
答案
const a = {
i: 1,
toString: function () {
return this.i++
}
}
// eslint-disable-next-line
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
在这个例子中,a 被定义为一个对象,有一个属性 i 初始化为 1,同时重写了 toString 方法,在每次调用时返回 i 的值,并且每次返回后将 i 自增。这样在比较 a 是否等于 1、2、3 的时候,会依次调用 a.toString() 方法,得到的结果就是满足条件的 1,依次打印出来。
深拷贝和浅拷贝
答案
核心概念
- 浅拷贝(Shallow Copy):仅拷贝对象的第一层属性。对于基本类型值,拷贝其数值;对于引用类型值,拷贝其内存地址引用。修改新对象的嵌套属性会影响原对象。
- 常见方式:
Object.assign({}, obj)、展开运算符{ ...obj }、Array.prototype.slice()。
- 常见方式:
- 深拷贝(Deep Copy):递归复制对象的所有层级结构,开辟全新的内存空间。修改新对象的任何属性都不会影响原对象。
- 常见方式:
structuredClone(obj)(现代浏览器/Node 17+ 原生支持)、JSON.parse(JSON.stringify(obj))(无法处理函数、Symbol、undefined、循环引用、BigInt)。
- 常见方式:
手写健壮深拷贝(支持循环引用、特殊对象、Symbol 属性):
function deepClone (target, map = new WeakMap()) {
// 1. 基本类型与 null 直接返回
if (target === null || typeof target !== 'object') {
return target
}
// 2. 特殊引用类型处理
if (target instanceof Date) return new Date(target)
if (target instanceof RegExp) return new RegExp(target.source, target.flags)
if (typeof target === 'function') return target // 函数通常无需拷贝,保持引用
// 3. 处理循环引用(利用 WeakMap 记录已访问对象)
if (map.has(target)) {
return map.get(target)
}
// 4. 处理 Set
if (target instanceof Set) {
const cloneSet = new Set()
map.set(target, cloneSet)
target.forEach(val => cloneSet.add(deepClone(val, map)))
return cloneSet
}
// 5. 处理 Map
if (target instanceof Map) {
const cloneMap = new Map()
map.set(target, cloneMap)
target.forEach((val, key) => cloneMap.set(deepClone(key, map), deepClone(val, map)))
return cloneMap
}
// 6. 数组与普通对象(保持正确的原型)
const cloneTarget = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target))
map.set(target, cloneTarget)
// 7. 支持 Symbol 键与普通键
const keys = Reflect.ownKeys(target)
for (const key of keys) {
cloneTarget[key] = deepClone(target[key], map)
}
return cloneTarget
}
// 示例与验证
const obj = {
num: 1,
str: 'hello',
date: new Date(),
reg: /abc/gi,
arr: [1, 2, { a: 3 }],
set: new Set([1, 2]),
map: new Map([['k', 'v']])
}
obj.self = obj // 循环引用测试
const cloned = deepClone(obj)
console.log(cloned !== obj) // true
console.log(cloned.self === cloned) // true
console.log(cloned.arr[2] !== obj.arr[2]) // true
面试官视角
- 考查深度:从最简单的
JSON.stringify局限性切入,逐步下探到WeakMap解决循环引用与避免内存泄漏、Reflect.ownKeys获取不可枚举或 Symbol 键、以及Date/RegExp等特殊实例类型的还原。
延伸阅读
实现一个 sum 函数,支持任意个参数的累加,在 console.log 时输出结果?
// example1
console.log(sum(1)(2)(3, 4)) // 10
// example2
console.log(sum(1, 2, 3, 4)) // 10
答案
function sum (...args) {
let total = args.reduce((a, b) => a + b, 0)
function inner (...rest) {
total += rest.reduce((a, b) => a + b, 0)
return inner
}
inner.toString = inner.valueOf = () => total
inner[Symbol.toPrimitive] = () => total
return inner
}
// example1
console.log(+sum(1)(2)(3, 4)) // 10
// example2
console.log(+sum(1, 2, 3, 4)) // 10
模拟new操作
答案
在 JavaScript 中,new 关键字的核心作用是:
- 创建一个新的空对象;
- 将新对象的原型指向构造函数的
prototype; - 将构造函数内部的
this绑定到新对象; - 执行构造函数逻辑;
- 如果构造函数返回一个对象,则返回该对象,否则返回新创建的对象。
自定义实现时需注意:
- 必须正确设置原型链;
- 需处理构造函数显式返回对象的情况;
- 不能省略
this绑定和参数传递。
实现代码如下:
function myNew (constructor, ...args) {
// 1. 创建一个新对象,原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype)
// 2. 执行构造函数,将 this 绑定到新对象
const result = constructor.apply(obj, args)
// 3. 返回构造函数返回的对象(如果是对象),否则返回新对象
return (typeof result === 'object' && result !== null) ? result : obj
}
使用示例:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () {
console.log(`Hi, I'm ${this.name}, ${this.age} years old.`)
}
const p = myNew(Person, 'Alice', 20)
p.sayHi() // Hi, I'm Alice, 20 years old.
实现链式调用 ?
答案
链式调用是指多个方法可以连续调用,每个方法返回当前对象,以便可以继续调用其他方法。实现链式调用通常需要在每个方法中返回 this,以保持对当前对象的引用。
class Chainable {
constructor (value) {
this.value = value
}
add (num) {
this.value += num
return this
}
subtract (num) {
this.value -= num
return this
}
multiply (num) {
this.value *= num
return this
}
divide (num) {
this.value /= num
return this
}
getValue () {
return this.value
}
}
const result = new Chainable(10)
.add(5)
.subtract(2)
.multiply(3)
.divide(2)
.getValue()
console.log(result) // 19.5