一文详解JS私有属性的6种实现方式

作者:代码拾荒

时间:2026-10-25

浏览:197

本文详细解析JavaScript中实现私有属性的6种方式。从简单的下划线命名规范,到利用Proxy拦截访问、Symbol生成唯一键、WeakMap存储数据,再到ES新语法的#prop以及TypeScript的private修饰符。文章对比了各方案的优缺点,区分了伪私有与真正私有,并提供了代码示例与编译原理说明,帮助开发者选择最适合的私有化方案。

文章目录

类是对象的模板,由属性和方法构成,表示对同一概念的数据和操作。

部分属性和方法对外公开,部分仅内部使用,即私有属性,需探讨其实现方式。

梳理出6种实现私有属性和方法的方式,分别进行解析。

_prop

区分私有与公有最简单的方式是添加下划线 _,通过命名进行区分。

例如:

class Dong {
    constructor() {
        this._name = 'dong';
        this._age = 20;
        this.friend = 'guang';
    }

    hello() {
        return 'I\'m ' + this._name + ', '  + this._age + ' years old';
    }
}

const dong = new Dong();

console.log(dong.hello());

此处 Dong 类拥有私有属性 _name、_age 以及公有属性 friend。

此方式仅为命名规范,提示开发者属性为私有,非强制限制,仍可被调用。

_prop

该方式应用广泛且历史较为悠久。

基于此规范实现真正私有,需借助 Proxy。

Proxy

Proxy 可定义目标对象的 get、set、Object.keys 逻辑,通过判断下划线 _ 开头限制访问。

例如沿用上述 class:

class Dong {
    constructor() {
        this._name = 'dong';
        this._age = 20;
        this.friend = 'guang';
    }

    hello() {
        return 'I\'m ' + this._name + ', '  + this._age + ' years old';
    }
}

const dong = new Dong();

不直接调用对象属性方法,而是使用 Proxy 约束 get、set、getKeys 行为:

const dong = new Dong();
 
const handler = {
    get(target, prop) {
        if (prop.startsWith('_')) {
            return;
        }
        return target[prop];
   },
   set(target, prop, value) {
    if (prop.startsWith('_')) {
        return;
     }
     target[prop] = value;
   },
   ownKeys(target, prop) {
      return Object.keys(target).filter(key => !key.startsWith('_'))
   },
 }
 
const proxy = new Proxy(dong, handler)

通过 new Proxy 为 dong 定义 get、set、ownKeys 的 handler:

  • get: 若属性以下划线 _ 开头则返回空,否则返回目标对象属性值 target[prop]
  • set: 若属性以下划线 _ 开头则直接返回,否则设置目标对象属性值
  • ownKeys: 访问 keys 时,过滤掉目标对象中下划线开头的属性后返回

由此实现以下划线开头属性的私有化:

进行测试:

const proxy = new Proxy(dong, handler)

for (const key of Object.keys(proxy)) {
    console.log(key, proxy[key])
}

_prop

结果显示仅打印公有属性方法,下划线开头的两个属性未打印。

基于 _prop 命名规范实现了真正的私有属性!

进一步调用方法测试:

_prop

为何返回 undefined?

因 proxy.hello 方法的 this 指向 proxy,受限制,需额外处理:

_prop

若使用方法,需将其 this 绑定为目标对象。

如此 hello 方法即可访问 _ 开头的私有属性:

_prop

通过 Proxy 实现了下划线命名规范的真正私有属性,但定义 Proxy 较繁琐,有无无需定义 Proxy 的方式?

确实存在,例如 Symbol:

Symbol

Symbol 是 es2015 添加的 API,用于创建唯一值,基于此特性可实现私有属性。

例如如下:

const nameSymbol = Symbol('name');
const ageSymbol = Symbol('age');

class Dong {
    constructor() {
        this[nameSymbol] = 'dong';
        this[ageSymbol] = 20;
    }

    hello() {
        return 'I\'m ' + this[nameSymbol] + ', '  + this[ageSymbol] + ' years old';
    }
}

const dong = new Dong();

不再使用 name 和 age 作为私有属性名,而是用 Symbol 生成唯一值作为名称。

外部因无法获取属性名,故无法取得对应属性值:

_prop

此方式比 Proxy 更简单,也是常用的私有属性实现方式之一。

若需暴露,可定义 get 方法:

_prop

但这种私有属性是否真的无法访问?

并非如此,存在 Object.getOwnPropertySymbols API,可获取对象所有 Symbols 属性,进而获取属性值:

_prop

因此该方式仅是 Object.keys 无法获取对应属性,不如 Proxy 方式完善。

不使用 Proxy 且比 Symbol 更完善的方式是否存在?

可尝试以下方式:

WeakMap

外部能访问属性和方法是因为将其挂载到 this 上,若不挂载到 this 则无法访问。

例如使用 Map 保存私有属性:

const privateFields = new Map();

class Dong {
    constructor() {
        privateFields.set('name', 'dong');
        privateFields.set('age', 20);
    }

    hello() {
        return 'I\'m ' + privateFields.get('name') + ', '  + privateFields.get('name') + ' years old';
    }
}

测试如下:

_prop

看似可行,但存在以下问题:

  • 所有对象共用同一个 Map,相互之间会产生影响。
  • 对象销毁后,该 Map 依然存在。

如何解决上述问题?

可尝试使用 WeakMap,其特性是仅能以对象作为 key,对象销毁时键值对随之销毁。

完美解决上述两个问题:

  • 因以对象作为 key,不同对象位于不同键值对,互不影响。
  • 对象销毁时,对应键值对自动销毁,无需手动管理。

实现如下:

const dongName = new WeakMap();
const dongAge = new WeakMap();

const classPrivateFieldSet = function(receiver, state, value) {
    state.set(receiver, value);
}

const classPrivateFieldGet = function(receiver, state) {
    return state.get(receiver);
}


class Dong {
    constructor() {
        dongName.set(this, void 0);
        dongAge.set(this, void 0);

        classPrivateFieldSet(this, dongName, 'dong');
        classPrivateFieldSet(this, dongAge, 20);
    }

    hello() {
        return 'I\'m ' + classPrivateFieldGet(this, dongName) + ', '  + classPrivateFieldGet(this, dongAge) + ' years old';
    }
}

每个属性定义一个 WeakMap 进行维护,key 为当前对象,值为属性值,get 和 set 使用 classPrivateFieldSet 和 classPrivateFieldGet 方法,最终通过从 WeakMap 中存取。

在构造器中初始化当前对象对应的属性值,即 dongName.set(this, void 0),此处 void 0 返回值为 undefined,含义相同。

进行测试:

_prop

通过 WeakMap 也能实现私有属性!

但此处 classPrivateFieldGet 是否必要?直接 xxMap.get 即可。

确实,封装一层旨在添加额外逻辑,此处也可直接从 weakMap 获取。

但这样编写仍显繁琐,有无更简单的方式?

能否设计一种语法糖,自动编译为此种方式?

设想正确,确实存在此类语法糖:

#prop

目前存在私有属性的 ES 草案,可通过 # 标识私有属性和方法。

例如如下:

class Dong {
    constructor() {
        this.#name = 'dong';
        this.#age = 20;
        this.friend = 'guang';
    }
    hello() {
        return 'I\'m ' + this.#name + this.#age + 'years old';
    }
}

此处 name 和 age 均为私有,而 friend 为公有。

新语法 JS 引擎支持尚需时日,但可通过 babel 或 ts 编译器编译为低版本语法提前使用。

例如 babel 提供 @babel/proposal-private-property-in-object 插件,可实现该语法编译:

_prop

babel 将 #prop 编译为上述 WeakMap 方式实现。

该插件包含在 @babel/preset-env 预设中,会自动引入:

_prop

除 babel 外,ts 中也可直接使用此语法:

_prop

同样会编译为 WeakMap 方式实现。

ts 实现的新语法较多,例如 ? 和 ?? 分别为可选链和默认值语法,以下两种写法等价:

const res = data?.name ?? 'dong';
const res2 = data && data.name  || 'dong';

此类新语法可直接使用,babel 则需引入 proposal 插件。

另外,记得 ts 中 class 有 private 修饰符,那是否也是私有属性?

其实它是私有属性但也不完全,我们来看一下:

ts private

ts 可通过 private 修饰属性、方法的可见性:

  • private 表示属性私有,仅 class 内部可访问
  • protected 表示保护,仅 class 和子 class 可访问
  • public 表示公有,外部也可访问

类型检查和提示时有区别,例如 private 属性在 class 外部不可访问:

_prop

而 class 内部是可以访问的:

_prop

但此约束仅用于类型检查,仅存在于编译期间,运行时并无此约束。

可查看编译后的代码:

_prop

可见未做任何处理。

若使用 #prop 方式,除编译时是 private 的,运行时也是:

_prop

因此,要实现真正的 private,仍应用 #prop 方式,若仅需编译时约束则声明 private 即可。

总结

class 用于定义围绕某概念的一系列属性和方法,部分内部使用,部分对外公开。仅内部使用的属性、方法需实现私有化。

实现私有属性方法,共梳理出 6 种方式:

  • 通过下划线 _prop 从命名上区分
  • 通过 Proxy 来定义 get、set、ownKeys 的逻辑
  • 通过 Symbol 来定义唯一的属性名,不能通过 keys 拿到
  • 通过 WeakMap 来保存所有对象的私有属性和方法
  • 通过 #prop 的 es 新语法实现私有,babel 和 tsc 会把它们编译成 WeakMap 的方式
  • 通过 ts 的 private 在编译时约束

这六种方式中,有三种仅为伪私有,例如 _prop(仍可访问)、ts 的 private(运行时可访问)、Symbol(可通过 Object.getOwnSymbols 拿到 symbol 来访问)。

另外三种是真正的私有,包括 Proxy、WeakMap、#prop(目前编译为 WeakMap 方式)。

有的从属性名入手,如 _prop 和 Symbol;有的从 this 入手,如 Proxy(包一层)和 WeakMap(不挂到 this);有的从语言本身入手,如 ts 的 private 或 es 新语法的 #prop。

这 6 种实现私有属性的方式,你用过几种?

以上是一文详解JS私有属性的6种实现方式的详细内容,更多关于JS私有属性的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章