それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Object.assign は何をする為にあるんや?

Object.assign が使えるようになってる。

Object.assign() - JavaScript | MDN

Object.assign(target, ...sources)

せっかくなので調べてみて、ソースコードの重複が減らせるのに使えるか調べてみた。

Object.assign は何をするのか

var a = { age : 17 }
var b = {name:'takuya', age: 19 }
var c = Object.assign( a,b )

console.log( c )

実行結果

{ age: 19, name: 'takuya' }

a に b を上書きする。ハッシュの結合とも言える。

連想配列を結合するときに Object.assign が便利コレ。

また、データと操作を別に作っておいて

クラスを new する感じにも使える。

コンストラクタで初期化データを渡してオブジェクトをnewして、データをいれて初期化する基本パターンを手軽に書くという意味においては便利

コンストラクタで、初期データを投入するパターン

Java で初級者が習う、コンストラクタで初期化するパターン

class Person {
  public String name;
  public String age;
  public Persion ( name, age ) {
    this.name = name ;
    this.age= age;
  }
  public static void main (String args[]) {
    Person p1 = new Person( "takuya", "19" ) 
  }
}

これをJSで書くとこんな感じになってめんどくさい。

var Person = function Person( name,age ){
  this.name = name;
  this.age  = age;
  this.say = function say (){ return (`Hello , I'm ${this.name}`) }
}
var p1_data = { name: 'takuya', age: 19 }  //← めんどくさい
var p1 = new Person( p1.name, p1.age ) //← めんどくさい

プロパティをEnumerationしていちいち突っ込むのがめんどくさいよね。

Object.assgin を使うと楽ができる。

var Person = {
    say: function () { return (`Hello , I'm ${this.name}`) }
}
var json_str = '{ "name":"takuya", "age": 19 }'

var c = Object.assign( JSON.parse(json_str) , Person )
console.log( c )
console.log( c.say() )

実行結果

{ name: 'takuya', age: 19, say: [Function: say] }
"Hello , I'm takuya"

Object.assign の多重利用( Mix-in )

複数のメソッドをもつオブジェクトに多重継承的にやりたいときも便利

var json_str = '{ "name":"takuya", "age": 19 }'
var c = Object.assign( JSON.parse(json_str) , {} )
var Person = {
      say: function () { return (`Hello , I'm ${this.name}`) }
}
var User = {
      'is_成年': function () { return this.name >= 20; }
}
c = Object.assign( c , Person )
c = Object.assign( c , User )

console.log( c )
console.log( c.say() )
console.log( c.is_成年() )

ネストした場合にどうなるのか

オブジェクトがネストしている場合、assign するとどうなるのか。

deep copy を期待すると出来ない。参照になる。

var person_01 = { name: 'takuya', address: { pref:'osaka' } }
var person_02 = { name: 'yakuta' }
c = Object.assign( person_01 , person_02 )
console.log( c )
// => { name: 'yakuta', address: { pref: 'osaka' } }
person_01.address.pref = 'kyoto'
console.log( c ) 
// =>  { name: 'yakuta', address: { pref: 'kyoto' } } # 参照コピーなので変わっちゃう

ディープコピーを意図しても参照扱いになって、ちょっと面倒になる。この辺は非常に使いづらい気がする。

他にも prototype の扱いに注意。

var Person = function Person( name,age ){
  this.name = name;
  this.age  = age;
  this.say = function say (){ return ("Hello , I'm "+this.name+"." ) }
}

p1.__proto__ //Person {}
p2 = Object.assign( {}, p1  ) 
// => { name: 'takuya', age: 19, say: [Function: say] }
p2.__proto__ //  {}
p1.name //'takuya'
p1.name = 'ya'  //'ya'
p2.name // 'takuya'

コピーされるのは、最上位のオブジェクトだけで、かつfor .. in列挙可能 な物に限られる。 prototype はコピーされない。

コピーされるのは、次のもの

// #### TODO 要確認
for ( i in a ) {
 if( a.hasOwnProperty( i ) ) {
   console.log(i)//ここに制御くるやつ
 }
}

コンストラクタの代わりに突っ込む

冒頭の例の、コンストラクタの代わりに突っ込んでassign することも出来るが。。。実はかえって面倒なことになる可能性を秘めている。

deep copy と参照問題で余り使い所はないかもしれない。

コレくらいのかんたんな結合なら全く問題ない。

// Person { name: undefined, age: undefined, say: [Function: say] }
Object.assign( p1, {name:'takuya'} )
// => Person { name: 'takuya', age: undefined, say: [Function: say] }
p1 = Object.assign( p1, {name:'takuya', age: 19} )
// => Person { name: 'takuya', age: 19, say: [Function: say] }

ただし、これを延長したりネストしたオブジェクトを使うと、途端に考えることが増えてめんどくさくなる。

なので、使い捨てJSON.parse(str) や shallow copy を拡張するのに限りとても便利だと思われる。

安全なObject.assign

prototype がコピーされない、参照コピーになってしまう。このあたりを考慮しObject.assign() を使うと new して 即時オブジェクトをAssignすることになる。

// # 使い捨てなので参照を間違って書き換える心配はない その1
var c = Object.assign( new Person() , JSON.parse(json_str) ) 
// # 使い捨てなので参照を間違って書き換える心配はない その2
var c = Object.assign( new Person() , {name:'takuya', age: 19, type: { user: true }} ) 
// # 浅いコピーなので問題ない。
var a = {name:'takuya', age: 19}
var c = Object.assign( new Person() , a ) 

まとめ

  • Object.assign は {} を結合するのに便利
  • ただし、深いコピーは出来ない
  • 浅いコピーは出来るが、深い部分は参照コピーになって不便
  • __proto__ など列挙されないものは、コピーされない
  • 変数同士の結合は参照の書き換えでバグる可能性がある。
  • JSON.parse()の結果にメソッド生やすのに便利
  • 逆手に取れば、生やしたメソッドを参照共有してくれるので便利。

此のままでは使いにくいので、なにかラッパーを噛ませる。たとえば再帰的にコピーするとか、プロトタイプをコピーするような関数と組み合わせたら便利に使える気がする。まぁそのへんはフレームワーク側がいいようにやってくれるんだろうと期待しておく。

浅いコピーのあたりを油断してassignをガンガン使うと、この先の仕様変更で技術的負債になりそうな気がする。

今回は、prototype に assign するようなことは試してないので今後試してみたい