javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript私有属性

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

作者:刘同学有点忙

在JavaScript中,私有属性是指只能在对象内部访问的属性,外部无法直接访问,JavaScript并没有提供官方的私有属性的支持,但可以通过一些技巧来模拟实现私有属性,所以本文给大家总结了JavaScript实现私有属性的几种方式,需要的朋友可以参考下

什么是私有属性

我们不抠定义,用大白话来说,如果类中的某个属性只能在类内部使用,在类外部(比如通过类的实例)访问不到,这个属性就是私有属性。

我们用下面的代码举例说明,当然了,代码中的属性并不是私有属性,只是为了说明私有属性是怎么一回事:

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函数的词法环境,利用闭包的特性实现了私有属性。私有属性namePerson外部无法访问,只能通过特权方法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私有属性的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文