You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何原型含私有属性类实例的对象访问私有成员会报错?

问题:私有成员访问异常与类实例克隆方法

问题示例代码

class Klass {
  #priv = 42

  get pub() {
    return this.#priv
  }
}

// 以下两种情况正常返回42
console.log(new Klass().pub);
console.log((new class extends Klass {}).pub)

// 但此代码抛出错误:"TypeError: Cannot read private member #priv from an object whose class did not declare it"
console.log(Object.create(new Klass()).pub) // 报错 

// 尽管以下判断结果为true
console.log(Object.create(new Klass()) instanceof Klass) // true
console.log(Object.getPrototypeOf(Object.create(new Klass())).pub) // 42

我原本以为只要原型链中存在Klass的真实实例,访问Object.create(new Klass()).pub就不会报错。请问这是设计如此吗?若如此,原因是什么?另外,克隆类似Klass这类任意类实例的正确方法是什么?

背景场景

我在使用Vitest测试时遇到该问题,测试代码如下:

import { it, expect } from 'vitest';

class Klass {
    #priv = 42;

    get pub() {
        return this.#priv;
    }
}

it('works', () => {
    expect(new Klass()).toMatchObject({
        make_this_test_fail: 'yup',
        pub: 42,
    });
});

测试失败后未正常显示属性缺失的差异,而是抛出了私有成员访问错误。经排查发现,Vitest的deepClone方法使用Object.create创建类实例导致该问题。


解答

1. 这是JavaScript私有字段的设计特性

没错,这就是ECMAScript私有字段(#开头的成员)的既定规则。私有字段的访问逻辑并非基于原型链查找,而是严格绑定对象自身的类声明:

  • 只有当对象是由声明了该私有字段的类(或其派生类)直接实例化出来的,才能访问这个私有字段。
  • Object.create(new Klass())创建的对象,只是把Klass实例作为原型,但这个对象本身并不是Klass类的实例——它没有在自身结构中持有#priv字段,也没有被Klass类的构造函数初始化过,所以即使原型链上有带私有字段的实例,也无法通过这个对象访问私有字段。

instanceof返回true只是因为它只检查原型链中是否存在Klass的原型,不关心对象本身是否持有类的私有成员。

2. 克隆含私有字段类实例的正确方法

针对包含私有字段的类,没有通用的“万能克隆法”,但可以根据场景选择以下方案:

方案一:类自身实现克隆方法

在类内部添加clone方法,利用类的构造函数和内部对私有字段的访问权限来复制:

class Klass {
  #priv = 42

  get pub() {
    return this.#priv
  }

  clone() {
    const newInstance = new Klass();
    // 如果私有字段需要自定义复制逻辑,在这里处理
    newInstance.#priv = this.#priv;
    return newInstance;
  }
}

const original = new Klass();
const cloned = original.clone();
console.log(cloned.pub); // 42

方案二:使用结构化克隆(Structured Clone)

对于支持结构化克隆的场景(比如浏览器环境、Node.js),可以用structuredClone API,它能正确复制类实例的私有字段:

const original = new Klass();
const cloned = structuredClone(original);
console.log(cloned.pub); // 42

注意:结构化克隆有局限性,比如无法复制函数、Symbol属性等,但对于普通带私有字段的类实例适用。

方案三:针对测试场景的临时处理(以Vitest为例)

如果是Vitest的toMatchObject导致的问题,可以避开深度克隆,改用其他断言方式:

  • 单独断言公共属性:expect(new Klass().pub).toBe(42);
  • 手动提取公共属性后断言:expect({ pub: new Klass().pub }).toMatchObject({ pub: 42, make_this_test_fail: 'yup' });
  • 升级Vitest版本,部分新版本已经修复了私有字段克隆的问题。

内容的提问来源于stack exchange,提问作者Bartosz Gościński

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 06:54:53