约定命名版
约定以 _
开头的属性为私有属性
这种不是真正的私有属性,可以直接访问自定义的私有属性命名
class ClassA {
constructor(x) {
this._x = x
}
getX() {
return this._x
}
}
const a = new ClassA(1)
// 可以直接访问自定义的私有属性命名
console.log(a._x) // 1
console.log(a.getX()) // 1
内部变量版
class ClassB {
constructor(x) {
let _x = x
this.getX = function() {
return _x
}
}
}
const b1 = new ClassB(1)
console.log(b1._x) // undefined
console.log(b1.getX()) // 1
const b2 = new ClassB(2)
console.log(b1.getX(), b2.getX()) // 1 2
闭包版
- 普通闭包版(实例之间会共享变量)
const ClassA = (function() {
let _x
return class {
constructor(x) {
_x = x
}
getX() {
return _x
}
}
})()
const a1 = new ClassA(1)
console.log(a1._x) // undefined
console.log(a1.getX()) // 1
console.log(a1 instanceof ClassA) // true
const a2 = new ClassA(2)
console.log(a1.getX(), a2.getX()) // 2 2 --- 实例之间会共享变量!!!
Symbol
版闭包
const ClassB = (function() {
const _x = Symbol('x')
return class {
constructor(x) {
this[_x] = x
}
getX() {
return this[_x]
}
}
})()
const b1 = new ClassB(3)
console.log(b1._x) // undefined
console.log(b1.getX()) // 3
console.log(b1 instanceof ClassB) // true
const b2 = new ClassB(4)
console.log(b1.getX(), b2.getX()) // 3 4
WeakMap
版闭包
const ClassC = (function() {
const _x = new WeakMap()
return class {
constructor(x) {
_x.set(this, x)
}
getX() {
return _x.get(this)
}
}
})()
const c1 = new ClassC(5)
console.log(c1._x) // undefined
console.log(c1.getX()) // 5
const c2 = new ClassC(6)
console.log(c1.getX(), c2.getX()) // 5 6
- 多个私有属性的
WeakMap
版闭包
const ClassD = (function() {
const _x = new WeakMap()
return class {
constructor(x, y) {
_x.set(this, { x, y })
}
getX() {
return _x.get(this).x
}
}
})()
const d1 = new ClassD(7, 8)
const d2 = new ClassD(9, 10)
console.log(d1.getX(), d2.getX()) // 7 9
WeakMap版
WeakMap
的正确做法应该如下
const map = new WeakMap()
// 创建一个在每个实例中存储私有变量的对象
const internal = obj => {
if (!map.has(obj)) {
map.set(obj, {})
}
return map.get(obj)
}
class ClassA {
constructor(name, age) {
// Object.assign(internal(this), { x, y })
internal(this).name = name
internal(this).age = age
}
get userInfo() {
return '姓名:' + internal(this).name + ',年龄:' + internal(this).age
}
}
const a1 = new ClassA('wmm', 18)
const a2 = new ClassA('66', 20)
console.log(a1.name) // undefined
console.log(a1.age) // undefined
console.log(a1.userInfo) // 姓名:wmm,年龄:18
console.log(a2.userInfo) // 姓名:66,年龄:20
Proxy版
使用Proxy
拦截属性前缀是_
的读写操作
class Student {
constructor(name, age) {
this._name = name
this._age = age
this.job = 'fe'
}
get userInfo() {
return '姓名:' + this._name + ',年龄:' + this._age
}
get getName() {
return this._name
}
sayName() {
// console.log('say: ', this._name) // 这种方式会报错 会访问 this._name
console.log('say: ', this.getName) // 得这么写
}
}
const handler = {
get: function(target, key) {
if (key[0] === '_') {
// 访问私有属性,返回一个 error
throw new Error('Attempt to access private property')
} else if (key === 'toJSON') {
// 只返回公共属性
const obj = {}
for (let k in target) {
if (k[0] !== '_') {
obj[k] = target[k]
}
}
return () => obj
}
// 访问公共属性,默认返回
return target[key]
},
set: function(target, key, value) {
if (key[0] === '_') {
throw new Error('Attempt to access private property')
}
target[key] = value
},
// 解决私有属性能遍历问题,通过访问属性对应的属性描述符,然后设置 enumerable 为 false
getOwnPropertyDescriptor(target, key) {
const desc = Object.getOwnPropertyDescriptor(target, key)
if (key[0] === '_') {
desc.enumerable = false
}
return desc
}
}
const stu = new Proxy(new Student('wmm', 18), handler)
console.log(stu.userInfo) // 姓名:wmm,年龄:18
// console.log(stu.getName()) // 这种方式会报错
console.log(stu instanceof Student) // true
console.log(JSON.stringify(stu)) // {"job":"fe"}
for (let k in stu) {
console.log(k) // job
}
// stu._name = 'wmm66' // Error: Attempt to access private property
stu.sayName() // 这种方式也会报错
Typescript版
Typescript
中,使用private
关键字即可
class Student1 {
private name
private age
constructor(name, age) {
this.name = name
this.age = age
}
get userInfo() {
return '姓名:' + this.name + ',年龄:' + this.age
}
}
const stu = new Student1('wmm', 18)
console.log(stu.userInfo) // 姓名:wmm,年龄:18
console.log(stu instanceof Student1) //
console.log(JSON.stringify(stu)) //
for (let k in stu) {
console.log(k) //
}
ECMAScript提案
ES6
尚不支持class
的私有属性。
有一个提案是在属性或者方法名前加#
表示私有的;只能在类的内部使用,如果在类的外部使用,就会报错。
可以设置私有属性、私有方法,私有属性也可以设置getter
和setter
方法
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
发表评论