Class私有属性实现

约定命名版

约定以 _ 开头的属性为私有属性

这种不是真正的私有属性,可以直接访问自定义的私有属性命名

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的私有属性。
有一个提案是在属性或者方法名前加#表示私有的;只能在类的内部使用,如果在类的外部使用,就会报错。

可以设置私有属性、私有方法,私有属性也可以设置gettersetter方法

class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: