JavaScript 中的私有变量
近年来 JavaScript 不断有新特性或语法加进来,不过还是始终保持一切皆对象的原则,基于运行时的概念,JavaScript 并没有所谓的公共、私有属性的概念。
自 ES6 以后,虽然 JavaScript 有了 类,但不像 C、Java 那样有专门的修饰符来控制变量的访问权限,JavaScript 所有的属性都需要在函数中定义。以下文章内容将介绍如何实现私有变量。
约定命名规则的方式
最简单粗暴的方式就是在团队中约定命名规范,通常是以下划线作为属性名称的前缀(e.g. _count)。其本质上并没有阻止变量的访问权限,仅仅是开发之间默认的规则,即不能乱引用和修改我的变量。
WeakMap
WeakMap 虽然不会阻止对数据的访问,但是它能将私有变量和用户的可操作对象分开,示例代码如下:
const map = new WeakMap(); // Create an object to store private values in per instance const internal = obj => { if (!map.has(obj)) { map.set(obj, {}); } return map.get(obj); } class Shape { constructor(width, height) { internal(this).width = width; internal(this).height = height; } get area() { return internal(this).width * internal(this).height; } } const square = new Shape(10, 10); console.log(square.area); // 100 console.log(map.get(square)); // { height: 100, width: 100 }
以上代码中,将 WeakMap 的关键字设置为私有属性所属对象的实例,通过一个函数来管理返回值,如无则创建之,所有的属性将被存储在其中。在 Shape 实例中,遍历属性或者在执行 JSON.stringify 等都不会展示出实例的私有属性。
Symbol
Symbol 的实现方式与 WeakMap 类似,不过这种实现方式需要为每个私有属性创建一个 Symbol,但是在类外还是可以访问该 Symbol,即还是可以拿到这个私有属性。示例代码如下:
const widthSymbol = Symbol('width'); const heightSymbol = Symbol('height'); class Shape { constructor(width, height) { this[widthSymbol] = width; this[heightSymbol] = height; } get area() { return this[widthSymbol] * this[heightSymbol]; } } const square = new Shape(10, 10); console.log(square.area); // 100 console.log(square.widthSymbol); // undefined console.log(square[widthSymbol]); // 10
闭包
前面介绍的几种方式仍然允许从类外访问私有属性,闭包是将私有变量数据封装在调用时创建的函数作用域内,从内部返回函数的结果,从而使这一作用域无法从外部访问。闭包想必不用再介绍了吧,这里可以联想一下常出现的一到面试题:JavaScript 实现一个私有变量,每次调用一个函数自动加 1。
Proxy
通俗的说 Proxy 在数据外层套了个壳,然后通过这层壳访问内部的数据,即在原对象的基础上进行了功能的衍生而又不影响原对象。理解如何使用 Proxy 实现私有变量,这里我们需要关注 set 和 get。
使用 Proxy 的方式是:let proxy = new Proxy(target, handler);
,Proxy 构造函数中的两个参数具体是:
- target 是用 Proxy 包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- handler 是一个对象,其声明了代理 target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数
一个示例代码如下:
class Shape { constructor(width, height) { this._width = width; this._height = height; } get area() { return this._width * this._height; } } const handler = { get: function(target, key) { if (key[0] === '_') { throw new Error('Attempt to access private property'); } return target[key]; }, set: function(target, key, value) { if (key[0] === '_') { throw new Error('Attempt to access private property'); } target[key] = value; } } const square = new Proxy(new Shape(10, 10), handler); console.log(square.area); // 100 console.log(square instanceof Shape); // true square._width = 200; // Error: Attempt to access private property
如上代码,我们实例化了一个 Proxy,在 target 参数中引入了 Shape 对象,该类里边实现了两个变量,通过 square 是没法直接访问里边的 _width 和 _height 的,这就通过代理实现了私有变量的效果。
TypeScript 中的 private
TypeScript 是 JavaScript 的超集,最终是编译为原生 JavaScript 用在生产环境。TS 支持指定私有、公共和受保护的属性,这里就不再像原文那样举例了。
需要提醒的是,使用 TypeScript 只有在编译时才能获知到这些类型,而私有、公共和受保护修饰符在编译时才起作用。所以,其实你可以使用 x.y 访问内部私有变量的,只不过 TypeScript 会在编译时给你报出一个错误,但不会停止它的编译。其实并不是真正意义上的私有变量,只是在开发编译的时候能够提醒到开发人员,间接达到实现私有变量的效果。
private fields 的方式,即 # 符号
其实在 TC39 中已有提案引入 private fields,目前还在 Stage 3 阶段,它使用 # 符号表示它是私有的,# 的使用方式与以上提的命名约定方式非常类似,但对变量的实际访问权限提供了限制。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论