JavaScript中是否需要使用`getter`和`setter`?

34
class Employee {

    constructor(name, age) {
        this._name = name;
        this.age = age;
    }

    doWork() {
        return `${this._name} is working`;
    }

    get name() {
        return this._name.toUpperCase();
    }

    set name(newName){
        if(newName){ 
            this._name = newName;
        }
    }
}

let man = new Employee('A', 10);
console.log(man.name, man.age);
man.name = 'B';
man.age = 20;
console.log(man.name, man.age);

这是我的代码。我为_name成员创建了一个getter和setter。我没有为age创建getter和setter。但是两者都可以像这样更新这两个字段:man.name = 'B';man.age = 20;因此,我很困惑,在JavaScript中是否需要getter和setter?

有时候它们是必要的。如果你的代码不需要它们,就不要使用它们。 - Oriol
为了更好地理解,这个问题在Java中已经有了答案,可以参考这个问题。一些方面是相似的。 - Dan Dascalescu
6个回答

58
在JavaScript中,是否需要使用getter和setter?
“必要性”这个词有点不明确。任何问题都可以在没有getter和setter的情况下解决(通常通过将接口更改为方法而不是直接访问属性),就像任何问题都可以在没有for循环的情况下解决(通过用while循环和手动维护的循环变量替换),但它们是编程语言中有用的功能,因为当它们匹配某个用例时,它们是更清晰和更便利的编程方式。因此,它们是“有用”的和“有帮助的”。
一个getter或setter有时可以非常有用,但只有在需要它们的具体功能时才需要使用它们 - 否则,没有getter或setter的普通属性访问也可以胜任。
当你想要每次请求某个属性时运行一些代码时,getter很有用。在你的示例中,getter始终返回名字的大写版本,而不管名字的大小写如何,同时保留实际的大小写供内部使用。
当您想要每次设置属性时运行一些代码时,setter有用。在你的情况下,它防止了设置虚假名称。如果没有getter / setter,您无法实现这两个功能。
当您不需要getter或setter特殊逻辑时,直接访问属性是一种完全可以胜任的方法。

有时,gettersetter可能会获取并设置一些不公开的属性(因此无法通过正常属性访问完全获得),这可能是因为该属性存储在其他地方(而不是该对象的属性)或者存储在私有位置甚至存储在某些硬件设备中。在这些情况下,gettersetter模拟属性值存在的位置,即使实际上并不存在该属性。因此,它们简化了界面同时允许实际的值从任何地方存储或检索。


1
@tera_789 - 基本函数能做什么?OP可以创建两个常规方法getName()setName(),但那将与使用属性接口后面的设置器和获取器不同。如果您有一个虚拟属性,希望有属性接口访问,则只能使用设置器和获取器。否则,您可以使用普通方法进行获取和设置。因此,这取决于您想要对象具有的接口类型。 - jfriend00
从面向对象编程的角度来看,这个答案似乎过于肤浅。通常情况下,至少在面向对象编程中,直接操作字段是一个不好的想法。 - EmilCataranciuc
1
@Plee - 所以你的观点是每个对象的每个属性都只能通过getter和setter访问?真的吗?为什么不仅在有益/需要时才通过getter/setter访问特定属性?您始终可以稍后添加getter/setter(当实际需要时),因为它不会改变访问属性的方式。同时,保持不需要getter/setter的简单属性可以保留简单属性的性能优化,并使代码更简洁,这两点都是好的。 - jfriend00
1
@Plee - 另外,将Javascript与Java进行比较并不会带领你走向正确的方向。它们是不同的语言,具有不同的设计工具、影响性能的不同因素、不同的目标等等... 当我从C++转学Javascript时,由于我的C++背景,我在Javascript中做了各种各样的事情,最终后悔不已。我们需要学习新语言的本质以及如何在该语言中编写最佳代码,而不是试图从另一种语言中获取设计意见并假设它们适用于Javascript。 - jfriend00
1
@Plee - 我不确定你在问什么。我认为这超出了我们在评论中可以适当完成的范围。如果您想进一步追究此事,我建议您提出自己的问题,针对您在最后一条评论中所说的内容。您可以在评论中放置一个问题链接,我会查看它。 - jfriend00
显示剩余3条评论

10

必需吗?不是。

是否有情况最好用getter/setter表达?是的。考虑“计算属性”:

//declare an object
rectangle = {
   x: 10,
   y: 20,
   get area() { return this.x * this.y }  //won't occupy storage
}

//usage
log('the area is', rectangle.area)        //=> 'the area is 200'.
                                          //   cleaner syntax (no parentheses)

考虑 API 设计人员的需求——他们很可能对用户对其 API 的感知非常敏感。每一点(在这种情况下,括号)都对整理界面有影响。


2
在你的例子中,这个方法不就和 getter 一样吗,只是需要加上括号才能调用吗?area: () => { return this.x * this.y } - jsejcksn
完全相同 - Gal Margalit

6

是的,它们非常必要。但并非适用于每个属性。

以下是两个需要设置器的原因:

1. 您需要验证传入值。

假设您有一个值,它必须是介于0和100之间的整数。您的设置器可以验证传入值是否为正确类型且在正确范围内:

class Range {
  set value(newVal) {
    let val = Number(newVal);
    
    if( newVal == null || typeof newVal === 'string' || !Number.isInteger(val) || val < 0 || val > 100 ) {
      const err = `'value' must be an integer from 0 to 100 and not ${newVal}`;
      console.error(err);
      //throw new TypeError(err);
    }
    
    // save newVal
  }
}

const x = new Range();
x.value = 200;
x.value = 10;
x.value = "10";
x.value = new Number(22);

2. 每当一个值发生改变,您需要执行某些操作

class ValOutput {
  constructor(el) {
    this._el = el;
  }

  set value(newVal) {
    this._el.innerHTML = `The value is ${newVal}`;
  }
}

const output = document.getElementById('output');
const x = new ValOutput(output);
x.value = 100;
setTimeout(() => {
  x.value="after timeout";
}, 2000);
<div id="output"></div>

以下是两个需要使用getter的原因:

1. 值是计算出来的

class Rect {
  constructor(l = 0,t = 0,r = 0,b = 0) {
    this.left = l;
    this.top = t;
    this.right = r;
    this.bottom = b;
  }
  
  get height() {
    return this.bottom - this.top;
  }

  get width() {
    return this.right - this.left;
  }
}

let a = new Rect(0, 10, 33, 55);
console.log(a.width, a.height);

 a = new Rect(35, 50, 200, 200);
console.log(a.width, a.height);

2. 你正在代理另一个对象的值

class OtherThing {
  constructor(a) {
    this.a = a;
  }
}

class MyObj {
  constructor(oVal = 0) {
    this._otherThing = new OtherThing(oVal);
  }
  
  get a() {
    return this._otherThing.a;
  }
}

const x = new MyObj();
console.log(x.a);

const y = new MyObj('tacos');
console.log(y.a);

隐藏私有变量

getterssetter是隐藏私有数据的好方法。尽管我知道ES规范正在引入私有变量,但以下示例可以在规范标准化之前使用。

const privateVars = new WeakMap();

class MyObj {
  constructor(inVal) {
    const pVars = {
      age: inVal,
      dog: ''
    }
    
    privateVars.set(this, pVars);
  }
  
  get dog() {
    return privateVars.get(this).dog;
  }
  set dog(newVal) {
    privateVars.get(this).dog = newVal;
  }
}

const x = new MyObj(10);
const y = new MyObj(20);


x.dog = "woof";
y.dog = "bark";

console.log(x.dog);
console.log(y.dog);

你需要gettersetter吗?不需要。但它们是必要的吗?是的。


3

Getter和Setter特性被实现的主要原因之一是为了弥补一个功能差距,这需要人们对js解释器/浏览器进行黑客攻击来实现一个浏览器可以执行的功能:

"最初的回答"

element.innerHTML = "<div> some HTML string </dif>";

现在,innerHTML 看起来像一个属性,但实际上它的行为更像是一个函数。它实际上是一个 HTML 编译器。
您可以通过尝试执行以下操作来查看这一点:
element.innerHTML += "<table>";
element.innerHTML += "<tr><td>test</td></tr>"; // does not work

这是因为第一次调用innerHTML会将HTML编译为: "最初的回答"
<table></table>

第二行会失败,因为在表格之外的<tr>是无效的HTML。
使用getter和setter,您最终可以在纯javascript中实现这样一个功能 - 使属性访问触发函数调用。
当然,innerHTML只是getter和setter的一个示例,而且是一个非常糟糕的API示例。您可以根据自己的创意实际使用getter和setter。
现在,对于我的个人意见(仅代表我个人的意见,请根据自己的需求决定是否采纳):我认为innerHTML提供了一个很好的例子,说明getter和setter通常不应该被使用。对于<table>的混淆是打破用户期望的一个很好的例子。如果innerHTML被实现为element.appendHTML(),它的行为将会更加令人满意。作为一个程序员,如果属性访问执行了我意料之外的操作,我会感到非常惊讶。
话虽如此,我很高兴getter和setter存在,使得语言库特性可以自我实现,而不需要诉诸于C/C++的黑科技手段。

-1

我们使用类的原因是它包含了一个实体的所有属性。但是你也可以通过创建一个对象并在需要时添加属性来完成相同的任务。

当代码变得越来越大时,你可能无法调试出代码失败的原因。如果你遵循结构,你将能够轻松地进行调试。因为每个实体都会在单独的文件中,并且逻辑是分离的。

但是JavaScript是一种面向对象的编程语言。我们使用类而不是对象的原因与获取器和设置器的原因相同。这不是说你可以选择是否使用它们。通过获取器和设置器来设置和获取所有属性是一种良好的实践。

在某个时间点上,你可能需要在设置属性时添加一些条件,比如如果一个人的年龄大于100岁,你就不应该设置属性。在这种情况下,你需要更改基类和子类中的代码。这将是我们生活中最困难的部分(代码重构)。所以避免这些问题,让你的变量更安全,从我的角度来看,我们应该使用获取器和设置器。对于这个长答案,我表示抱歉。


-2

使用gettersetter方法取决于类中的属性,如果您想使一个属性只读,则可以使用getter方法,如下所示:

function Person(name, age){
  var _name = name;
  this.age = age;
  
  this.getName = function(){
    return _name;
  }
}

var teacher = new Person('Sara', 24);

//now you can just get the name of teacher 
alert(teacher.getName());
  


你可以通过添加一个名为setName的新方法来完成,这只是一种对_name进行不可变操作的方法,仅适用于es3风格代码。 - Heartbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接