[关闭]
@bornkiller 2017-11-09T15:40:17.000000Z 字数 3021 阅读 1402

OOP 私有变量 JavaScript 描述

前端编程


前言

JavaScript 名义上并不支持传统的面向对象编程。前端刀耕火种阶段,通过构造函数和原型链继承,能够模拟 OOP ,但语法层面无法通过关键词处理 public fieldprivate field,因而在解决部分实际应用场景时会带来困扰,本文将探讨如何处理这些问题。

场景

先假设场景,需要实现简单的栈(遵循 LIFO 原则的有序数据集合)类,实例代码如下:

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. export default class Stack {
  6. constructor() {
  7. this.items = [];
  8. }
  9. push(item) {
  10. this.items.push(item);
  11. }
  12. pop() {
  13. return this.items.pop();
  14. }
  15. }
  1. /**
  2. * @description - consume lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. import Stack from './lib/Stack';
  6. const history = new Stack();
  7. history.push('https://www.baidu.com');
  8. history.push('https://www.google.com');
  9. history.push('https://www.qq.com');
  10. console.log(history.items);

上述代码用以说明场景,其满足 LIFO 原则,但问题处在用于内部存储的数组,Stack 实例可以直接访问 items,意味着外部操作极易影响内部稳定,将其转化为 private field,则可以完美解决问题。

解决方案

代码约定

团队内部使用同样的约定,通过 eslinttslint来强制确保执行,例如使用前缀:

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. export default class Stack {
  6. constructor() {
  7. this._items = [];
  8. }
  9. push(item) {
  10. this._items.push(item);
  11. }
  12. pop() {
  13. return this._items.pop();
  14. }
  15. }

优点:

缺点:

如果使用 transpile to javascript 方案,例如 typescript,可以利用其增强语法进行声明:

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. export default class Stack {
  6. private items: any[];
  7. constructor() {
  8. this.items = [];
  9. }
  10. public push(item) {
  11. this.items.push(item);
  12. }
  13. public pop() {
  14. return this.items.pop();
  15. }
  16. }

具体实例,对私有变量的访问,将无法通过编译:

typescript-private.png-59.9kB

优点:

缺点:

Emulated

应用开发中,通过团队约定的方式,即前述解决方案,能够处理大部分场景。如果安全性要求较高,考虑使用模拟的方式来实现,解决思路主要是避免把 private field 挂载到 this 上。

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. export default class Stack {
  6. constructor() {
  7. const items = [];
  8. this.push = (item) => {
  9. items.push(item);
  10. };
  11. this.pop = () => {
  12. return items.pop();
  13. };
  14. }
  15. }

优点:

缺点:

改造方案如下:

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. const Stack = (function () {
  6. const items = [];
  7. class Stack {
  8. push(item) {
  9. items.push(item);
  10. }
  11. pop() {
  12. return items.pop();
  13. }
  14. }
  15. return Stack;
  16. })();
  17. export default Stack;

优点:

缺点:

再改造方案如下:

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. const Stack = (function () {
  6. const PrivateStorage = Reflect.construct(WeakMap, []);
  7. class Stack {
  8. constructor() {
  9. PrivateStorage.set(this, {
  10. items: []
  11. });
  12. }
  13. push(item) {
  14. const PrivateSet = PrivateStorage.get(this);
  15. const PrivateSetNext = {
  16. ...PrivateSet,
  17. items: [...PrivateSet.items, item]
  18. };
  19. PrivateStorage.set(this, PrivateSetNext);
  20. }
  21. pop() {
  22. const PrivateSet = PrivateStorage.get(this);
  23. return PrivateSet.items.pop();
  24. }
  25. }
  26. return Stack;
  27. })();
  28. export default Stack;

多实例 this 指针不同,即可为不同实例存储各自私有数据。

Native

目前已经出现相关提案,https://github.com/tc39/proposal-class-fields,浏览器尚未支持。

  1. /**
  2. * @description - implement lite Stack
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. export default class Stack {
  6. #items;
  7. constructor() {
  8. this.#items = [];
  9. }
  10. push(item) {
  11. this.#items.push(item);
  12. }
  13. pop() {
  14. return this.#items.pop();
  15. }
  16. }

Contact

Email: hjj491229492@hotmail.com

qrcode_for_gh_d8efb59259e2_344.jpg-8.7kB

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注