代入したのに、代入されない・・・？ちょっと不思議なオブジェクト【#ゆめみからの挑戦状】

# 先日、Twitter上でこんなクイズを出題しました。

JavaScriptのクイズです。

https://twitter.com/yumemiinc/status/1580440205441441793?s=20&t=LhralO8Hz-9Fl2zKTUnRGw

# クイズの内容
`"たかし"`を代入したのに`"やめ太郎"`と出力されました。なぜ？

コードの`/* ここに解答を書いてください */`の部分を埋めてください。

### コード
```diff_javascript:JavaScript
const user = /* ここに解答を書いてください */

user.name = "たかし"

console.log(user.name)
// -> "やめ太郎"
```

# この記事の内容
この記事では、上記のクイズの正解発表と解説をして行きます。
また、**別解**や**面白解答**もいくつかご紹介したいと思います。

# 正解発表

想定していた解答は以下です。

```diff_javascript:JavaScript
const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })
```

`{ name: "" }`というオブジェクトを元に、
`Proxy`オブジェクトを生成するやり方を想定していました。

# 解説
`Proxy`オブジェクトを生成することによって、
元のオブジェクトへの操作を**傍受**したり**再定義**できます。

### 例

例えば以下のように`user`という名前の`Proxy`オブジェクトを生成すると、
どのプロパティにアクセスしても`"やめ太郎"`を取得するようになります。

```diff_javascript:JavaScript
// Proxyオブジェクトを生成
const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })

console.log(user.name)
// -> "やめ太郎"

console.log(user.a)
// -> "やめ太郎"

console.log(user.b)
// -> "やめ太郎"
```

↑存在しないプロパティにアクセスしても`"やめ太郎"`を取得します。

また、`name`プロパティを上書きしたとしても、結局`"やめ太郎"`が出力されます。

```diff_javascript:JavaScript
  // Proxyオブジェクトを生成
  const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })

+ user.name = "たかし"

  console.log(user.name)
  // -> "やめ太郎"
```

「あれ、`name`プロパティに代入したのに、代入されていない？」と感じるかもしれませんが、**代入はされています。**
元のオブジェクトの`name`は`"たかし"`に書き換わっています。

ただ、`Proxy`オブジェクトを通して`name`プロパティを取得しているため
`() => "やめ太郎"`という関数が作動し、`"やめ太郎"`という値が取得されます。

# ちゃんと代入されていることが分かる例

`user.name`に`"たかし"`を代入した後で、元のオブジェクトに直接アクセスすると`"たかし"`が出力されます。
ちゃんと`"たかし"`が代入できていることが分かります。

```diff_javascript:JavaScript
// 元のオブジェクト
const obj = { name: "" }

// Proxyオブジェクトを生成
const user = new Proxy(obj, { get: () => "やめ太郎" })

user.name = "たかし"

console.log(obj.name)
// -> "たかし"
```

# 代入をハックすることもできる

先ほどの例では、代入できていないように見えて、ちゃんと代入ができていました。
でも、代入処理をハックすることもできます。


```diff_javascript:JavaScript
const user = new Proxy(obj, { set: (target) => { target.name = "やめ太郎"; return true; } })
```

↑このように、`new Proxy()`するときの第二引数に`set`プロパティを持たせます。

こうすることで、どのプロパティに何を代入しようとしても、
`name`プロパティに`"やめ太郎"`が代入されるようになります。

```diff_javascript:JavaScript
// 元のオブジェクト
const obj = { name: "" }

// Proxyオブジェクトを生成
const user = new Proxy(obj, { set: (target) => { target.name = "やめ太郎"; return true; } })

user.name = "たかし"

console.log(user.name)
// -> "やめ太郎"

console.log(obj.name)
// -> "やめ太郎"
```

↑元のオブジェクト（`obj`）の`name`にアクセスしても`"やめ太郎"`が出力されます。
代入処理を再定義できていることが分かります。

# Vue.jsでも使われている

Vue3では、`Proxy`を利用して

- オブジェクトのプロパティが上書きされたら、DOMに反映させる

という仕組みを実現しているようです。

# 別解

ここからは、別解をご紹介していきます。

# 別解①

https://twitter.com/TsukusuLina/status/1580775327185350657?s=20&t=PH4jU9GiZyx0_znA8EaxjQ

### コード
```diff_javascript:JavaScript
const user = { set name(s) {}, get name() {return "やめ太郎"} }
```

### **セッター**＆**ゲッター**
**セッター**と**ゲッター**を使うパターンです。
そういえば、**セッター**と**ゲッター**ってありましたね・・・！

# 別解②

https://twitter.com/mattn_jp/status/1580771750593249280?s=20&t=PH4jU9GiZyx0_znA8EaxjQ

### コード
```diff_javascript:JavaScript
const user = { log: console.log, name: "" }; console.log = () => { user.log("やめ太郎") }
```

### console.log()上書き勢・・・！

今回も出ました。
**console.log()上書き勢**ですね・・・！

ゆめみからの挑戦状を開催すると、一定数あらわれて
毎回の~~大喜利~~クイズを盛り上げてくれます。

問題文に「セミコロン禁止」と書かなかった私の負けです・・・！

# 別解③

https://twitter.com/kage1020/status/1580556039207849985?s=20&t=PH4jU9GiZyx0_znA8EaxjQ

### コード
```diff_javascript:JavaScript
const user = 1 ? { name: "やめ太郎" } :

user.name = "たかし"

console.log(user.name) // -> "やめ太郎"
```

### 無理やり三項演算子に・・・！

必ず条件が真になる三項演算子を使って、
`user.name = "たかし"`の部分を無効化していますね・・・！

これは思いつきませんでした。
アイディア力に脱帽です・・・！

# 別解④

https://twitter.com/coco_mirutiro/status/1580529426789322753?s=20&t=PH4jU9GiZyx0_znA8EaxjQ

### コード
```diff_javascript:JavaScript
const user = function やめ太郎 () {}
```

### 関数オブジェクトの`name`プロパティを利用

JavaScriptでは、関数も**オブジェクトの一種**です。
関数を定義すると、その関数は自動的に`name`というプロパティを持ちます。
`name`プロパティは読み取り専用なため、上書きできません。

その性質を利用した解答ですね。
こちらも思いつきませんでした・・・！

※ちなみに[TypeScriptのPlayground](https://www.typescriptlang.org/play?jsx=0&ssl=5&ssc=36&pln=5&pc=27#code/MYewdgzgLgBArhApgJxgXhgMzmYUCW4MgIQyCBDIFSagcwkwAUAlDAN4C+AUKwigHRgCGAtonQwARIH0GQNIMgdQYR7UJBAAbRF0UgA5jU7IeAxAwD0BmAFoAfKLJURQA)ではエラーになります

# 別解⑤

https://twitter.com/hw_nakajima/status/1581098084540420096?s=20&t=PH4jU9GiZyx0_znA8EaxjQ

### コード
```diff_javascript:JavaScript
const user = Object.freeze({name: "やめ太郎"})
```

### `Object.freeze()`を使用

`Object.freeze()`を使用して、オブジェクトを**凍結**させる方法です。
こうすることで、プロパティの値が変更できなくなります。

※こちらも[TypeScriptのPlayground](https://www.typescriptlang.org/play?jsx=0#code/MYewdgzgLgBArhApgJxgXhgeQEYCtHBQB0AZsoogF6IAUA3mAIYC2iAXDAESAhDIIEMgVJqA5hM4BfAJQBuAFBSEKIk1bougfQZA0gyB1Bk4zQkEABtERfSADmNOcgUtEYmAHp7MALQA+LnyGcgA)ではエラーになります

# まとめ

今回も、予想もしていなかったような解答が沢山ありました。
みなさんのおかげで、毎回すごく学びがあります。

ご参加いただいた皆さま、ありがとうございました！

# 参考文献

- [Proxy - JavaScript - MDN Web Docs - Mozilla](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
- [リアクティブの探求 - Vue.js](https://v3.ja.vuejs.org/guide/reactivity.html)

# 新しい記事もよろしくやで

- [押すと死んじゃうボタン](https://qiita.com/Yametaro/items/114301213ee525c096b5)

↓これの作り方を解説した記事ですやで:smiley:

https://twitter.com/Yametaro1983/status/1584503480160440322?s=20&t=wtOj7_Un1x8k7vuRsLhUcA