为什么在这两个示例中 getter 的工作方式不同?

huangapple go评论64阅读模式
英文:

Why do getters work differently in this two examples?

问题

为什么当我们有这个函数时:

function Car() {
  const fuel = 50
  return {
    fuel,
  }
}

const car = Car()
console.log(car.fuel) // 50
car.fuel = 3000
console.log(car.fuel) // 3000

如果我们添加一个 getter,fuel 属性就无法被修改:

function Car() {
  const fuel = 50
  return {
    get fuel() {
      return fuel
    },
  }
}

const car = Car()
car.fuel = 3000
console.log(car.fuel) // 50

但是,如果我尝试在一个独立的对象上运行它,它不起作用相同的方式:

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fname() {
    return this._firstName;
  }
}

console.log(person.fname); // 打印 'John'
person._firstName = 'Juan';
console.log(person.fname); // 打印 'Juan'

背景:我试图弄清楚为什么存在setter,我无法理解它的用处。我明白为什么setter有其存在的理由。所以我找到了这段代码片段,解释了其中一个优点是保护属性不受更改的影响。参考链接在这里

英文:

Why when we have this

function Car() {
  const fuel = 50
  return {
    fuel,
  }
}

const car = Car()
console.log(car.fuel) // 50
car.fuel = 3000
console.log(car.fuel) // 3000

And if we add a getter, the fuel property cannot be muted:

function Car() {
  const fuel = 50
  return {
    get fuel() {
      return fuel
    },
  }
}

const car = Car()
car.fuel = 3000
console.log(car.fuel) // 50

But then, if I try it on an isolated object it doesn't work the same way:

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // prints 'John'
person._firstName = 'Juan';
console.log(person.fname); //prints 'Juan'

Context: I was trying to figure out why setters exist, I couldn't wrap around the idea of it usefulness. I get why setters have a place. So I found this code snippet explaining that one of the advantages was to keep properties safe from changes. Reference here.

答案1

得分: 6

当你在做

```javascript
function Car() {
  const fuel = 50
  return {
    fuel,
  }
}

const car = Car()

Car() 只是返回 { fuel: 50 },一个普通对象。

所以当你设置 car.fuel = 3000 时,它只会变成 { fuel: 3000 }

让我们看看第二种情况。

function Car() {
  const fuel = 50
  return {
    get fuel() {
      return fuel
    },
  }
}

const car = Car()

让我为了清晰起见重新命名 fuelfuel()

function Car() {
  const a = 50
  return {
    get b() {
      return a
    },
  }
}

const car = Car()

在这里,当你执行 car = Car() 时,你可以理解为 car = { get b(): {return a} }。当你定义这样的 getter 函数时,你不能改变它。

所以当你执行 car.b = 3000 时,JS 会说 "哦,你试图改变 b 的值,但那是一个 getter 函数,你不能改变它。" 不过,JS 不会抛出错误。

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // 打印 'John'
person._firstName = 'Juan';
console.log(person.fname); // 打印 'Juan'

这个例子相当明显,person._firstName 就像对象中的任何其他属性一样,所以你可以随意更改它。但是,类似于第二种情况,如果你尝试这样做:

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // 打印 'John'
person.fname = 'Juan';
console.log(person.fname); // 打印 'John'

它不会改变 fname


<details>
<summary>英文:</summary>

When you&#39;re doing

```javascript
function Car() {
  const fuel = 50
  return {
    fuel,
  }
}

const car = Car()

Car() is just returning { fuel: 50 }, a plain object.

So when you set car.fuel = 3000, it just changes to { fuel: 3000 }.

Let's take a look at the second case.

function Car() {
  const fuel = 50
  return {
    get fuel() {
      return fuel
    },
  }
}

const car = Car()

Let me rename fuel and fuel() for clarity:

function Car() {
  const a = 50
  return {
    get b() {
      return a
    },
  }
}

const car = Car()

Here, when you do car = Car(), you can understand it as car = { get b(): {return a} }. When you define a getter function like this, you cannot change it.

So when you do car.b = 3000, JS is saying "Oh, you're trying to change the value of b, but that is a getter function, you're not allowed to change it." JS being JS, it doesn't throw you an error though.

const person = {
  _firstName: &#39;John&#39;,
  _lastName: &#39;Doe&#39;,
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // prints &#39;John&#39;
person._firstName = &#39;Juan&#39;;
console.log(person.fname); //prints &#39;Juan&#39;

This one is pretty self evident, person._firstName is just plain old any other property in an object, so you can change it however you want. But, similarly to the second case, if you try this:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const person = {
  _firstName: &#39;John&#39;,
  _lastName: &#39;Doe&#39;,
  get fname() {
    return this._firstName;
  }
}
 
console.log(person.fname); // prints &#39;John&#39;
person.fname = &#39;Juan&#39;;
console.log(person.fname); //prints &#39;John&#39;

<!-- end snippet -->

It will not change fname.

答案2

得分: -2

我将回答你更深层次的问题。但首先,这是你简单问题的答案:

燃料属性无法被改变

这是因为它是一个 const 常量。

第一个示例可以被改变的原因是因为它不是一个 setter。它只是一个属性。我们可以重新编写第一个示例,以更清楚地说明发生了什么:

function Car() {
  const fuel = 50;

  let car_properties = new Object();

  car_properties.fuel = fuel;

  return car_properties;
}

现在很明显,第一个示例中返回的 fuelconst fuel 没有任何关系,除了它的值是由它初始化的。

在最后一个示例中,原因是你没有将内部变量指定为 const

为什么存在 Setter

这在 JavaScript 中是一个历史性的漏洞,需要修补。

在浏览器中,有几个地方 DOM API 公开了看起来像属性但行为怪异的东西。首先是 .innerHTML

.innerHTML 属性看起来像一个字符串,但实际上不是。它是一个 HTML 编译器。基本上,它是一个函数,而不是一个字符串。但它的 API 就像一个普通的字符串(就像设置器和获取器的行为一样)。例如,如果它是一个字符串,以下代码将不会按照你的预期工作:

let div = document.getElementById('mydiv');

div.innerHTML = '<table>';
div.innerHTML += '<tr><td>Hello</td><td>World</td><tr>';
div.innerHTML += '<tr><td>This is</td><td>a test</td><tr>';
div.innerHTML += '</table>';

上面的代码不会创建一个具有两行两列的表格,而是创建一个表格(第一个 innerHTML 行),这意味着它会将 &lt;table&gt;&lt;/table&gt; 插入文档中。它会自动关闭表格,因为它假设你作为一个人仅仅是忘记关闭它(怪癖模式)。

然后它将在表格之后插入文本 "HelloWorldThis isa test"。&lt;tr&gt;&lt;td&gt; 被忽略,因为它们在表格外部是无效的 HTML。

最后的 innerHTML 行将被忽略,因为单独的 &lt;/table&gt; 是无效的 HTML。

所以基本上 .innerHTML 不是一个字符串。它是一个函数,是一个 HTML 编译器。当你给它一个值时,它会编译你的 HTML 并执行所有必要的验证,丢弃无效的字符串部分。当你读取它时,它会解析活动 DOM 并给你返回字符串表示。这基本上是一个设置器和获取器。

这里还有另一个例子,即 cookie API。

你可以通过读取 document.cookie 的值来读取 cookie。它会返回页面上(JavaScript 有访问权限的页面)所有 cookie 的人类可读形式,用 "; "(注意分号后面的空格 - 值应该是人类可读的,而不仅仅是机器可读的)分隔开。

要设置一个 cookie,你需要写入 document.cookie。但 document.cookie 不是一个字符串。它是一个管理 cookie 的函数,具有模仿字符串的 API(基本上是一个 getter/setter)。你可以尝试这样做:

document.cookie = 'a=hello';
document.cookie = 'b=world';

console.log(document.cookie); // 输出 'a=hello; b=world'

从历史上看,JavaScript 中存在这些奇怪的东西,是不可能在 JavaScript 中实现的。浏览器实现这些功能的方式是编写 C/C++ 代码,直接操作解释器中的 JavaScript 变量的结构。

为了填补这一语言的不足,引入了获取器和设置器,现在你可以实现自己的 .innerHTMLdocument.cookie API。不要这么做 - 正如你在上面的示例中看到的,这对程序员来说非常令人困惑,尤其是初学者/初级程序员。对于你自己的代码(或第三方代码),这也会让高级开发人员感到困惑,因为没有人期望变量的行为像其他东西一样。

但这确实是语言能力的一个缺口,需要被弥补。

英文:

I'm going to answer your deeper question. But first here's the answer to your simple question:

> the fuel property cannot be muted

It's because it's a const.

The reason the first example can be mutated is because it's not a setter. It's just a property. We can rewrite the first example differently to make it more clear what's going on:

function Car() {
  const fuel = 50;

  let car_properties = new Object();

  car_properties.fuel = fuel;

  return car_properties;
}

It's now obvious that the fuel returned in the first example has nothing to do with the const fuel except that its value is initialised by it.

In the final example it's because you haven't specified the internal variables as const.

Why Setters Exist

It's a bit of a historical hole in javascript that needed to be patched.

In the browser, there are several places where the DOM API expose what look like properties but behave in weird ways. First is .innerHTML.

The .innerHTML property looks like a string but it is not. It is an HTML compiler. Basically, it is a function, not a string. But it's API is that of a plain string (exactly how setters and getters behave). For example, the following will not work how you'd expect if it's a string:

let div = document.getElementById(&#39;mydiv&#39;);

div.innerHTML = &#39;&lt;table&gt;&#39;;
div.innerHTML += &#39;&lt;tr&gt;&lt;td&gt;Hello&lt;/td&gt;&lt;td&gt;World&lt;/td&gt;&lt;tr&gt;&#39;;
div.innerHTML += &#39;&lt;tr&gt;&lt;td&gt;This is&lt;/td&gt;&lt;td&gt;a test&lt;/td&gt;&lt;tr&gt;&#39;;
div.innerHTML += &#39;&lt;/table&gt;&#39;;

Instead of a table that has two rows with two columns each, the code above will create an empty table (the first innerHTML line) which means it inserts &lt;table&gt;&lt;/table&gt; in the document. It auto-closes the table because it assumes you as a human have merely forgotten to close it (quirks mode).

Then it will insert the text "HelloWorldThis isa test" after the table. The &lt;tr&gt; and &lt;td&gt; are ignored because they're invalid HTML outside of tables.

The last innerHTML line will be ignored because &lt;/table&gt; on its own is invalid HTML.

So basically .innerHTML is not a string. It is a function that is an HTML compiler. When you give it a value it will compile your HTML and perform all the necessary validations and discard invalid parts of your string. When you read it it will parse the live DOM and give you back the string representation. This is basically a setter and getter.

Here's another example. The cookie API.

You can read cookies by reading the value of document.cookie. It will return a list of all the cookies on the page (that javascript has access to) in human readable form separated by "; " (note the space after the ; - the value is supposed to be human readable, not simply machine readable).

To set a cookie you write to document.cookie. But document.cookie is not a string. It is a function that manages cookies with an API that imitates a string (basically a getter/setter). You can try this:

document.cookie = &#39;a=hello&#39;;
document.cookie = &#39;b=world&#39;;

console.log(document.cookie); // prints out &#39;a=hello; b=world&#39;

Historically we've had these weird things in javascript that is impossible to implement in javascript. The way browsers implement these things is by writing C/C++ code that directly manipulate the structure of javascript variables in the interpreter.

To fix this gap in the language getters and setters were introduced so now you can implement your own .innerHTML or document.cookie API. DON'T DO IT - as you've seen in the examples above this is very confusing to programmers especially beginner/junior programmers. With your own code (or 3rd party code) it's also confusing to senior developers because nobody expects variables to behave like something else.

But it was a gap in the language's capabilities that needed to be closed.

huangapple
  • 本文由 发表于 2023年7月14日 08:16:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76683981.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定