当前位置: 代码网 > it编程>编程语言>Javascript > JavaScript实现私有属性的几种方式小结

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

2024年05月28日 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私有属性的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com