92
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-10-23

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

JavaScriptのクイズです。

クイズの内容

"たかし"を代入したのに"やめ太郎"と出力されました。なぜ?

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

コード

JavaScript
const user = /* ここに解答を書いてください */

user.name = "たかし"

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

この記事の内容

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

正解発表

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

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

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

解説

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

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

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

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

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

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

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

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

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

+ user.name = "たかし"

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

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

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

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

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

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

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

user.name = "たかし"

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

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

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

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

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

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

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に反映させる

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

別解

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

別解①

コード

JavaScript
const user = { set name(s) {}, get name() {return "やめ太郎"} }

セッターゲッター

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

別解②

コード

JavaScript
const user = { log: console.log, name: "" }; console.log = () => { user.log("やめ太郎") }

console.log()上書き勢・・・!

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

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

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

別解③

コード

JavaScript
const user = 1 ? { name: "やめ太郎" } :

user.name = "たかし"

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

無理やり三項演算子に・・・!

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

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

別解④

コード

JavaScript
const user = function やめ太郎 () {}

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

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

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

※ちなみにTypeScriptのPlaygroundではエラーになります

別解⑤

コード

JavaScript
const user = Object.freeze({name: "やめ太郎"})

Object.freeze()を使用

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

※こちらもTypeScriptのPlaygroundではエラーになります

まとめ

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

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

参考文献

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

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

92
18
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?