阅读 130

JavaScript-使用WeakMap创建对象的私有属性

我们都知道JavaScript本身并没有共有、私有属性的概念,不过可以通过一些方式实现私有属性。
WeakMap也是ES6里就有了,不过我曾一直不太了解它的应用场景,看到有文章说用它的应用场景之一是实现私有属性。怎么实现?为何要用它实现?
关于用WeakMap实现私有属性的方式,本文会从它最原始的面貌看起,理解使用WeakMap的意义

闭包-可行吗?

提到“私有”的实现方式,我的第一反应就是闭包,似乎可行:

const Person = (function() {
  let name;

  function Person(n) {
      name = n;
  }

  Person.prototype.getName = function() {
      return name;
  };

  return Person;
}());
let person1 = new Person('小明');复制代码

这样person1就有它的私有name属性了,外部无法直接访问或修改改属性。等等,似乎有什么问题,如果我们再实例化一次Person呢?

let person1 = new Person('小明');
let person2 = new Person('大明');
console.log(person1.getName()); // 大明复制代码

小明变成了大明!person1name属性被覆盖了,所以这种方式实现的私有属性,实际上是被所有实例共享的,如果需要每个实例单独拥有自己的私有属性,这种方法就不行了。

改进

为了让每个实例拥有自己的私有属性,相互之间不影响,我们可以引入一个id作为每个实例的唯一标识,这样就实现了真正的私有属性:

const Person = (function() {
  const private = {};
  let privateId = 0;

  function Person(name) {
    this._privateId = privateId++;
    private[this._privateId] = {};
    private[this._privateId].name = name;
  }

  Person.prototype.getName = function() {
      return private[this._privateId].name;
  };

  return Person;
}());

let person1 = new Person('小明');
let person2 = new Person('大明');
console.log(person1.getName()); // 小明
复制代码

不过这样仍然有隐患,实例化后我们还是能更改其_privateId属性的值,一旦它被更改,私有属性就获取不到了。

person1._privateId = 111;
console.log(person1.getName()); // 报错
复制代码

所以我们改为用Object.defineProperty方法申明_private属性,防止它被更改。

Object.defineProperty(this, '_privateId', {
  value: privateId++,
  writable: false, // 设为不可写,_privateId就不会被更改了(其实默认就是false)
});
this._privateId = privateId++;
private[this._privateId] = {};
private[this._privateId].name = name;
复制代码

至此,我们仅用es5的特性就实现了私有属性,而然该方法还有以下几个弊端:

  • 即便Person的某个实例对象被垃圾回收了,private对象里存储的它的全部私有属性依旧不会被回收,这会导致内存泄漏问题
  • 每个实例对象多出了一个_privateId属性,而且该方法不够直观优雅

WeakMap了解一下?

既然我们不想多造一个单独的_privateId属性去实现私有属性的存储,那还有什么值可以作为该实例对象的唯一标识呢?它的内存地址?可是js似乎不允许直接获取一个对象的地址。直接拿对象本身作为key可以吗?

private[this] = {};
private[this].name = name;
复制代码

这么写在语法上当然没问题,但实际上一个对象的key只能是字符串或Symbol,因而所有对象都会被转为同一段符串“[object Object]”(private[this] = {}实际上就是private[‘[object Object]’] = {}),所以这种方法当然不行。
而ES6的Map和WeakMap是可以将对象作为key的(WeakMap的key只能是对象)。那我们用WeakMap试一下?

const Person = (function() {
  const private = new WeakMap();

  function Person(name) {
    private.set(this, {});
    private.get(this).name = name;
  }

  Person.prototype.getName = function() {
      return private.get(this).name;
  };

  return Person;
}());

let person1 = new Person('小明');
let person2 = new Person('大明');
console.log(person1.getName()); // 小明
复制代码

现在代码看上去简单优雅了很多,我们不再需要额外的id去标识每个实例,那内存泄漏问题呢?这就是为何要用WeakMap而不是Map了,WeakMap对key对象仅有“弱引用”,当没有其它引用指向该key对象时,该对象即可被垃圾回收,WeakMap不会阻止回收。


其实用WeakMap实现私有属性本身是比较简单的,但了解其背后的原因和原理更加重要。

感谢阅读,希望能给你带来一些收货~



关注下面的标签,发现更多相似文章
评论