私有变量即只能在对象内部被访问 外部不能访问。
这里总结下实现私有变量的六种方式:
Symbol
javascript
const _name = Symbol("_name");
export class P {
constructor() {
this[_name] = "test";
this.age = 18;
}
say() {
return `name = ${this[_name]}, age = ${this.age}`;
}
}
// 利用模块化不导出_name 只导出对象P
const p = new P();
console.log("keys:", Object.keys(p)); // keys: [ 'age' ]
console.log("_name: ", "_name" in p); // _name: false
console.log("_name: ", p._name, " age: ", p.age); // _name: undefined age: 18
console.log("say: ", p.say()); // say: name = test, age = 18
但是Symbol值并不是真正的私有的 是可以通过getOwnPropertySymbols
获取到的。
javascript
Object.getOwnPropertySymbols(p).forEach((key) => {
console.log("symbol key: ", p[key]);
});
// symbol key: test
缺点:并不是真正的属性私有,但是大部分场景是可以满足的 如果没有严格要求是可以使用的。
weakmap
symbol的问题在于我们把属性挂在了对象上然后导出了对象 导致属性被访问到。
javascript
let info = new Map();
export class P1 {
constructor() {
info.set('_name', 'test')
this.age = 18;
}
say() {
return `name = ${info.get('_name')}, age = ${this.age}`;
}
}
const p = new P();
console.log("keys:", Object.keys(p)); // keys: [ 'age' ]
console.log("say: ", p.say()); // say: name = test, age = 18
这里将属性存储在map里面然后利用模块化 不导出info也不挂在this上 可以实现变量的私有。 然而问题是多个实例会共享配置而且对象被map引用无法被释放。
javascript
let info = new Map();
export class P1 {
constructor() {
info.set('_name', 'test')
this.age = 18;
}
say() {
return `name = ${info.get('_name')}, age = ${this.age}`;
}
}
export class P2 {
constructor() {
this.age = 20;
}
say() {
return `name = ${info.get('_name')}, age = ${this.age}`;
}
}
const p2 = new P2();
console.log("say: ", p2.say()); // say: name = test, age = 18
所以我们可以改为weakmap实现。
javascript
let info = new WeakMap();
export class P1 {
constructor() {
info.set(this, 'test')
this.age = 18;
}
say() {
return `name = ${info.get(this)}, age = ${this.age}`;
}
}
export class P2 {
constructor() {
this.age = 20;
}
say() {
return `name = ${info.get(this)}, age = ${this.age}`;
}
}
const p1 = new P1();
console.log("say: ", p1.say()); // say: name = test, age = 18
const p2 = new P2();
console.log("say: ", p2.say()); // say: name = undefined, age = 18
proxy
通过代理拦截属性的读取,赋值,查询操作 达到属性私有的目的。
javascript
class P {
constructor() {
this._name = 'test';
this.age = 18;
}
say() {
return `name = ${this._name}, age = ${this.age}`;
}
}
const handler = {
set(target, prop, value, receiver) {
if (prop.startsWith('_')) {
return true;
}
Reflect.set(target, prop, value, receiver)
},
get(target, prop, receiver) {
if (prop.startsWith('_')) {
return;
}
return Reflect.get(target, prop, receiver);
},
ownKeys(target) {
const keys = Reflect.ownKeys(target);
return keys.filter((key) => !key.startsWith('_'));
},
has(target, prop){
if (prop.startsWith('_')) {
return false;
}
return Reflect.has(target, prop)
}
};
const p = new P();
const proxy = new Proxy(p, handler);
console.log('keys:', Object.keys(proxy)) // keys: [ 'age' ]
console.log('_name: ', '_name' in proxy); // _name: false
console.log('_name: ', proxy._name, ' age: ', proxy.age) // _name: undefined age: 18
然后调用
arduino
console.log('say:', proxy.say());
// say:name = undefined, age = 18
因为在say方法中this._name
中的this指向的是proxy代理对象 然后走到了get被拦截了 所以在这种情况我们需要指定say方法的this指向原始的对象即代理中的target
。
get钩子修改如下:
kotlin
get(target, prop, receiver) {
if (prop.startsWith("_")) {
return;
}
// 如果是函数 绑定this执向target原始对象
if (typeof target[prop] === "function") {
return target[prop].bind(target);
}
return Reflect.get(target, prop, receiver);
}
缺点:虽然可以实现属性的私有化但是过于复杂了还需要处理this指向问题,不建议
ES2022
javascript
class P {
#name = "test";
say() {
return `name = ${this.#name}`;
}
}
const p = new P();
console.log(Object.keys(p)); // []
console.log(p.say()); // name = test
'#'标识私有属性,通过babel编译后: babel-plugin-transform-private-property-in-object
javascript
function _classPrivateFieldInitSpec(obj, privateMap, value) {
_checkPrivateRedeclaration(obj, privateMap);
privateMap.set(obj, value);
}
function _checkPrivateRedeclaration(obj, privateCollection) {
if (privateCollection.has(obj)) {
throw new TypeError(
"Cannot initialize the same private elements twice on an object"
);
}
}
function _classPrivateFieldGet(receiver, privateMap) {
var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get");
return _classApplyDescriptorGet(receiver, descriptor);
}
function _classExtractFieldDescriptor(receiver, privateMap, action) {
if (!privateMap.has(receiver)) {
throw new TypeError(
"attempted to " + action + " private field on non-instance"
);
}
return privateMap.get(receiver);
}
function _classApplyDescriptorGet(receiver, descriptor) {
if (descriptor.get) {
return descriptor.get.call(receiver);
}
return descriptor.value;
}
var _name = /*#__PURE__*/ new WeakMap();
var P = /*#__PURE__*/ (function () {
"use strict";
function P() {
_classPrivateFieldInitSpec(this, _name, {
writable: true,
value: "test"
});
}
var _proto = P.prototype;
_proto.say = function say() {
return "name " + _classPrivateFieldGet(this, _name);
};
return P;
})();
可以看到 也是通过WeakMap来实现的。
函数闭包
闭包可以让你在一个函数内部创建一个变量,这个变量对于函数外部是不可见的,但是对于这个函数内部的其他函数来说是可见的
javascript
function createObject() {
var privateProperty = "I am private";
return {
getPrivateProperty: function() {
return privateProperty;
}
};
}
var obj = createObject();
console.log(obj.getPrivateProperty()); // "I am private"
console.log(obj.privateProperty);
ts的private
typescript
class P {
private _name='test';
say() {
return `name ${this._name}`;
}
}
const p = new P();
console.log(p._name) // 提示 属性为私有属性,只能在类中访问
但是这个只能作用于编译时 运行时读取还是可以访问到。
typescript
class Person {
private name='test';
say() {
return `name ${this.name}`;
}
}
const p1 = new Person();
console.log(p1['name']) // test
console.log(p1.say()) // name test
因为ts代码经过编译后会被去除 可以看如下编译后的代码
javascript
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : String(i);
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var P = /*#__PURE__*/ (function () {
"use strict";
function P() {
_defineProperty(this, "_name", "test");
}
var _proto = P.prototype;
_proto.say = function say() {
return "name " + this._name;
};
return P;
})();
_defineProperty(this, "_name", "test");
依然是将_name属性添加到了this上 所以还是可以访问到。