LoginSignup
1
2

More than 5 years have passed since last update.

JavaScript : 配列とオブジェクトを再帰的に凍結する関数

Last updated at Posted at 2019-03-10

JavaScriptでは、constで宣言しても配列やオブジェクトの要素は変更可能ですよね。
中味が変わらないといいなあと思ったことはありませんか?

中味も変更不可にした新しい配列、オブジェクトを返す関数deepFreezeを作ってみました。
元ネタは、 こちら の例にあるdeepFreeze。
再帰を今風に書くとこんなかな?というのと、使いやすいように新しいオブジェクトを返すようにしてみました。
対象は、配列、オブジェクトとプリミティブな値。関数もいけるかな? Map、Setはfreezeできないみたいです。

const mapForObj = f => a => 
  Object.keys(a).reduce( (acc, e)=>({ ...acc, [e]:f(a[e]) }), {})
const isObj = a => Object.prototype.toString.call(a)==="[object Object]"

const deepFreeze = a =>
  Array.isArray( a )?  Object.freeze( a.map( deepFreeze ) )
  : isObj( a )?  Object.freeze( mapForObj( deepFreeze )( a ) )
  : Object.freeze( a )

//使ってみる:
let func=x=>x

const obj = { a:1, b:[ { a:1 }, 2 ], c:func, d:{ x:[ 1, 2 ], y:2 } }

const frozen = deepFreeze(obj)
> frozen
=> { a: 1,
  b: [ { a: 1 }, 2 ],
  c: [Function: func],
  d: { x: [ 1, 2 ], y: 2 } }
> Object.isFrozen(frozen)
=> true
> Object.isFrozen(frozen.b)
=> true
> Object.isFrozen(frozen.b.a)
=> true
> Object.isFrozen(frozen.c)
=> true
> Object.isFrozen(frozen.d)
=> true
> Object.isFrozen(frozen.d.x)
=> true  // すべての階層で凍っている
> frozen.c=0  //関数frozen.cを変更してみるが...
=> 0
> frozen.c  
=> [Function: func] //変わらない。
> frozen.c(5)
=> 5      // x=>x 。変わらない
> frozen.c.a=0  //propertyを追加してみるが...
=> 0
> frozen.c.a
=> undefined  //追加できない
//ちなみにobjは凍っていない(当然ですが):
> Object.isFrozen(obj)
=> false
> 

ちゃんと中味も凍っているようです。

参照がループしてると無限ループに陥いる可能性とwindowとか凍らせて大変なことになる可能性があるそうな。 ご使用は自己責任で。

追記: 見た目をいじってみた。

JavaScriptは、オブジェクトの種類によってできることも方法も微妙に違っていたりするので、ちょっと複雑な感じになりましたが、やりたいことは単純です。

  • 深く凍結したものを返す関数は a を引数にとり、
  • a が配列かオブジェクトなら、aの要素を再帰的に深く凍結したものを新しく作り、それを破壊的操作で凍結して、それを返す。
  • そうでなければ、a を破壊的操作で凍結して、それを返す。

そう見えるようにコードにするとこんな感じでしょうか。

const pipe = (x, ...fs) => fs.reduce( (acc, f) => f(acc), x)

const isAryOrObj = a =>
  Array.isArray( a ) || Object.prototype.toString.call(a)==="[object Object]"
const map = f => a =>
  Array.isArray( a )? a.map( f )
  : Object.keys(a).reduce( (acc, e)=>({ ...acc, [e]:f(a[e]) }), {})
const freezeInPlace = a => Object.freeze( a )

const deepFreeze = a =>
  isAryOrObj( a )? 
    pipe( a, map(deepFreeze), freezeInPlace )
  : pipe( a, freezeInPlace )

関数の分割のしかたを変えて、それっぽい名前を付けてみました。同じように動きます。
パイプライン風またはメソッドチェーン風です。
前置きが長いですが、deepFreeze本体はかなりすっきりした感じです。

1
2
0

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
1
2