详解ES6系列之私有变量的实现
前言
在阅读《ECMAScript6入门》的时候,零散的看到有私有变量的实现,所以在此总结一篇。
1.约定
实现
classExample{ constructor(){ this._private='private'; } getName(){ returnthis._private } } varex=newExample(); console.log(ex.getName());//private console.log(ex._private);//private
优点
- 写法简单
- 调试方便
- 兼容性好
缺点
- 外部可以访问和修改
- 语言没有配合的机制,如forin语句会将所有属性枚举出来
- 命名冲突
2.闭包
实现一
/** *实现一 */ classExample{ constructor(){ var_private=''; _private='private'; this.getName=function(){return_private} } } varex=newExample(); console.log(ex.getName());//private console.log(ex._private);//undefined
优点
- 无命名冲突
- 外部无法访问和修改
缺点
- constructor的逻辑变得复杂。构造函数应该只做对象初始化的事情,现在为了实现私有变量,必须包含部分方法的实现,代码组织上略不清晰。
- 方法存在于实例,而非原型上,子类也无法使用super调用
- 构建增加一点点开销
实现二
/** *实现二 */ constExample=(function(){ var_private=''; classExample{ constructor(){ _private='private'; } getName(){ return_private; } } returnExample; })(); varex=newExample(); console.log(ex.getName());//private console.log(ex._private);//undefined
优点
- 无命名冲突
- 外部无法访问和修改
缺点
- 写法有一点复杂
- 构建增加一点点开销
3.Symbol
实现
constExample=(function(){ var_private=Symbol('private'); classExample{ constructor(){ this[_private]='private'; } getName(){ returnthis[_private]; } } returnExample; })(); varex=newExample(); console.log(ex.getName());//private console.log(ex.name);//undefined
优点
- 无命名冲突
- 外部无法访问和修改
- 无性能损失
缺点
- 写法稍微复杂
- 兼容性也还好
4.WeakMap
实现
/** *实现一 */ const_private=newWeakMap(); classExample{ constructor(){ _private.set(this,'private'); } getName(){ return_private.get(this); } } varex=newExample(); console.log(ex.getName());//private console.log(ex.name);//undefined
如果这样写,你可能觉得封装性不够,你也可以这样写:
/** *实现二 */ constExample=(function(){ var_private=newWeakMap();//私有成员存储容器 classExample{ constructor(){ _private.set(this,'private'); } getName(){ return_private.get(this); } } returnExample; })(); varex=newExample(); console.log(ex.getName());//private console.log(ex.name);//undefined
优点
- 无命名冲突
- 外部无法访问和修改
缺点
- 写法比较麻烦
- 兼容性有点问题
- 有一定性能代价
5.最新提案
classPoint{ #x; #y; constructor(x,y){ this.#x=x; this.#y=y; } equals(point){ returnthis.#x===point.#x&&this.#y===point.#y; } }
那么为什么不直接使用private字段呢?比如说这样:
classFoo{ privatevalue; equals(foo){ returnthis.value===foo.value; } }
简单点来说,就是嫌麻烦,当然也有性能上的考虑……
举个例子,如果我们不使用#,而是使用private关键字:
classFoo{ privatevalue='1'; equals(foo){ returnthis.value===foo.value; } } varfoo1=newFoo(); varfoo2=newFoo(); console.log(foo1.equals(foo2));
在这里我们新建了两个实例,然后将foo2作为参数传入了foo1的实例方法中。
那么我们可以获取foo2.value的值吗?如果我们直接foo2.value肯定是获取不到值的,毕竟是私有变量,可是equals是Foo的一个类方法,那么可以获取到的吗?
答案是可以的。
其实这点在其他语言,比如说Java和C++中也是一样的,类的成员函数中可以访问同类型实例的私有变量,这是因为私有是为了实现“对外”的信息隐藏,在类自己内部,没有必要禁止私有变量的访问,你也可以理解为私有变量的限制是以类为单位,而不是以对象为单位,此外这样做也可以为使用者带来便利。
既然获取值是可以的,那么打印的结果应该为true,但是如果我们传入的值不是Foo的实例,而是一个其他对象呢?
varfoo1=newFoo(); console.log(foo1.equals({ value:2 }));
当然这里代码也是可以正常运行的,但是对于编译器来说,就有一点麻烦了,因为编译器不知道value到底是foo的正常属性还是私有属性,所以编译器需要做判断,先判断foo是不是Foo的实例,然后再接着获取值。
这也意味着每次属性访问都需要做这样一个判断,而引擎已经围绕属性访问做了高度优化,懒得改,而且还降低速度。
不过除了这个工作之外,还会有一些其他的内容需要考虑,比如说:
- 你必须将私有的key编码进每个词法环境
- forin可以遍历这些属性吗?
- 私有属性和正常属性同名的时候,谁会屏蔽谁?
- 怎么防止私有属性的名称不被探测出来。
关于使用#而不使用private更多的讨论可以参考这个Issue。
当然这些问题都可以被解决啦,就是麻烦了点。
而如果你选择#,实现的方式将跟JavaScript对象属性完全没有关系,将会使用privateslots的方式以及使用一个新的slot查找语法,总之就是会比private的实现方式简单很多。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。