资讯专栏INFORMATION COLUMN

ES6 系列之私有变量的实现

lentoo / 1418人阅读

摘要:前言在阅读入门的时候,零散的看到有私有变量的实现,所以在此总结一篇。构造函数应该只做对象初始化的事情,现在为了实现私有变量,必须包含部分方法的实现,代码组织上略不清晰。

前言

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

1. 约定 实现

</>复制代码

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

写法简单

调试方便

兼容性好

缺点

外部可以访问和修改

语言没有配合的机制,如 for in 语句会将所有属性枚举出来

命名冲突

2. 闭包 实现一

</>复制代码

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

无命名冲突

外部无法访问和修改

缺点

constructor 的逻辑变得复杂。构造函数应该只做对象初始化的事情,现在为了实现私有变量,必须包含部分方法的实现,代码组织上略不清晰。

方法存在于实例,而非原型上,子类也无法使用 super 调用

构建增加一点点开销

实现二

</>复制代码

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

无命名冲突

外部无法访问和修改

缺点

写法有一点复杂

构建增加一点点开销

3. Symbol 实现

</>复制代码

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

无命名冲突

外部无法访问和修改

无性能损失

缺点

写法稍微复杂

兼容性也还好

4. WeakMap 实现

</>复制代码

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

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

</>复制代码

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

无命名冲突

外部无法访问和修改

缺点

写法比较麻烦

兼容性有点问题

有一定性能代价

5. 最新提案

</>复制代码

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

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

</>复制代码

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

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

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

</>复制代码

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

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

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

答案是可以的。

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

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

</>复制代码

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

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

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

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

你必须将私有的 key 编码进每个词法环境

for in 可以遍历这些属性吗?

私有属性和正常属性同名的时候,谁会屏蔽谁?

怎么防止私有属性的名称不被探测出来。

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

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

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

参考

《编程语言如何演化——以JS的private为例》贺师俊

Exploring ES6

译 JS 新语法:私有属性

ES6 系列

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

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

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108767.html

相关文章

  • ES6 系列 WeakMap

    摘要:一个对象若只被弱引用所引用,则被认为是不可访问或弱可访问的,并因此可能在任何时刻被回收。也就是说,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。 前言 我们先从 WeakMap 的特性说起,然后聊聊 WeakMap 的一些应用场景。 特性 1. WeakMap 只接受对象作为键名 const map = ...

    CollinPeng 评论0 收藏0
  • ES6模块详解

    摘要:例如我们导入模块,可以这么导入桃翁欢迎关注公众号前端桃园报错不能定义相同名字变量报错,不能重新赋值小猪可以看到导入绑定这里不理解绑定,文章后面会解释时,形式类似于对象解构,但实际上并无关联。 欢迎访问个人站点 简介 何为模块 一个模块只不过是一个写在文件中的 JavaScript 代码块。 模块中的函数或变量不可用,除非模块文件导出它们。 简单地说,这些模块可以帮助你在你的模块中编写...

    huashiou 评论0 收藏0
  • ES6 完全使用手册

    摘要:前言这里的泛指之后的新语法这里的完全是指本文会不断更新这里的使用是指本文会展示很多的使用场景这里的手册是指你可以参照本文将项目更多的重构为语法此外还要注意这里不一定就是正式进入规范的语法。 前言 这里的 ES6 泛指 ES5 之后的新语法 这里的 完全 是指本文会不断更新 这里的 使用 是指本文会展示很多 ES6 的使用场景 这里的 手册 是指你可以参照本文将项目更多的重构为 ES6...

    kgbook 评论0 收藏0
  • JavaScript 是如何工作:模块构建以及对应打包工具

    摘要:挂机科了次使用这个结构,匿名函数就有了自己的执行环境或闭包,然后我们立即执行。注意,匿名函数的圆括号是必需的,因为以关键字开头的语句通常被认为是函数声明请记住,中不能使用未命名的函数声明。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 20 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可以在这里找到它们: ...

    hedzr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<