如何正确实现 getter/setter 功能,无论是属性的“get”和“set”,还是通过两个方法和属性的“get”?

问题描述 投票:0回答:3

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 上执行逻辑。

希望你明白我的想法。谢谢

javascript class oop getter-setter encapsulation
3个回答
4
投票

您可以使用私有属性

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)


0
投票

根据我的上述评论...

“1/2 ...无论如何,存在一个设计缺陷,一个人要么使用

get
/
set
解决方案来公开作为单个公共财产(此处
isOn
),要么使用两个不同名称的解决方案公共方法(此处为
turnOn
/
turnOff
)。并且由于这两种解决方案都至少实现了
set
功能,因此都需要通过以某种方式更改/控制私有值(无论是私有实例字段还是局部变量,甚至可能是基于 WeakMap 的解决方案)。”

“@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>


-2
投票
正如

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

 类)和一组数据。代理允许控制访问并(根据您的需要)在访问时造成副作用。

© www.soinside.com 2019 - 2024. All rights reserved.