JS实现class的私有属性

在JavaScript中,类(Class)作为ES6引入的重要特性之一,为我们提供了更加面向对象的编程范式。然而,与一些其他编程语言(如Java或C#)不同,ES6中的类并没有直接支持私有属性或方法。尽管如此,随着JavaScript语言的不断演进,我们仍然可以通过一些技巧和模式来实现类似私有属性的功能。

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。MDN地址

我们可以先给私有变量一个特殊的命名格式,例如前面加一个下划线 _,当Proxy劫持对象后,根据用户访问的属性名的格式来判断用户即将访问的属性是否是私有属性:

class Stu {
  constructor(name, age, sexy) {
    this._name = name;
    this._age = age;
    this.sexy = sexy
  }
  get info() {
        return `该学生姓名:${this._name},年龄:${this._age}`;
  }
}

// 定义一个处理器
const handler = {
  get (target, key) {
    if (key[0] === '_') {
      throw new Error('禁止访问!');
    }
    return target[key];
  },
  set (target, key, value) {
    if (key[0] === '_') {
      throw new Error('禁止访问!');
    }
    target[key] = value;
  },
  ownKeys(target, prop) {
    return Object.keys(target).filter(key => key[0] !== '_')
  },
}

const zs = new Proxy(new Stu('张三', 18, '男'), handler);
console.log(zs.info);           // 该学生姓名:张三,年龄:18
console.log(Object.keys(zs))    // [ 'sexy' ]
zs._age = 20;                   // 错误:禁止访问!

通过以上代码就实现了通过Proxy去控制对象内属性的访问权限,当用户尝试访问以 _ 开头的属性时就会抛出禁止访问的错误,当然,也可以在get和set中进行其他的处理,例如不抛出错误,而是返回 undefined 。当定义了 ownKeys 时,使用 Object.keys() 时就会过滤掉目标对象中下划线开头的属性再返回。

Symbol

Symbol 是ES6中新增加的一种数据类型,用于创建一个唯一的值,那么怎么通过这个特性来创建私有变量呢?例如如下代码:

const nameSymbol = Symbol('name');
const ageSymbol = Symbol('age');

class Stu {
  constructor(name, age) {
    this[nameSymbol] = name;
    this[ageSymbol] = age;
  }

  get info() {
    return `该学生姓名:${this[nameSymbol]},年龄:${this[ageSymbol]}`;
  }
}

const zs = new Stu('张三', 18);
console.log(zs.info);          // 该学生姓名:张三,年龄:18
console.log(Object.keys(zs))   // []
console.log(zs.age);           // undefined

在如上代码中,我们没有直接使用 nameage 来作为属性名,而是使用了Symbol生成了唯一的名字,所以在外部是拿不到属性名的,相比于proxy方式更加简单,但是,有一个api叫做 Object.getOwnPropertySymbols ,通过这个api可以获取到对象中所有Symbol属性:

const allSymbol = Object.getOwnPropertySymbols(zs);
console.log(allSymbol);         // [ Symbol(name), Symbol(age) ]
console.log(zs[allSymbol[0]]);  // 张三
console.log(zs[allSymbol[1]]);  // 18

那么有没有更加完善的方法呢?

WeakMap

在MDN私有属性一节中,提到了在 # 这个语法出现之前,可以通过 WeakMap 来创建私有变量。WeakMap与Map的区别就是 只能用对象作为 key,对象销毁,这个键值对就销毁。 所以使用 WeakMap 可以避免所有的实例对象都共有同一个Map的问题,代码实现如下:

const wmName = new WeakMap();
const wmAge = new WeakMap();

const wmSet = function (receiver, key, value) {
  key.set(receiver, value);
};
const wmGet = function (receiver, key) {
  return key.get(receiver);
};

class Stu {
  constructor(name, age) {
    wmName.set(this, undefined);
    wmAge.set(this, undefined);

    wmSet(this, wmName, name);
    wmSet(this, wmAge, age);
  }

  get info() {
    return `该学生姓名:${wmGet(this, wmName)},年龄:${wmGet(this, wmAge)}`;
  }
}

const zs = new Stu("张三", 18);
console.log(zs.info);          // 该学生姓名:张三,年龄:18
console.log(Object.keys(zs));  // []
console.log(zs.age);           // undefined

我们针对每一个私有变量,定义了一个 WeakMap ,在对象内部通过 wmGetwmSet 两个函数来进行私有变量的读写,而在对象外部则无法访问到私有变量。这种办法相对于前两种办法更加完善,在js引擎未完全支持前,babel对 # 语法进行转换时就会转为 WeakMap 的实现方式,可以理解为 #WeakMap 的语法糖。

#

在最新的ES标准中,可以通过 # 的方式来标识私有属性和方法。例如:

class Stu {
  #name;
  #age;
  constructor(name, age) {
    this.#name = name;
    this.#age = age;
    this.sexy = '男';
  }
  get info() {
    return `该学生姓名:${this.#name},年龄:${this.#age}`;
  }
}

const zs = new Stu('张三', 18);
console.log(zs.info);          // 该学生姓名:张三,年龄:18
console.log(Object.keys(zs))   // [ 'sexy' ]
console.log(zs.age);           // undefined

虽然使用 # 可以很方便的创建私有变量,但是需要注意该语法的浏览器兼容性,大多是2020年之前的浏览器版本都没有提供相应的支持。

 

 至于为什么要用 # ,TC39 委员会解释道,他们也是做了深思熟虑最终选择了 # 符号,而没有使用 private 关键字。其中还讨论了把 private 和 # 符号一起使用的方案。并且还打算预留了一个 @ 关键字作为 protected 属性 。

文章转自:https://juejin.cn/post/7372108850269487116

  • 33
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值