setter js 无法在 javascript 中设置相同的变量 this 。 setter 和 getter 需要具有相同的名称
我有一个逻辑需要反应性地运行一些函数
如果
this.variableName
在课堂内外发生变化
问题出在这个逻辑上,getter 不起作用,我得到了
undefined
。
不仅如此,还有最大堆栈错误。
因为就像:在 setter 中设置
this
,重新设置 this
,再次调用 setter,设置 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)
根据我的上述评论...
“1/2 ...无论如何,存在一个设计缺陷,一个人要么使用
/get
解决方案来公开作为单个公共财产(此处set
),要么使用两个不同名称的解决方案公共方法(此处为isOn
/turnOn
)。并且由于这两种解决方案都至少实现了turnOff
功能,因此都需要通过以某种方式更改/控制私有值(无论是私有实例字段还是局部变量,甚至可能是基于 WeakMap 的解决方案)。”set
“@stackdeveloper ... 2/2 ... OP的代码到目前为止试图实现的目标,以及接受的答案提供的内容,都是不必要的。已经有两个明确的设置器,例如
/turnOn
,其中两者都确实改变了公共turnOff
属性,这是完全错误的,至少它很容易出错。更糟糕的是,isOn
已经通过isOn
/get
实现了。要么选择后者,要么选择前者,并实现任一变体正确。不要将两者混合;这没有任何意义。”set
考虑到上述内容,有以下两个代码示例。
第 1st 重点关注get
/
set
实现单个公共 isOn
属性,而 2nd 选择显式方法 的方法,如
turnOn
/
turnOff
与 单一 get
结合用于 唯一公共 isOn
财产。 每种方法都提供
两种实现,一种选择私有实例字段,另一种基于封装的局部变量。
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 logSwitchStateWithBoundDefault(fieldtype, instance) {
console.log({ [`${ fieldtype }IsOn`]: instance.isOn });
}
const onOffStateRadio =
new SwitchStateWithGetSetAndPrivateFields(
true, logSwitchStateWithBoundDefault.bind(null, 'radio')
);
const onOffStateText =
new SwitchStateWithGetSetAndLocalValues(
false, logSwitchStateWithBoundDefault.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;
});
logSwitchStateWithBoundDefault('radio', onOffStateRadio);
logSwitchStateWithBoundDefault('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 logSwitchStateWithBoundDefault(fieldtype, instance) {
console.log({ [`${ fieldtype }IsOn`]: instance.isOn });
}
const onOffStateRadio =
new SwitchStateWithExplicitMethodsSingleGetAndPrivateFields(
true, logSwitchStateWithBoundDefault.bind(null, 'radio')
);
const onOffStateText =
new SwitchStateWithExplicitMethodsSingleGetAndLocalValues(
false, logSwitchStateWithBoundDefault.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']();
});
logSwitchStateWithBoundDefault('radio', onOffStateRadio);
logSwitchStateWithBoundDefault('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>
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("Direct turnOff()");
indirectObject.turnOff();
这里,我们有两个
MyClass
类的对象,它们作用于相同的数据,其中一个通过代理。代理的
MyClass
的方法会产生副作用。这允许一种行为实现(一个
MyClass
类)和一组数据。代理允许控制访问并(根据您的需要)在访问时造成副作用。