详解ES6系列之私有变量的实现


Posted in Javascript onNovember 21, 2018

前言

在阅读 《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

优点

  • 写法简单
  • 调试方便
  • 兼容性好

缺点

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

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

优点

  • 无命名冲突
  • 外部无法访问和修改

缺点

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

实现二

/**
 * 实现二
 */
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

优点

  • 无命名冲突
  • 外部无法访问和修改

缺点

  • 写法有一点复杂
  • 构建增加一点点开销

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. 兼容性也还好

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

优点

  • 无命名冲突
  • 外部无法访问和修改

缺点

  • 写法比较麻烦
  • 兼容性有点问题
  • 有一定性能代价

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 的实例,然后再接着获取值。

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

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

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

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript Keycode对照表
Oct 24 Javascript
jquery悬浮提示框完整实例
Jan 13 Javascript
JSON 的正确用法探讨:Pyhong、MongoDB、JavaScript与Ajax
May 15 Javascript
jQuery实现边框动态效果的实例代码
Sep 23 Javascript
JS基于onclick事件实现单个按钮的编辑与保存功能示例
Feb 13 Javascript
在javascript中,null>=0 为真,null==0却为假,null的值详解
Feb 22 Javascript
JS+canvas绘制的动态机械表动画效果
Sep 12 Javascript
Vue脚手架的简单使用实例
Jul 10 Javascript
详解解决Vue相同路由参数不同不会刷新的问题
Oct 12 Javascript
深入学习JavaScript 高阶函数
Jun 11 Javascript
layui字体图标 loading图标静止不旋转的解决方法
Sep 23 Javascript
Kettle中使用JavaScrip调用jar包对文件内容进行MD5加密的操作方法
Sep 04 Javascript
jQuery实现上下滚动公告栏详细代码
Nov 21 #jQuery
Vue.js 中 axios 跨域访问错误问题及解决方法
Nov 21 #Javascript
vue  directive定义全局和局部指令及指令简写
Nov 20 #Javascript
详解Vue.js在页面加载时执行某个方法
Nov 20 #Javascript
element vue validate验证名称重复 输入框与后台重复验证 特殊字符 字符长度 及注意事项小结【实例代码】
Nov 20 #Javascript
jQuery 同时获取多个标签的指定内容并储存为数组
Nov 20 #jQuery
一步步教你利用Docker设置Node.js
Nov 20 #Javascript
You might like
php+oracle 分页类
2006/10/09 PHP
《PHP边学边教》(01.开篇――准备工作)
2006/12/13 PHP
简单采集了yahoo的一些数据
2007/02/14 PHP
php侧拉菜单 漂亮,可以向右或者向左展开,支持FF,IE
2009/10/15 PHP
php中FTP函数ftp_connect、ftp_login与ftp_chmod用法
2014/11/18 PHP
PHP基于单例模式实现的数据库操作基类
2016/01/15 PHP
JS定时器实例
2013/04/17 Javascript
js中创建对象的几种方式示例介绍
2014/01/26 Javascript
js二维数组定义和初始化的三种方法总结
2014/03/03 Javascript
jQuery学习笔记之toArray()
2014/06/09 Javascript
windows8.1+iis8.5下安装node.js开发环境
2014/12/12 Javascript
使用js画图之正弦曲线
2015/01/12 Javascript
使用jQuery的ajax方法向服务器发出get和post请求的方法
2017/01/13 Javascript
JS+DIV实现的卷帘效果示例
2017/03/22 Javascript
Node.js 基础教程之全局对象
2017/08/06 Javascript
利用express启动一个server服务的方法
2017/09/17 Javascript
node.js中express模块创建服务器和http模块客户端发请求
2019/03/06 Javascript
axios+Vue实现上传文件显示进度功能
2019/04/14 Javascript
Vue路由之JWT身份认证的实现方法
2019/08/26 Javascript
el-table表头根据内容自适应完美解决表头错位和固定列错位
2021/01/07 Javascript
Python中函数及默认参数的定义与调用操作实例分析
2017/07/25 Python
python实现壁纸批量下载代码实例
2018/01/25 Python
Python实现的视频播放器功能完整示例
2018/02/01 Python
Python获取系统所有进程PID及进程名称的方法示例
2018/05/24 Python
Python 图像处理: 生成二维高斯分布蒙版的实例
2019/07/04 Python
使用Python快乐学数学Github万星神器Manim简介
2019/08/07 Python
利用PyQt中的QThread类实现多线程
2020/02/18 Python
在HTML5中如何使用CSS建立不可选的文字
2014/10/17 HTML / CSS
eBay德国站:eBay.de
2017/09/14 全球购物
洛佩桑酒店官方网站:Lopesan Hotels
2019/04/15 全球购物
会计系毕业个人自荐信格式
2013/09/23 职场文书
毕业生求职推荐信
2013/11/04 职场文书
世界遗产导游词
2015/02/13 职场文书
表扬信范文
2015/05/04 职场文书
详解vue中v-for的key唯一性
2021/05/15 Vue.js
Vue+TypeScript中处理computed方式
2022/04/02 Vue.js