07-动态属性
9.2.2 动态属性
Car 类中的 shift 方法貌似很智能,因为它能防止选择一个无效的档位。不过这种保护也有它的局限性,因为可以直接为它赋值: car1.userGear = 'X' 。大部分面向对象语言都会竭尽全力提供一些机制来阻止这种直接赋值被滥用,它们允许为类的方法和属性指定访问级别。而JavaScript没有这种机制,这也是JavaScript语言中饱受非议的一点。
动态属性可以稍微有效地弥补这种不足[1]。它们具有属性的语义,但同时可以像方法一样被调用。可以通过修改Car类来看看它的好处:
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
this._userGears = ['P', 'N', 'R', 'D'];
this._userGear = this.userGears[0];
}
get userGear() { return this.userGear; }
set userGear(value) {
if(this._userGears.indexOf(value) < 0)
throw new Error('Invalid gear: ${value}');
this._userGear = vaule;
}
shift(gear) { this.userGear = gear; }
}
聪明的读者会注意到其实还没有解决直接赋值的问题: car1._userGear= 'X' 。这个例子中,使用“穷人访问限制”给私有属性加上下划线作为前缀(_)。但这只是一种约定俗成的做法,它能让大家快速识别出哪些代码访问了被保护的属性。
如果确实想要强制属性私有化,可以使用一个 WeakMap 的实例(见第10章),它是被作用域保护的(如果不用 WeakMap ,私有属性将永远不会跑出作用域,即使是它们引用的实例)。以下代码是通过修改 Car 类,使得当前档位属性真正私有化:
const Car = (function() {
const carProps = new WeakMap();
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
this._userGears = ['P', 'N', 'R', 'D'];
carProps.set(this, { userGear: this._userGears[0] });
}
get userGear() { return carProps.get(this).userGear; }
set userGear(value) {
if(this.userGears.indexOf(value) < 0)
throw new Error('Invalid gear: ${value}');
carProps.get(this).userGear = value;
}
shift(gear) { this.userGear = gear; }
}
return Car;
})();
这里使用即时调用函数表达式将 WeakMap 隐藏在一个闭包内,从而阻止了外界的访问。这个 WeakMap 可以安全地存储任何不想被Car类外部访问的属性。
另一种方式是使用符号代替属性名。它们有一些针对误操作的保护措施,但是类中的符号属性是可以被访问的,所以即使是这种方法也可能会失效。