跳到主要内容

核心概念✅

下面代码中 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 关键字的核心作用是:

  1. 创建一个新的空对象;
  2. 将新对象的原型指向构造函数的 prototype;
  3. 将构造函数内部的 this 绑定到新对象;
  4. 执行构造函数逻辑;
  5. 如果构造函数返回一个对象,则返回该对象,否则返回新创建的对象。

自定义实现时需注意:

  • 必须正确设置原型链;
  • 需处理构造函数显式返回对象的情况;
  • 不能省略 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