Symbol.for のオブジェクト版みたいなことがしたい
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ テンプレートリテラルのタグ機能を使ってみる
WeakMap のキーなどで使う 関数中で毎回同じになるオブジェクトが欲しいです
イメージは Symbol.for が近いのですが Symbol 型はプリミティブ値なので オブジェクトではありません
WeakMap のキーにも使えません
普通にやろうとすると オブジェクトは作るたびに参照が違うので 最初に作ったのを保存しておいて 2 回目からはそれを取得するということになります
とりあえず 関数自身のプロパティとして uref にからオブジェクトを設定しました
2 回目からはすでにあるのでそれを使います
パット見分かりづらいですが 目的の動きです
この値を WeakMap のキーとして保存したオブジェクトの n プロパティをインクリメントしているので 呼び出した回数がカウントできています
これでも良いといえば良いのですが 関数のプロパティにしたくないのと なんかもうちょっといい感じの書き方がないかなと思ってしまいます
そこで思いついたのがこれ
参照を作る部分はここです
すごくシンプルです
(e => e) の部分は1つ目の引数を返す関数であればいいので 外部に作っておいたものでもいいです
でも大丈夫です
パット見わかるかといえば分かりづらいのは変わりませんが スッキリしていて見た目的に好みです
仕組み的には テンプレートリテラルのタグ機能を使っていて タグ関数の 1 つ目の引数に渡されるテンプレートのオブジェクト(文字列の配列)はソースコード中の同じ場所で得られるものなら何度呼び出しても同じオブジェクトが返されます
このオブジェクトを参照として使っています
このオブジェクトは freeze されていて変更はできないので毎回同じオブジェクトでも問題は起きません
わかりづらいので 名前をつけた関数にして呼び出そうとしたい気もしますが ソースコード中のリテラルの場所で同一とみなすので関数にしてまとめてしまうと 他の関数でも同じ参照になってしまいます
Symbol.for の場合は名前に応じて同じオブジェクトを返しますが これはソースコード中の場所に応じて同じ値になります
テンプレートリテラル中の文字列に何を入れるかは関係ありません
と書いても ref1 と ref2 は別物です
Symbol.for とは違って全然関係無いところで同じ参照のオブジェクトを取得することはできませんが 毎回関数を呼び出したときに同じ参照を得たいときに使う分には十分だと思います
テンプレートリテラルの実行してみた例です
a 関数で配列 v に 2 回 pass`` を push していますが 何回 a を実行しても 1 つめに push するものは同じもので 2 回目に push するものも同じです
v の配列に入ってる値は 偶数回目と奇数回目の 2 種類になります
イメージは Symbol.for が近いのですが Symbol 型はプリミティブ値なので オブジェクトではありません
WeakMap のキーにも使えません
普通にやろうとすると オブジェクトは作るたびに参照が違うので 最初に作ったのを保存しておいて 2 回目からはそれを取得するということになります
const wmap = new WeakMap()
function a(){
const uref = a.uref = a.uref || {}
const v = wmap.get(uref) || { n: 0 }
wmap.set(uref, v)
v.n++
return v.n
}
a()
//1
a()
//2
a()
//3
a()
//4
とりあえず 関数自身のプロパティとして uref にからオブジェクトを設定しました
2 回目からはすでにあるのでそれを使います
const uref = a.uref = a.uref || {}
パット見分かりづらいですが 目的の動きです
この値を WeakMap のキーとして保存したオブジェクトの n プロパティをインクリメントしているので 呼び出した回数がカウントできています
これでも良いといえば良いのですが 関数のプロパティにしたくないのと なんかもうちょっといい感じの書き方がないかなと思ってしまいます
そこで思いついたのがこれ
function b(){
const uref = (e => e)``
const v = wmap.get(uref) || { n: 0 }
wmap.set(uref, v)
v.n++
return v.n
}
b()
//1
b()
//2
b()
//3
b()
//4
参照を作る部分はここです
const uref = (e => e)``
すごくシンプルです
(e => e) の部分は1つ目の引数を返す関数であればいいので 外部に作っておいたものでもいいです
const pass = e => e
const uref = pass``
でも大丈夫です
パット見わかるかといえば分かりづらいのは変わりませんが スッキリしていて見た目的に好みです
仕組み的には テンプレートリテラルのタグ機能を使っていて タグ関数の 1 つ目の引数に渡されるテンプレートのオブジェクト(文字列の配列)はソースコード中の同じ場所で得られるものなら何度呼び出しても同じオブジェクトが返されます
このオブジェクトを参照として使っています
このオブジェクトは freeze されていて変更はできないので毎回同じオブジェクトでも問題は起きません
わかりづらいので 名前をつけた関数にして呼び出そうとしたい気もしますが ソースコード中のリテラルの場所で同一とみなすので関数にしてまとめてしまうと 他の関数でも同じ参照になってしまいます
Symbol.for の場合は名前に応じて同じオブジェクトを返しますが これはソースコード中の場所に応じて同じ値になります
テンプレートリテラル中の文字列に何を入れるかは関係ありません
const ref1 = pass``
const ref2 = pass``
と書いても ref1 と ref2 は別物です
Symbol.for とは違って全然関係無いところで同じ参照のオブジェクトを取得することはできませんが 毎回関数を呼び出したときに同じ参照を得たいときに使う分には十分だと思います
テンプレートリテラルの実行してみた例です
a 関数で配列 v に 2 回 pass`` を push していますが 何回 a を実行しても 1 つめに push するものは同じもので 2 回目に push するものも同じです
v の配列に入ってる値は 偶数回目と奇数回目の 2 種類になります
const v = []
const pass = e => e
const a = () => v.push(pass``, pass``)
a()
a()
const compare = arr => {
const matrix = []
for(const [i, vr] of Object.entries(arr)){
const row = []
for(const [j, vc] of Object.entries(arr)){
row[j] = vr === vc
}
matrix[i] = row
}
console.table(matrix)
}
compare(v)
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | true | false | true | false |
| 1 | false | true | false | true |
| 2 | true | false | true | false |
| 3 | false | true | false | true |