我们都知道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()); // 大明复制代码
person1
的
name
属性被覆盖了,所以这种方式实现的私有属性,实际上是被所有实例共享的,如果需要每个实例单独拥有自己的私有属性,这种方法就不行了。
改进
为了让每个实例拥有自己的私有属性,相互之间不影响,我们可以引入一个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实现私有属性本身是比较简单的,但了解其背后的原因和原理更加重要。
感谢阅读,希望能给你带来一些收货~