先日、Twitter上でこんなクイズを出題しました。
JavaScriptのクイズです。
クイズの内容
"たかし"
を代入したのに"やめ太郎"
と出力されました。なぜ?
コードの/* ここに解答を書いてください */
の部分を埋めてください。
コード
const user = /* ここに解答を書いてください */
user.name = "たかし"
console.log(user.name)
// -> "やめ太郎"
この記事の内容
この記事では、上記のクイズの正解発表と解説をして行きます。
また、別解や面白解答もいくつかご紹介したいと思います。
正解発表
想定していた解答は以下です。
const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })
{ name: "" }
というオブジェクトを元に、
Proxy
オブジェクトを生成するやり方を想定していました。
解説
Proxy
オブジェクトを生成することによって、
元のオブジェクトへの操作を傍受したり再定義できます。
例
例えば以下のようにuser
という名前のProxy
オブジェクトを生成すると、
どのプロパティにアクセスしても"やめ太郎"
を取得するようになります。
// Proxyオブジェクトを生成
const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })
console.log(user.name)
// -> "やめ太郎"
console.log(user.a)
// -> "やめ太郎"
console.log(user.b)
// -> "やめ太郎"
↑存在しないプロパティにアクセスしても"やめ太郎"
を取得します。
また、name
プロパティを上書きしたとしても、結局"やめ太郎"
が出力されます。
// Proxyオブジェクトを生成
const user = new Proxy({ name: "" }, { get: () => "やめ太郎" })
+ user.name = "たかし"
console.log(user.name)
// -> "やめ太郎"
「あれ、name
プロパティに代入したのに、代入されていない?」と感じるかもしれませんが、代入はされています。
元のオブジェクトのname
は"たかし"
に書き換わっています。
ただ、Proxy
オブジェクトを通してname
プロパティを取得しているため
() => "やめ太郎"
という関数が作動し、"やめ太郎"
という値が取得されます。
ちゃんと代入されていることが分かる例
user.name
に"たかし"
を代入した後で、元のオブジェクトに直接アクセスすると"たかし"
が出力されます。
ちゃんと"たかし"
が代入できていることが分かります。
// 元のオブジェクト
const obj = { name: "" }
// Proxyオブジェクトを生成
const user = new Proxy(obj, { get: () => "やめ太郎" })
user.name = "たかし"
console.log(obj.name)
// -> "たかし"
代入をハックすることもできる
先ほどの例では、代入できていないように見えて、ちゃんと代入ができていました。
でも、代入処理をハックすることもできます。
const user = new Proxy(obj, { set: (target) => { target.name = "やめ太郎"; return true; } })
↑このように、new Proxy()
するときの第二引数にset
プロパティを持たせます。
こうすることで、どのプロパティに何を代入しようとしても、
name
プロパティに"やめ太郎"
が代入されるようになります。
// 元のオブジェクト
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に反映させる
という仕組みを実現しているようです。
別解
ここからは、別解をご紹介していきます。
別解①
コード
const user = { set name(s) {}, get name() {return "やめ太郎"} }
セッター&ゲッター
セッターとゲッターを使うパターンです。
そういえば、セッターとゲッターってありましたね・・・!
別解②
コード
const user = { log: console.log, name: "" }; console.log = () => { user.log("やめ太郎") }
console.log()上書き勢・・・!
今回も出ました。
console.log()上書き勢ですね・・・!
ゆめみからの挑戦状を開催すると、一定数あらわれて
毎回の大喜利クイズを盛り上げてくれます。
問題文に「セミコロン禁止」と書かなかった私の負けです・・・!
別解③
コード
const user = 1 ? { name: "やめ太郎" } :
user.name = "たかし"
console.log(user.name) // -> "やめ太郎"
無理やり三項演算子に・・・!
必ず条件が真になる三項演算子を使って、
user.name = "たかし"
の部分を無効化していますね・・・!
これは思いつきませんでした。
アイディア力に脱帽です・・・!
別解④
コード
const user = function やめ太郎 () {}
関数オブジェクトのname
プロパティを利用
JavaScriptでは、関数もオブジェクトの一種です。
関数を定義すると、その関数は自動的にname
というプロパティを持ちます。
name
プロパティは読み取り専用なため、上書きできません。
その性質を利用した解答ですね。
こちらも思いつきませんでした・・・!
※ちなみにTypeScriptのPlaygroundではエラーになります
別解⑤
コード
const user = Object.freeze({name: "やめ太郎"})
Object.freeze()
を使用
Object.freeze()
を使用して、オブジェクトを凍結させる方法です。
こうすることで、プロパティの値が変更できなくなります。
※こちらもTypeScriptのPlaygroundではエラーになります
まとめ
今回も、予想もしていなかったような解答が沢山ありました。
みなさんのおかげで、毎回すごく学びがあります。
ご参加いただいた皆さま、ありがとうございました!
参考文献
新しい記事もよろしくやで
↓これの作り方を解説した記事ですやで