ES6 系列之私有变量的实现

前言

在阅读 《ECMAScript 6 入门》的时候,零散的看到有私有变量的实现,所以在此总结一篇。

1. 约定

实现

1class Example { 2 constructor() { 3 this._private = 'private'; 4 } 5 getName() { 6 return this._private 7 } 8} 9 10var ex = new Example(); 11 12console.log(ex.getName()); // private 13console.log(ex._private); // private

优点

  1. 写法简单
  2. 调试方便
  3. 兼容性好

缺点

  1. 外部可以访问和修改
  2. 语言没有配合的机制,如 for in 语句会将所有属性枚举出来
  3. 命名冲突

2. 闭包

实现一

1/** 2 * 实现一 3 */ 4class Example { 5 constructor() { 6 var _private = ''; 7 _private = 'private'; 8 this.getName = function() {return _private} 9 } 10} 11 12var ex = new Example(); 13 14console.log(ex.getName()); // private 15console.log(ex._private); // undefined

优点

  1. 无命名冲突
  2. 外部无法访问和修改

缺点

  1. constructor 的逻辑变得复杂。构造函数应该只做对象初始化的事情,现在为了实现私有变量,必须包含部分方法的实现,代码组织上略不清晰。
  2. 方法存在于实例,而非原型上,子类也无法使用 super 调用
  3. 构建增加一点点开销

实现二

1/** 2 * 实现二 3 */ 4const Example = (function() { 5 var _private = ''; 6 7 class Example { 8 constructor() { 9 _private = 'private'; 10 } 11 getName() { 12 return _private; 13 } 14 } 15 16 return Example; 17 18})(); 19 20var ex = new Example(); 21 22console.log(ex.getName()); // private 23console.log(ex._private); // undefined

优点

  1. 无命名冲突
  2. 外部无法访问和修改

缺点

  1. 写法有一点复杂
  2. 构建增加一点点开销

3. Symbol

实现

1const Example = (function() { 2 var _private = Symbol('private'); 3 4 class Example { 5 constructor() { 6 this[_private] = 'private'; 7 } 8 getName() { 9 return this[_private]; 10 } 11 } 12 13 return Example; 14})(); 15 16var ex = new Example(); 17 18console.log(ex.getName()); // private 19console.log(ex.name); // undefined

优点

  1. 无命名冲突
  2. 外部无法访问和修改
  3. 无性能损失

缺点

  1. 写法稍微复杂
  2. 兼容性也还好

4. WeakMap

实现

1/** 2 * 实现一 3 */ 4const _private = new WeakMap(); 5 6class Example { 7 constructor() { 8 _private.set(this, 'private'); 9 } 10 getName() { 11 return _private.get(this); 12 } 13} 14 15var ex = new Example(); 16 17console.log(ex.getName()); // private 18console.log(ex.name); // undefined

如果这样写,你可能觉得封装性不够,你也可以这样写:

1/** 2 * 实现二 3 */ 4const Example = (function() { 5 var _private = new WeakMap(); // 私有成员存储容器 6 7 class Example { 8 constructor() { 9 _private.set(this, 'private'); 10 } 11 getName() { 12 return _private.get(this); 13 } 14 } 15 16 return Example; 17})(); 18 19var ex = new Example(); 20 21console.log(ex.getName()); // private 22console.log(ex.name); // undefined

优点

  1. 无命名冲突
  2. 外部无法访问和修改

缺点

  1. 写法比较麻烦
  2. 兼容性有点问题
  3. 有一定性能代价

5. 最新提案

1class Point { 2 #x; 3 #y; 4 5 constructor(x, y) { 6 this.#x = x; 7 this.#y = y; 8 } 9 10 equals(point) { 11 return this.#x === point.#x && this.#y === point.#y; 12 } 13}

那么为什么不直接使用 private 字段呢?比如说这样:

1class Foo { 2 private value; 3 4 equals(foo) { 5 return this.value === foo.value; 6 } 7}

简单点来说,就是嫌麻烦,当然也有性能上的考虑……

举个例子,如果我们不使用 #,而是使用 private 关键字:

1class Foo { 2 private value = '1'; 3 4 equals(foo) { 5 return this.value === foo.value; 6 } 7} 8 9var foo1 = new Foo(); 10var foo2 = new Foo(); 11 12console.log(foo1.equals(foo2));

在这里我们新建了两个实例,然后将 foo2 作为参数传入了 foo1 的实例方法中。

那么我们可以获取 foo2.value 的值吗?如果我们直接 foo2.value 肯定是获取不到值的,毕竟是私有变量,可是 equals 是 Foo 的一个类方法,那么可以获取到的吗?

答案是可以的。

其实这点在其他语言,比如说 Java 和 C++ 中也是一样的,类的成员函数中可以访问同类型实例的私有变量,这是因为私有是为了实现“对外”的信息隐藏,在类自己内部,没有必要禁止私有变量的访问,你也可以理解为私有变量的限制是以类为单位,而不是以对象为单位,此外这样做也可以为使用者带来便利。

既然获取值是可以的,那么打印的结果应该为 true,但是如果我们传入的值不是 Foo 的实例,而是一个其他对象呢?

1var foo1 = new Foo(); 2 3console.log(foo1.equals({ 4 value: 2 5}));

当然这里代码也是可以正常运行的,但是对于编译器来说,就有一点麻烦了,因为编译器不知道 value 到底是 foo 的正常属性还是私有属性,所以编译器需要做判断,先判断 foo 是不是 Foo 的实例,然后再接着获取值。

这也意味着每次属性访问都需要做这样一个判断,而引擎已经围绕属性访问做了高度优化,懒得改,而且还降低速度。

不过除了这个工作之外,还会有一些其他的内容需要考虑,比如说:

  1. 你必须将私有的 key 编码进每个词法环境
  2. for in 可以遍历这些属性吗?
  3. 私有属性和正常属性同名的时候,谁会屏蔽谁?
  4. 怎么防止私有属性的名称不被探测出来。

关于使用 # 而不使用 private 更多的讨论可以参考这个 Issue

当然这些问题都可以被解决啦,就是麻烦了点。

而如果你选择 #,实现的方式将跟 JavaScript 对象属性完全没有关系,将会使用 private slots 的方式以及使用一个新的 slot 查找语法,总之就是会比 private 的实现方式简单很多。

参考

  1. 《编程语言如何演化——以JS的private为例》贺师俊
  2. Exploring ES6
  3. 译 JS 新语法:私有属性

ES6 系列

ES6 系列目录地址:https://github.com/mqyqingfeng/Blog

ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。

原文链接

点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

ES6 参数默认值引起的中间作用域

ES6参数默认值的问题,其实之前在另一篇文章中已经有涉及,之所以再谈起这个问题,是在阅读《ES6标准入门》时产生的一个疑惑。阮老师的代码是:varx1;functionfoo(x,yfunction(){x2;}){varx3;y();console.log(x);}foo();

ES6学习笔记(3)

参考书《ECMAScript6入门》http://es6.ruanyifeng.com/字符串的扩展ES6之前只能识别\\u0000\\uFFFF之间的字符,超过此范围,识别会出错;ES6弥补了这个错误ES6扩展的新方法codePointAt"𠮷".CodePointAt(0)//返回超过\\u00

1.5万字概括ES6全部特性

因图片过大无法上传,请大家点击阅读原文查看图文并茂的详情哟,原文末尾有惊喜前言第三次阅读阮一峰老师的《ECMAScript6入门》了,以前阅读时不细心,很多地方都是一目十行。最近这次阅读都是逐个逐个字来读,发现很多以前都没有注意到的知识点,为了方便记忆和预览全部ES6特性,所以写下本文。以下提到的《ECMAScri

IOS 面试题(四)

1.ObjectC有多继承吗?没有的话用什么代替?
1OC是单继承,没有多继承2有时可以用分类和协议来代替多继承2.ObjectC有私有方法吗?私有变量呢?
1OC没有类似@private(http://my.