创建一个对外部世界只读的属性,但我的方法仍然可以设置
Make a property that is read-only to the outside world, but my methods can still set
在JavaScript (ES5+)中,我试图实现以下场景:
- 一个对象(将有许多独立的实例),每个对象都具有只读属性
.size
,可以通过直接属性读取从外部读取,但不能从外部设置。 -
.size
属性必须从原型上的一些方法维护/更新(并且应该留在原型上)。 - 我的API已经被一个规范定义了,所以我不能修改它(我正在为一个已经定义的ES6对象做一个多边形)。
- 我主要是试图防止人们意外地射击自己的脚,而不是真的有防弹的只读性(虽然它越防弹越好),所以我愿意妥协一些侧门进入财产,只要直接设置
obj.size = 3;
是不允许的。
我知道我可以使用在构造函数中声明的私有变量并设置getter来读取它,但是我必须将需要维护该变量的方法移出原型并在构造函数中声明它们(因此它们可以访问包含该变量的闭包)。对于这种特殊情况,我宁愿不把我的方法从原型中去掉,所以我正在寻找其他可能的选择。
可能有什么其他的想法(即使有一些妥协)?
好的,对于一个解决方案,您需要两个部分:
- 一个不可分配的
size
属性,即有writable:true
或没有setter
属性 - 一种改变
size
反映的值的方法,它不是.size = …
,并且是公共的,以便原型方法可以调用它。
@plalx已经给出了第二个"半私有"_size
属性的明显方法,该属性由size
的getter反映。这可能是最简单和最直接的解决方案:
// declare
Object.defineProperty(MyObj.prototype, "size", {
get: function() { return this._size; }
});
// assign
instance._size = …;
另一种方法是使size
属性不可写,但可配置,这样您就必须对Object.defineProperty
使用"长路"(尽管对于辅助函数来说甚至太短了)来设置其中的值:
function MyObj() { // Constructor
// declare
Object.defineProperty(this, "size", {
writable: false, enumerable: true, configurable: true
});
}
// assign
Object.defineProperty(instance, "size", {value:…});
这两个方法绝对足以防止"搬起石头砸脚"的size = …
分配。对于更复杂的方法,我们可以构建一个公共的、特定于实例的(闭包)setter方法,该方法只能从原型模块作用域的方法中调用。
(function() { // module IEFE
// with privileged access to this helper function:
var settable = false;
function setSize(o, v) {
settable = true;
o.size = v;
settable = false;
}
function MyObj() { // Constructor
// declare
var size;
Object.defineProperty(this, "size", {
enumerable: true,
get: function() { return size; },
set: function(v) {
if (!settable) throw new Error("You're not allowed.");
size = v;
}
});
…
}
// assign
setSize(instance, …);
…
}());
只要不泄露对settable
的封闭访问,这确实是故障安全的。还有一种类似的、流行的、更短的方法是使用对象的标识作为访问令牌,如下所示:
// module IEFE with privileged access to this token:
var token = {};
// in the declaration (similar to the setter above)
this._setSize = function(key, v) {
if (key !== token) throw new Error("You're not allowed.");
size = v;
};
// assign
instance._setSize(token, …);
然而,这种模式是不安全的,因为有可能通过将带有赋值的代码应用于带有恶意_setSize
方法的自定义对象来窃取token
。
老实说,我发现为了在JS中强制执行真正的隐私需要做出太多的牺牲(除非你正在定义一个模块),所以我更喜欢只依赖于命名约定,如this._myPrivateVariable
。
这对任何开发人员来说都是一个明确的指示,他们不应该直接访问或修改这个成员,并且不需要牺牲使用原型的好处。
如果你需要你的size
成员作为一个属性被访问,你将别无选择,只能在原型上定义一个getter。
function MyObj() {
this._size = 0;
}
MyObj.prototype = {
constructor: MyObj,
incrementSize: function () {
this._size++;
},
get size() { return this._size; }
};
var o = new MyObj();
o.size; //0
o.size = 10;
o.size; //0
o.incrementSize();
o.size; //1
我看到的另一种方法是使用模块模式来创建privates
对象映射,该映射将保存单个实例私有变量。实例化后,在实例上分配一个只读私钥,然后使用该私钥设置或检索privates
对象的值。
var MyObj = (function () {
var privates = {}, key = 0;
function initPrivateScopeFor(o) {
Object.defineProperty(o, '_privateKey', { value: key++ });
privates[o._privateKey] = {};
}
function MyObj() {
initPrivateScopeFor(this);
privates[this._privateKey].size = 0;
}
MyObj.prototype = {
constructor: MyObj,
incrementSize: function () { privates[this._privateKey].size++; },
get size() { return privates[this._privateKey].size; }
};
return MyObj;
})();
您可能已经注意到,这个模式很有趣,但是上面的实现是有缺陷的,因为私有变量永远不会被垃圾收集,即使没有对持有键的实例对象的引用。
然而,在ES6 WeakMaps中,这个问题消失了,它甚至简化了设计,因为我们可以使用对象实例作为键,而不是像上面那样使用数字。如果实例被垃圾收集,弱映射将不会阻止该对象引用的值的垃圾收集。
我最近一直在这样做:
// File-scope tag to keep the setters private.
class PrivateTag {}
const prv = new PrivateTag();
// Convenience helper to set the size field of a Foo instance.
function setSize(foo, size)
{
Object.getOwnPropertyDiscriptor(foo, 'size').set(size, prv);
}
export default class Foo
{
constructor()
{
let m_size = 0;
Object.defineProperty(
this, 'size',
{
enumerable: true,
get: () => { return m_size; },
set: (newSize, tag = undefined) =>
{
// Ignore non-private calls to the setter.
if (tag instanceof PrivateTag)
{
m_size = newSize;
}
}
});
}
someFunc()
{
// Do some work that changes the size to 1234...
setSize(this, 1234);
}
}
我认为这涵盖了OP的所有要点。我没有做任何性能分析。对于我的用例,正确性更重要。
想法吗?
- 为什么我的Mongoose findAll方法返回500错误.
- 有没有一种方法可以生成Braintree令牌,而不必向我的服务器添加PHP脚本
- 为什么't我的角度控制器'在其某个方法内的作用域中的依赖项
- JQuery$.ajax()发布数据以调用我的控制器中的方法
- 为什么可以't我从hamsters.js内部运行我的方法
- 为什么我的按钮的 onClick() 方法没有用
- 我无法在操作方法中将我的文件传递给 httppostedfilebase
- 使用 CSS 或 javascript/jQuery,我会使用哪种方法来使我的网站的导航栏看起来更 3d-ish
- 在我的上下文中添加对象的动态方法
- 在我的代码中管理大量硬编码数据的最佳方法
- 我可以将.getState()方法作为道具传递给我的组件吗
- 随机数生成器,what'我的方法/统计数据有问题吗?[JS]
- 我的jquery方法没有'不起作用
- 有没有一种方法可以在IE8中解决我的Rails javascript应用程序.js的问题
- 有没有一种方法可以将我的JS函数排队到JSF事件队列中
- 为什么当我指定POST时,即使我已经尝试了类型和方法选项,我的ajax代码仍然会触发GET请求
- 有没有一种方法可以使用gull来修改我的index.thml,以防止CSS缓存
- 为什么不'我的角度测试$scope上定义了方法
- 带有方法的 Javascript 对象返回“没有方法'我的方法名称'”错误
- ajax xmlhttprequest-post方法我的xhr被截断了,有没有数据限制大小