我们在开发过程中,有时候期望使一个属性只有内部可以访问,而外部无法访问。本章我们就探寻一下如何使一个属性私有化
命名约定
约定私有属性的命名为一个带有前缀的属性,通常使用下划线_
。一起看一下下面的代码:
class MyClass {
_name = "ray";
getName = () => {
return this._name;
};
}
const myClass = new MyClass();
console.log(myClass._name);
// 输出 ray
console.log(myClass.getName());
// 输出 ray
从上面的代码可以看出,虽然这并没有真正保护属性不被访问,但是表示这是一个私有属性,开发人员应该尽量避免直接访问它。
闭包
使用闭包来创建一个私有变量,它只能在闭包内部访问。如下:
class MyClass {
getName = () => {
const _name = "ray";
return _name;
};
}
const myClass = new MyClass();
console.log(myClass._name);
// 输出 undefined
console.log(myClass.getName());
// 输出 ray
从上面代码可以看出,通过在类中创建一个私有变量,并将其作为公有方法的一部分返回,可以实现私有属性的效果。这样可以确保在闭包外部无法直接访问私有变量,只能通过公有方法来访问它。因此,使用闭包可以实现一种模拟私有属性的机制。
需要注意的是,在JavaScript中,闭包可以创建一个
独立的作用域
,其中的变量在方法执行结束后仍然可以访问。换句话说,闭包会导致变量一直存在于内存中,即使它们已经不再需要,可能会造成内存泄漏,导致性能下降
Symbol
使用Symbol
创建一个唯一的标识符来模拟私有属性。由于 Symbol 是唯一的,因此开发人员无法直接获取该属性
const nameSymbol = Symbol("name");
class MyClass {
[nameSymbol] = "ray";
getName = () => {
return this[nameSymbol];
};
}
const myClass = new MyClass();
console.log(myClass.name);
// 输出 undefined
console.log(myClass.getName());
// 输出 ray
从上面的代码可以看出,我们使用getName
可以拿到这个私有属性,当我们直接获取时拿到的是一个undefined
。这虽然实现了我们的预期,但是如果我们拿到nameSymbol
的引用,我们依然是可以获取到这个属性
console.log(myClass[nameSymbol]);
// 输出 ray
当我们不知道一个Symbol属性的引用时,我们依然可以通过Object.getOwnPropertySymbols
获取到该引用
// a.js
const nameSymbol = Symbol("name");
export class MyClass {
[nameSymbol] = "ray";
getName = () => {
return this[nameSymbol];
};
}
// b.js
import { MyClass } from "./a.js";
var myClass = new MyClass();
var symbols = Object.getOwnPropertySymbols(myClass);
console.log(symbols);
// 输出 [ Symbol(name) ]
const name = symbols[0];
console.log(myClass[name]);
// 输出 ray
私有属性#
为了解决以上实现私有属性所存在的问题,在es12
中引入了新的语法,使用增加哈希前缀#
的方法来定义私有类字段
class MyClass {
#name = "ray";
getName = () => {
return this.#name;
};
}
const myClass = new MyClass();
console.log(myClass.getName());
// 输出 ray
console.log(myClass.#name);
console.log(myClass.#name);
/**
* 编辑器抛出异常
* 属性 "#name" 在类 "MyClass" 外部不可访问,因为它具有专用标识符。
*/