当前位置: 首页 > 科技观察

私有属性的六种实现你用过几个?

时间:2023-03-19 23:57:03 科技观察

类是创建对象的模板,由一系列的属性和方法组成,用于表示对同一概念的数据和操作。有的属性和方法是外部的,有的只供内部使用,也就是私有的,如何实现私有的属性和方法呢?不知道大家会怎么实现。我已经整理好了。我大概用过6种下面分别看看方法:_prop区分private和public最简单的方法就是加下划线_来从名字上区分。例如:classDong{constructor(){this._name='dong';这个._age=20;this.friend='光';}hello(){return'我\'+this._name+','+this._age+'岁数';}}constdong=newDong();console.log(dong.hello());这里的董有私有属性_name、_age,还有共享属性friend。但是这个方法只是一个命名约定,告诉开发者这个属性和方法是私有的,不能调用,但毕竟不是强制的,别人要用也不能阻止。不过,这种方法至今仍被大量使用,历史悠久。那么基于这个规范如何实现真正的隐私呢?这就需要Proxy:ProxyProxy可以定义目标对象的get、set、Object.keys的逻辑。你可以在这个层面上做出判断。如果是下划线_开头则不允许访问,否则允许访问。比如这个类:classDong{constructor(){this._name='dong';这个._age=20;this.friend='光';}hello(){return'我\'+this._name+','+this._age+'岁';}}constdong=newDong();我们不直接调用它的对象的属性方法,而是先用一层Proxy来约束get、set、getKeys的行为:constdong=newDong();consthandler={get(target,prop){if(prop.startsWith('_')){返回;}返回目标[prop];},set(target,prop,value){if(prop.startsWith('_')){返回;}target[prop]=值;},ownKeys(target,prop){返回对象。键(目标)。filter(key=>!key.startsWith('_'))},}constproxy=newProxy(dong,handler)我们通过newProxy为dong定义get,set,ownKeyshandlers:get:如果以下划线开头_,则返回空,否则返回目标对象的属性值target[prop]。set:如果是下划线_开头则直接返回,否则设置目标对象的属性值。ownKeys:访问key时,过滤掉目标对象中下划线开头的属性并返回。这样就实现了下划线开头的属性的私有化:下面测试一下:constproxy=newProxy(dong,handler)for(constkeyofObject.keys(proxy)){console.log(key,proxy[key])}确实,这里只打印有公共属性的方法,下划线开头的两个属性不打印。我们基于_prop的命名约定实现了真正的私有属性!尝试再次调用下一个方法:为什么它未定义?因为proxy.hello方法的this也指向代理,所以会被限制,所以我们需要再做一遍:如果你正在使用一个方法,那么将它与this作为目标对象绑定。这样hello方法就可以访问到那些_开头的私有属性了:我们已经通过Proxy带下划线的命名约定实现了真正的私有属性,只是定义一层Proxy比较麻烦。有没有办法不定义代理?是的,比如Symbol:SymbolSymbol是es2015新增的一个创建唯一值的api。基于这个独特的特性,我们可以实现私有属性。例如:constnameSymbol=Symbol('name');constageSymbol=Symbol('年龄');classDong{constructor(){this[nameSymbol]='dong';这个[年龄符号]=20;}hello(){return'我'+this[nameSymbol]+','+this[ageSymbol]+'岁数';}}constdong=newDong();我们不再使用name和age作为私有属性名,而是使用Symbol生成一个唯一的值作为名字。这样,由于外部无法获取到属性名,也就无法获取到对应的属性值:这种方式比代理方式简单,也是一种广泛使用的实现私有属性的方式。如果要暴露,可以定义一个get方法:但是这种私有属性真的不能访问吗?不是的,有一个api叫Object.getOwnPropertySymbols,可以获取到对象所有的Symbols属性,然后就可以获取属性了。它不如代理方法那么完美。不使用Proxy的方式比有Symbol更完美?那你可以试试这个:我们可以访问WeakMap外面的属性和方法,是因为我们挂在了this上面,那么如果我们不挂在this上面,是不是就不能访问了呢?比如用一个Map来保存私有属性:constprivateFields=newMap();classDong{constructor(){privateFields.set('name','dong');privateFields.set('年龄',20);}hello(){return'我是'+privateFields.get('name')+','+privateFields.get('name')+'岁';}}来测试一下:看起来是这样的,但是不知道大家有没有发现问题:所有的对象都使用同一个Map,互相影响。对象销毁后Map仍然存在。如何解决这个问题呢?不知道大家有没有用过WeakMap。它的特点是只能使用对象作为键。当对象被销毁时,键值对也会被销毁。完美解决了以上两个问题:因为以对象为键,不同的对象放在不同的键值对上,相互之间没有影响。当对象被销毁时,对应的键值对被销毁,无需人工管理。貌似很完美,实现一下吧:constdongName=newWeakMap();constdongAge=newWeakMap();constclassPrivateFieldSet=function(receiver,state,value){state.set(receiver,value);}constclassPrivateFieldGet=function(receiver,state){returnstate.get(receiver);}classDong{constructor(){dongName.设置(这个,无效0);dongAge.set(this,void0);classPrivateFieldSet(this,dongName,'dong');classPrivateFieldSet(this,dongAge,20);}hello(){return'我'+classPrivateFieldGet(this,dongName)+','+classPrivateFieldGet(this,dongAge)+'岁数';每个属性定义一个WeakMap来维护,key是当前对象,value是属性值。Get和set使用classPrivateFieldSet和classPrivateFieldGet这两个方法,最终都是从WeakMap中访问的。在构造函数中,初始化当前对象对应的属性值,即dongName.set(this,void0),其中void0的返回值为undefined,意思是。测试:哇,私有属性也可以通过WeakMap来实现!但是这里不用定义classPrivateFieldGet,直接xxMap.get不就可以了吗?确实,包裹一层的目的就是为了增加一些额外的逻辑,这里也可以直接从weakMap中获取。但是这样写很麻烦。有更容易的方法吗?能不能设计一种语法糖,自动编译成这样?没错,确实有这样的语法糖:#prop现在有一个私有属性的esdraft,私有属性和方法可以通过#来标识。例如:classDong{constructor(){this.#name='dong';这个。#age=20;this.friend='光';}hello(){return'我'+this.#name+this.#age+'岁数';}}这里的name和age是私有的,而friend是公开的。这种新语法没有那么快被JS引擎支持,但是可以通过babel或者ts编译器编译成低版本的语法来提前使用。比如babel有一个@babel/proposal-private-property-in-object插件,可以实现这种语法的编译:babel是通过将#prop编译到上面的WeakMap中实现的。这个插件会在@babel/preset-env的preset中自动导入:除了babel,这个语法也可以直接在ts中使用:它也会被编译成一个WeakMap。事实上,ts实现了相当多的新语法。例如,?和??分别是可选链和默认值的语法。下面两种写法是等价的:constres=data?.name??'东';constres2=data&&data.name||'东';这个新的语法是直接可用的,如果你使用babel,你需要引入proposal插件。对了,我记得ts里面的class也有private修饰符。那不也是私有属性吗?事实上,它是一个私有属性,但不完全是。我们来看一下:tsprivatets可以通过private修改属性和方法可见性:private表示属性是私有的,只能在类内访问。protected表示保护,只能访问类和子类。public表示共享,可以被外部访问。类型检查和提示是有区别的。比如私有属性在类外是不可访问的,但在类内是可以访问的:但是这个约束只用于类型检查,只在编译时存在。在运行时没有这样的约束。约束。我们可以查看编译后的代码:我们可以看到没有进行任何处理。而如果使用#prop方法,除了在编译时是private之外,在运行时也是private的:因此,如果要实现真正的private,还是应该使用#prop方法。如果你只是在编译时对其进行约束,则声明为private。概要类是用来围绕某个概念定义一系列的属性和方法,这些属性和方法有的是内部使用的,有的是外部使用的。只有内部使用的属性和方法需要私有化。为了实现私有属性方法,我建立了6种方式:通过下划线_prop从名字上区分。通过Proxy定义get、set、ownKeys的逻辑。唯一的属性名是通过Symbol定义的,不能通过key获取。所有对象的私有属性和方法都是通过WeakMap保存的。通过#prop的新es语法私有,babel和tsc将它们编译成WeakMap。在编译时受tsprivate约束。这六个方法中,只有三个是伪私有的,比如_prop(仍然可以访问)、tsprivate(运行时可以访问)、Symbol(可以通过Object.getOwnSymbols访问获取符号)。其他三个是真正私有的,包括Proxy、WeakMap、#prop(目前编译成WeakMap)。这6种实现私有属性的方式,你用过几种?