提问人: 提问时间:10/16/2023 最后编辑:Peter Seliger 更新时间:10/17/2023 访问量:122
如何正确实现 getter/setter 功能,无论是针对属性的 'get' 和 'set',还是针对两个方法和属性的 'get'?
How to properly implement getter/setter functionality, either for both a property's `get` and `set`, or for two methods and a property's `get`?
问:
我有一个逻辑,如果类内部或外部更改了 a,则需要被动地运行某些函数this.variableName
问题出在这个逻辑上,getter 不起作用,我得到.undefined
不仅如此,还有最大堆栈误差。
因为就像:设置内部的 setter,重新设置 ,重新调用另一个时间的 setter,设置 this,依此类推......(但这不是我的意思)this
this
示例版本,没有参数,因此,只是错误的最小示例。
class MyClass {
constructor() {
this.isOn = false;
}
set isOn(value) {
this.isOn = value;
// another functions that do stuff. you can put here a bunch of console.logs if you want just for the demo
}
// the idea is another, but i will show you the simpliest method, I could imagine, just to make you get the idea
turnOn() {
this.isOn = true;
}
turnOff() {
this.isOn = false;
}
// using this approach,
// I don't need to copy paste the same functions in this 2 methods,
// so I just need to care about the var,
// without using a imperative approach,
// but a declarative approach
}
我知道我们可以使用 _isOn,但这实际上与我们想要的相反,因为我希望 setter 与 getter 同名,
并且仍然在 setter 上执行逻辑。
希望你明白我的想法。谢谢
答:
您可以使用私有属性
class MyClass {
#isOn = false;
set isOn(value) {
this.#isOn = value;
}
get isOn() {
return this.#isOn
}
turnOn() {
this.isOn = true;
}
turnOff() {
this.isOn = false;
}
}
const x = new MyClass
x.isOn = true
console.log(x.isOn)
// This will throw an error:
// console.log(x.#isOn)
评论
turnOff
turnOff
this.isOn
turnOn/Off
正如 Konrad 所评论的,你不能用一个标识符引用两个不同的对象。相反,在从内部引用时,可以使用内部成员。
或者,如果我们将数据与类分开,我们可以实例化两个对象:一个是更改数据(例如通过代理)会产生副作用,另一个是直接更改。
例:
class MyClass {
#data = null;
constructor(data) {
this.#data = data;
}
set isOn(value) {
this.#data.isOn = value;
}
get isOn() {
return this.#data.isOn;
}
turnOn() {
this.isOn = true;
}
turnOff() {
this.isOn = false;
}
}
const data = {
isOn: false
};
const dataProxy = new Proxy(data, {
get(target, prop, receiver) {
if (prop === "isOn") {
console.log("get side-effect");
}
return Reflect.get(...arguments);
},
set(target, prop, value, receiver) {
if (prop === "isOn") {
console.log("set side-effect");
}
return Reflect.set(...arguments);
}
});
const directObject = new MyClass(data);
const indirectObject = new MyClass(dataProxy);
console.log("Direct isOn:", directObject.isOn);
console.log("Direct turnOn()");
directObject.turnOn();
console.log("Indirect isOn:", indirectObject.isOn);
console.log("Indirect turnOff()");
indirectObject.turnOff();
在这里,我们有两个类对象,它们作用于相同的数据,其中一个通过代理。被代理的方法会引起副作用。MyClass
MyClass
这允许行为的一个实现(一个类)和一组数据。代理允许控制访问并(如您所愿)在访问时引起副作用。MyClass
从我上面的评论......
无论如何,存在一个设计缺陷,要么使用 / 解决方案作为单个公共属性公开的内容(此处),要么使用两个不同名称的公共方法(此处 /)。由于这两种解决方案都至少实现了功能,因此两者都需要通过更改/控制某种私有值(无论是私有实例字段还是局部变量,甚至是基于 WeakMap 的解决方案)来实现这一点。 到目前为止,OP 的代码试图实现的目标,以及公认的答案提供的内容,都不是必需的。已经有两个明确的 setter,比如 /,它们都确实改变了公共属性是完全错误的,至少它容易出错。更糟糕的是,已经通过 / 实现了。要么选择后者,要么选择前者,并且正确实现任何一种变体。不要将两者混合使用;这没有任何意义。
get
set
isOn
turnOn
turnOff
set
turnOn
turnOff
isOn
isOn
get
set
考虑到上面所说的,下面有两个代码示例。
第一个侧重于单个公共 isOn
属性的 get/set 实现,而第二个则选择显式方法(如 turnOn
/turnOff
)的方法,以及唯一公共 isOn
属性的单个
get
。
每种方法都提供两种实现,一种选择私有实例字段,另一种基于封装的局部变量。
class SwitchStateWithGetSetAndPrivateFields {
#isOn;
#handleSwitchChange;
constructor(switchDefault = false, onSwitchChange) {
this.#isOn = Boolean(switchDefault);
this.#handleSwitchChange = onSwitchChange;
}
set isOn(value) {
if ('boolean' === typeof value) {
if (this.#isOn !== value) {
this.#isOn = value;
this.#handleSwitchChange(this);
}
} else {
throw new TypeError('The assigned value needs to be a boolean type.');
}
}
get isOn() {
return this.#isOn;
}
}
class SwitchStateWithGetSetAndLocalValues {
constructor(switchDefault = false, handleSwitchChange) {
let isOn = Boolean(switchDefault);
Object.defineProperty(this, 'isOn', {
set(value) {
if ('boolean' === typeof value) {
if (isOn !== value) {
isOn = value;
handleSwitchChange(this);
}
} else {
throw new TypeError('The assigned value needs to be a boolean type.');
}
},
get() {
return isOn;
},
enumerable: true,
});
}
}
function logSwitchStateOfBoundFieldtype(fieldtype, instance) {
console.log({ [`${ fieldtype }IsOn`]: instance.isOn });
}
const onOffStateRadio =
new SwitchStateWithGetSetAndPrivateFields(
true, logSwitchStateOfBoundFieldtype.bind(null, 'radio')
);
const onOffStateText =
new SwitchStateWithGetSetAndLocalValues(
false, logSwitchStateOfBoundFieldtype.bind(null, 'text')
);
document
.querySelector('form')
.addEventListener('submit', evt => {
evt.preventDefault();
const booleanMap = {
'true': true,
'false': false,
'': false,
};
const formElements = evt.currentTarget.elements;
const radioValue = booleanMap[formElements['is_on'].value];
const textValue = booleanMap[formElements['is_on_text'].value.trim().toLowerCase()];
onOffStateRadio.isOn = radioValue;
onOffStateText.isOn = textValue;
});
logSwitchStateOfBoundFieldtype('radio', onOffStateRadio);
logSwitchStateOfBoundFieldtype('text', onOffStateText);
body { margin: 0; }
form { width: 40%; }
fieldset { margin: 5px 0; }
form > label { display: block; margin: 5px 0 10px 14px; }
form > label > input, form > button { float: right; }
.as-console-wrapper { left: auto!important; min-height: 100%; width: 59%; }
<form>
<fieldset>
<legend>isOn:</legend>
<label>
<span>true</span>
<input type="radio" name="is_on" value="true" checked/>
</label>
<label>
<span>false</span>
<input type="radio" name="is_on" value="false"/>
</label>
</fieldset>
<label>
<span>isOn:</span>
<input type="text" name="is_on_text" placeholder="whatever"/>
</label>
<button type="submit">apply values</button>
</form>
class SwitchStateWithExplicitMethodsSingleGetAndPrivateFields {
#isOn;
#handleSwitchChange;
constructor(switchDefault = false, onSwitchChange) {
this.#isOn = Boolean(switchDefault);
this.#handleSwitchChange = onSwitchChange;
}
get isOn() {
return this.#isOn;
}
turnOn() {
if (this.#isOn === false) {
this.#isOn = true;
this.#handleSwitchChange(this);
}
}
turnOff() {
if (this.#isOn === true) {
this.#isOn = false;
this.#handleSwitchChange(this);
}
}
}
class SwitchStateWithExplicitMethodsSingleGetAndLocalValues {
constructor(switchDefault = false, handleSwitchChange) {
let isOn = Boolean(switchDefault);
Object.defineProperty(this, 'isOn', {
get() {
return isOn;
},
enumerable: true,
});
this.turnOn = function turnOn() {
if (isOn === false) {
isOn = true;
handleSwitchChange(this);
}
}
this.turnOff = function turnOff() {
if (isOn === true) {
isOn = false;
handleSwitchChange(this);
}
}
}
}
function logSwitchStateOfBoundFieldtype(fieldtype, instance) {
console.log({ [`${ fieldtype }IsOn`]: instance.isOn });
}
const onOffStateRadio =
new SwitchStateWithExplicitMethodsSingleGetAndPrivateFields(
true, logSwitchStateOfBoundFieldtype.bind(null, 'radio')
);
const onOffStateText =
new SwitchStateWithExplicitMethodsSingleGetAndLocalValues(
false, logSwitchStateOfBoundFieldtype.bind(null, 'text')
);
document
.querySelector('form')
.addEventListener('submit', evt => {
evt.preventDefault();
const booleanMap = {
'true': true,
'false': false,
'': false,
};
const formElements = evt.currentTarget.elements;
const radioValue = booleanMap[formElements['is_on'].value];
const textValue = booleanMap[formElements['is_on_text'].value.trim().toLowerCase()];
onOffStateRadio[radioValue && 'turnOn' || 'turnOff']();
onOffStateText[textValue && 'turnOn' || 'turnOff']();
});
logSwitchStateOfBoundFieldtype('radio', onOffStateRadio);
logSwitchStateOfBoundFieldtype('text', onOffStateText);
body { margin: 0; }
form { width: 40%; }
fieldset { margin: 5px 0; }
form > label { display: block; margin: 5px 0 10px 14px; }
form > label > input, form > button { float: right; }
.as-console-wrapper { left: auto!important; min-height: 100%; width: 59%; }
<form>
<fieldset>
<legend>isOn:</legend>
<label>
<span>true</span>
<input type="radio" name="is_on" value="true" checked/>
</label>
<label>
<span>false</span>
<input type="radio" name="is_on" value="false"/>
</label>
</fieldset>
<label>
<span>isOn:</span>
<input type="text" name="is_on_text" placeholder="whatever"/>
</label>
<button type="submit">apply values</button>
</form>
评论
this.isOn =
this.#isOn
_isOn
isOn
isOn
turnOn
turnOff
set
WeakMap