JavaScript实现私有属性的几种方式小结

作者:纸飞机时间:2026-10-31 13:19:59浏览:662

本文总结JavaScript中实现私有属性的多种方案。从早期的下划线命名约定、闭包技巧,到结合ES模块的变量隐藏,再到基于Symbol和TypeScript private修饰符的尝试。重点解析ES2022正式引入的#私有字段语法,以及为兼容旧环境使用WeakMap的实现方式,对比各方案的优缺点与适用场景。

私有属性的定义

通俗而言,若某属性仅限类内部调用,外部(如实例)无法访问,即视为私有属性。

以下代码仅作示例,其中属性并非真正的私有属性,旨在阐释私有属性的概念:

class Person {
  name = 'name'  // 我们假设name是一个私有属性,当然,它现在不是  
  getName() {
    return this.name // 类内部可以访问私有属性
  }
}
const person = new Person()
person.getName() // 'name'
person.name // 尝试直接在类外部访问私有属性会报错

实现路径

早期JavaScript缺乏原生私有属性支持,需借助变通手段实现。

基于命名规范的弱约束

一种策略是通过命名规范进行约束,约定以下划线开头的属性标识为私有。

class Person {
  _name = 'name'  // 约定下划线开头的属性是私有属性
  getName() {
    return this._name // 类内部可以访问私有属性
  }
}
const person = new Person()
person._name  // 开发者可以不遵守命名规范,运行时在类外部访问完全没问题

Vue源码中大量使用下划线前缀来命名属性和变量。

私有属性的定义

然而这仅是弱约束,运行时仍可在类外部直接访问这些属性,无任何限制。

基于闭包机制

function Person(){
  const name = 'name'
  this.getName = function(){
    return name
  }
}
const person = new Person()
person.getName()  // 'name'
person.name // undefined

上述代码中getName函数引用了Person函数的词法环境,借助闭包特性达成私有属性效果。私有属性name在Person外部不可见,仅能通过特权方法getName访问。

此方法的局限性同样显著:

私有属性与特权方法均须在构造函数内定义,且方法未挂载至原型,每实例化一个对象即生成一次方法副本。

结合ES模块将私有属性移至类外部

const name = 'name'
export class Person {
  getName() {
    return name
  }
}

前述代码中,ES模块仅导出类,未导出类外变量name,使得类可访问变量name,而外部无法触及。

const person = new Person()
person.getName() // 'name'
person.name // undefined

基于Symbol类型

const name = Symbol('name')
export class Person {
  [name] = 'name'
  getName() {
    return this[name]
  }
}

上述代码利用变量存储Symbol值,在类内部通过动态属性方式添加私有属性。同样依赖ES模块仅导出类而不导出Symbol,致使外部无法获取Symbol值以访问私有属性。

const person = new Person()
person.getName() // 'name'

然而,仍存在获取该Symbol值的途径。

const symbols = Object.getOwnPropertySymbols(person)
person[symbols[0])

因此,此方式并未实现绝对的私有化。

TypeScript中的private关键字

TypeScript提供了private修饰符,尝试使用此方式是否可行?

class Person {
  private name = 'name'
  getName() {
    return name
  }
}
const person = new Person()
person.getName() // 'name'
person.name // 编译时错误 Property 'name' is private and only accessible within class 'Person'.

在TypeScript中,于类外部访问private属性会在编译阶段报错,看似理想。但需铭记TypeScript最终需编译为JavaScript,观察编译结果:

私有属性的定义

编译为JavaScript后,private修饰符消失。若通过动态属性绕过编译时的类型检查,编译后的JavaScript代码在运行时不会报错:

person['name'] // 'name'

私有属性的定义

ES2022标准

ES2022正式引入私有属性,在属性名前添加#前缀以表示私有。

class Person {
  #name = 'name'
  getName() {
    return this.#name
  }
}
const person = new Person()
person.getName() // 'name'
person.#name // Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class

若将上述代码置于Chrome控制台执行,或许会发现person.#name仍可访问值。自Chrome111版本起,开发者工具允许读写私有属性且不报错,原因是Chrome团队认为此举便于调试。

查阅MDN以获取更多关于私有属性的信息。

利用WeakMap解决兼容性问题

若需兼顾ES2022之前的环境兼容性,可采用WeakMap方案。

const privateFields = new WeakMap()
export class Person {
  constructor() {
    privateFields.set(this, {
      name: 'name'
    })
  }
  
  getName() {
    return privateFields.get(this).name
  }
}

前述代码在类外部维护一个weakMap,并在constructor中向weakMap绑定实例this与{name: 'name'}的映射关系。访问时同样通过this从weakMap中检索name。

同样得益于ES模块特性,模块外部无法访问weakMap,自然也就无法触及私有属性。

const person = new Person()
person.getName() // 'name'

然而此写法存在缺点,即代码繁琐且不够直观。实际上,ES2022私有属性方案经babel编译后的代码大致与此类似。

私有属性的定义

可见babel编译后的代码量显著增加,因需确保程序健壮性,必须考虑诸多边缘场景。仅观察红框标注代码亦可看出,编译后确实采用了WeakMap方案。

总结

本文归纳了JavaScript中实现私有属性的多种途径。ES2022引入的正式写法无疑是正规军,且代码简洁。若需考虑兼容性,WeakMap方案确实保障了私有性,尽管写法稍显繁琐。其他方案在健壮性上各有不足,了解即可。

以上为JavaScript实现私有属性的几种方式小结的详细内容,更多关于JavaScript私有属性的资料请关注本站其它相关文章!

作者最新文章