ES6 系列之私有变量的实现

时间: 2018-11-21阅读: 802标签: es6

前言

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


1. 约定

实现

class Example {
    constructor() {
        this._private = 'private';
    }
    getName() {
        return this._private
    }
}

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex._private); // private

优点

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

缺点

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


2. 闭包

实现一

/**
 * 实现一
 */
class Example {
  constructor() {
    var _private = '';
    _private = 'private';
    this.getName = function() {return _private}
  }
}

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex._private); // undefined

优点

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

缺点

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

实现二

/**
 * 实现二
 */
const Example = (function() {
  var _private = '';

  class Example {
    constructor() {
      _private = 'private';
    }
    getName() {
      return _private;
    }
  }

  return Example;

})();

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex._private); // undefined

优点

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

缺点

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


3. Symbol

实现

const Example = (function() {
    var _private = Symbol('private');

    class Example {
        constructor() {
          this[_private] = 'private';
        }
        getName() {
          return this[_private];
        }
    }

    return Example;
})();

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex.name); // undefined

优点

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

缺点

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


4. WeakMap

实现

/**
 * 实现一
 */
const _private = new WeakMap();

class Example {
  constructor() {
    _private.set(this, 'private');
  }
  getName() {
      return _private.get(this);
  }
}

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex.name); // undefined

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

/**
 * 实现二
 */
const Example = (function() {
  var _private = new WeakMap(); // 私有成员存储容器

  class Example {
    constructor() {
      _private.set(this, 'private');
    }
    getName() {
        return _private.get(this);
    }
  }

  return Example;
})();

var ex = new Example();

console.log(ex.getName()); // private
console.log(ex.name); // undefined

优点

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

缺点

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


5. 最新提案

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }

  equals(point) {
    return this.#x === point.#x && this.#y === point.#y;
  }
}

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

class Foo {
  private value;

  equals(foo) {
    return this.value === foo.value;
  }
}

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

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

class Foo {
  private value = '1';

  equals(foo) {
    return this.value === foo.value;
  }
}

var foo1 = new Foo();
var foo2 = new Foo();

console.log(foo1.equals(foo2));

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

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

答案是可以的。

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

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

var foo1 = new Foo();

console.log(foo1.equals({
  value: 2
}));

当然这里代码也是可以正常运行的,但是对于编译器来说,就有一点麻烦了,因为编译器不知道 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 的模拟实现、模块加载方案、异步处理等内容。


原文来源:https://segmentfault.com/a/1190000017081250


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/1384

关闭

9个常用ES6特性归纳

顾名思义,用于对象或数组之前的展开操作符(…),将一个结构展开为列表。这种写法够优雅,够简洁吧?如果不用展开操作符

es6数据变更同步到视图层

数据变更同步到视图层有一个很重要的东西就是Proxy,Proxy的作用就是可以隐藏真正的对象,而用户去修改它的代理对象.Proxy可以监听数据的变化,例如

24个常用的es6方法

本文主要介绍 24 中 es6 方法,这些方法都挺实用的,本本请记好,时不时翻出来看看。如何检查元素是否具有指定的类?页面DOM里的每个节点上都有一个classList对象,程序员可以使用里面的方法新增、删除、修改节点上的CSS类。

ES6的循环和可迭代对象

本文将研究 ES6 的 for ... of 循环。在过去,有两种方法可以遍历 javascript。首先是经典的 for i 循环,它使你可以遍历数组或可索引的且有 length 属性的任何对象。

ES6+js原生注意点

Symbol属性私有化,数据保护,let不支持变量声明预解析(先声明后使用),支持块作用域,const不支持变量声明预解析(先声明后使用),不允许重复声明(暂存死区),事件委托,解构赋值

commonjs 与 es6相关Module语法的区别

export 在接口名字与模块内部的变量之间建立了一一对应的关系,export输出的接口; export的写法,除了像上面这样,还有另外一种。export命令除了输出变量,还可以输出函数或类(class)。

ES6新特性:JavaScript中的Map和WeakMap对象

Map对象是一种有对应 键/值 对的对象, JS的Object也是 键/值 对的对象 ;ES6中Map相对于Object对象有几个区别:

使用ES6让你的React代码提升到一个新档次

ES6使您的代码更具表现力和可读性。而且它与React完美配合!现在您已了解更多基础知识:现在是时候将你的ES6技能提升到一个新的水平!嵌套props解构、 传下所有props、props解构、作为参数的函数、列表解构

ES6迭代器精炼讲解

Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。

es6 class

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!