如何在ES6中管理类的私有数据?本文为你介绍四种方法:

  • 在类的构造函数作用域中处理私有数据成员

  • 遵照命名约定(例如前置下划线)标记私有属性

  • 将私有数据保存在WeakMap中

  • 使用Symbol作为私有属性的键

对构造函数来说,前两种方法在 ES5 中已经很常见了,后两种方法是 ES6 中新出现的。现在我们在同一个案例上分别用这四种方法来实践一下:

1. 在类的构造函数作用域中处理私有数据成员

我们要演示的这段代码是一个名为 Countdown 的类在 counter(初始值为 counter)变成0时触发一个名为 action 的回调函数。其中 action 和 counter 两个参数应被存储为私有数据。

在这个实现方案中,我们将 action 和 counter 存储在 constructor 这个类的环境里面。环境是指JS引擎存储参数和本地变量的内部数据结构,变量存在即可,无论是否进入一个新的作用域(例如通过一个函数调用或者一个类调用)。来看看代码:

class Countdown {
   constructor(counter, action) {
       Object.assign(this, {
           dec() {
               if (counter < 1) return;
               counter--;
               if (counter === 0) {
                   action();
               }
           }
       });
   }
}

然后这样使用 Countdown:

> let c = new Countdown(2, () => console.log('DONE'));
> c.dec();
> c.dec();
DONE

优点:

  • 私有数据非常安全;

  • 私有属性的命名不会与其他父类或子类的私有属性命名冲突。

缺点:

  • 当你需要在构造函数内把所有方法(至少那些需要用到私有数据的方法)添加到实例的时候,代码看起来就没那么优雅了;

  • 作为实例方法,代码会浪费内存;如果作为原型方法,则会被共享。

关于此方法的更多内容请参考:《Speaking Javascript》的 Private Data in the Environment of a Constructor (Crockford Privacy Pattern) (构造函数环境中的私有数据)章节。

2. 通过命名约定来标记私有属性

下面的代码将私有数据保存在添加了前置下划线命名的属性中:

class Countdown {
   constructor(counter, action) {
       this._counter = counter;
       this._action = action;
   }
   dec() {
       if (this._counter < 1) return;
       this._counter--;
       if (this._counter === 0) {
           this._action();
       }
   }
}

优点:

  • 代码比较美观;

  • 可以使用原型方法。

缺点:

  • 不够安全,只能用规范去约束用户代码;

  • 私有属性的命名容易冲突。

3. 通过 WeakMaps 保存私有数据

有一个利用 WeakMap 的小技巧,结合了方法一和方法二各自的优点:安全性和能够使用原型方法。可以参考以下代码:我们利用_counter 和 _action 两个WeakMap来存储私有数据。

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
   constructor(counter, action) {
       _counter.set(this, counter);
       _action.set(this, action);
   }
   dec() {
       let counter = _counter.get(this);
       if (counter < 1) return;
       counter--;
       _counter.set(this, counter);
       if (counter === 0) {
           _action.get(this)();
       }
   }
}

_counter 和 _action 这两个 WeakMap 都分别指向各自的私有数据。由于 WeakMap 的设计目的在于键名是对象的弱引用,其所对应的对象可能会被自动回收,只要不暴露 WeakMap ,私有数据就是安全的。如果想要更加保险一点,可以将WeakMap.prototype.get 和 WeakMap.prototype.set 存储起来再调用(动态地代替方法)。这样即使有恶意代码篡改了可以窥探到私有数据的方法,我们的代码也不会受到影响。但是,我们只保护我们的代码不受在其之后执行的代码的干扰,并不能防御先于我们代码执行的代码。

优点:

  • 可以使用原型方法;

  • 比属性命名约定更加安全;

  • 私有属性命名不会冲突。

Con:

  • 代码不如命名约定优雅。

4. 使用Symbol作为私有属性的键名

另外一个存储私有数据的方式是用 Symbol 作为其属性的键名:

const _counter = Symbol('counter');
const _action = Symbol('action'); class Countdown {
   constructor(counter, action) {
       this[_counter] = counter;
       this[_action] = action;
   }
   dec() {
       if (this[_counter] < 1) return;
       this[_counter]--;
       if (this[_counter] === 0) {
           this[_action]();
       }
   }
}

每一个 Symbol 都是唯一的,这就是为什么使用 Symbol 的属性键名之间不会冲突的原因。并且,Symbol 某种程度上来说是隐式的,但也并不完全是:

let c = new Countdown(2, () => console.log('DONE'));

console.log(Object.keys(c));
// []
console.log(Reflect.ownKeys(c));
// [Symbol(counter), Symbol(action)]

优点:

  • 可以使用原型方法;

  • 私有属性命名不会冲突。

缺点:

  • 代码不如命名约定优雅;

  • 不太安全:可以通过 Reflect.ownKeys() 列出一个对象所有的属性键名(即使用了 Symbol)。

延伸阅读:

  • Sect. “Keeping Data Private”(http://speakingjs.com/es5/ch17.html#private_data_for_objects) in “Speaking JavaScript” (covers ES5 techniques)

  • Chap. “Classes” (http://exploringjs.com/es6/ch_classes.html)in “Exploring ES6”

  • Chap. “Symbols”(http://exploringjs.com/es6/ch_symbols.html) in “Exploring ES6”

160803、如何在ES6中管理类的私有数据的更多相关文章

  1. ES6中的类

    前面的话 大多数面向对象的编程语言都支持类和类继承的特性,而JS却不支持这些特性,只能通过其他方法定义并关联多个相似的对象,这种状态一直延续到了ES5.由于类似的库层出不穷,最终还是在ECMAScri ...

  2. Nodejs与ES6系列4:ES6中的类

    ES6中的类 4.1.class基本语法 在之前的javascript语法中是不存在class这样的概念,如果要通过构造函数生成一个新对象代码 function Shape(width,height) ...

  3. ES6中的类继承和ES5中的继承模式详解

    1.ES5中的继承模式 我们先看ES5中的继承. 既然要实现继承,首先我们得要有一个父类. Animal.prototype.eat = function(food) { console.log(th ...

  4. TypeScript完全解读(26课时)_8.ES6精讲-ES6中的类(进阶)

    8.TypeScript完全解读-ES6精讲-类(进阶) 在index.ts内引入 Food创建的实例赋值给Vegetabled这个原型对象,这样使用Vegetables创建实例的时候,就能继承到Fo ...

  5. ES6中。类与继承的方法,以及与ES5中的方法的对比

    // 在ES5中,通常使用构造函数方法去实现类与继承 // 创建父类 function Father(name, age){ this.name = name; this.age = age; } F ...

  6. koa 基础(十八)es6中的类、静态方法、继承

    1.app.js /** * es6中的类.静态方法.继承 */ // 定义Person类 class Person { constructor(name, age) { /*类的构造函数,实例化的时 ...

  7. es6中class类的全方面理解(一)

    传统的javascript中只有对象,没有类的概念.它是基于原型的面向对象语言.原型对象特点就是将自身的属性共享给新对象.这样的写法相对于其它传统面向对象语言来讲,很有一种独树一帜的感脚!非常容易让人 ...

  8. Electron-vue实战(三)— 如何在Vuex中管理Mock数据

    Electron-vue实战(三)— 如何在Vuex中管理Mock数据 作者:狐狸家的鱼 本文链接:Vuex管理Mock数据 GitHub:sueRimn 在vuex中管理mock数据 关于vuex的 ...

  9. 如何在Java中测试类是否是线程安全的

    通过优锐课的java核心笔记中,我们可以看到关于如何在java中测试类是否线程安全的一些知识点汇总,分享给大家学习参考. 线程安全性测试与典型的单线程测试不同.为了测试一个方法是否是线程安全的,我们需 ...

随机推荐

  1. PayPal 对接

    时间:2019-1-29 15:39:39 文章相应链接: 官方SDK:https://developer.paypal.com/docs/api/rest-sdks/ 官方API:https://d ...

  2. photoshop 动作 自己定义快捷键 播放选定的动作

    今天在制作一组效果图.要用到动作.而且是同一个动作,便在网上寻找"播放选定的动作"就是那个三角形播放button的快捷键. 预期这样会大大加快制作过程. 首先制作好动作. 然后,在 ...

  3. memcahce文章精选

    浅谈分布式缓存那些事儿  http://os.51cto.com/art/201306/397999.htm

  4. NFC Basics(基本NFC)——翻译自developer.android.com

    NFC Basics 关于收发NDEF格式的消息,以及相关的api. 非NDEFdata以及其它更深入技术请參见Advanced NFC. 在android端使用NDEF有两种情况: - 从标签读取N ...

  5. Jquery学习笔记(11)--jquery的ajax删除用户,非常简单!

    jquery的ajax,简直简单!!只要一个$.get(url,map,function)就搞定了! index.php: <!DOCTYPE html> <html lang=&q ...

  6. 1 App Components - App组件

    Android应用框架可以让你使用一系列可重用的组件来创建极其丰富和新颖的应用.本节说明了Android应用如何工作,如何使用组件来创建应用. Managing the Activity Lifecy ...

  7. redis存储对象与对象序列化详解

    redis主要存储类型最常用的五种数据类型: String Hash List Set Sorted set redis存储对象序列化和反序列化 首先来了解一下为什么要实现序列化 为什么要实现序列化接 ...

  8. treeMap,key排序,value排序

    HashMap与TreeMap按照key和value排序 使用一个场景是mapreduce中用解决topn问题是用value 排序 topn MapReducetopN

  9. ecmall的物流配送体系改造

    接触多了ecshop.ecmall原始逻辑的,一般都习惯以整单的方式统一计算运费,这是一种很简单的思路. 但淘宝多了,就发现,物流运费没有那么简单. 首先,每种商品单独设置运费的体系,或者叫运费模板: ...

  10. visitor设计模式记录

    数据类型通过枚举来区分是一种简单实用的做法. 缺点是使用的时候需要通过if .switch 去判断什么类型执行什么分支操作,说是缺点其实也要看具体场景.不过如果if代码多会导致代码很长是肯定的. 复杂 ...