LoginSignup
7
5

More than 5 years have passed since last update.

JavascriptでObjectをextend / assign / mergeする4つの方法

Posted at

何が問題か

JavascriptでObjectを拡張する際、Object.assignを使うことが多いと思いますが、レガシーなライブラリにはjQuery.extend, angular.extend等、似たようなものがいっぱいあります。そして、少しずつ仕様が違うため、古いコードのメンテナンス等の際には、混乱することがあります。そこで、レガシーなライブラリのextend / assign系の機能の違いを整理してみました。

1. Object階層:たどらない / prototype chain: たどらない

Object.assign
lodash.assign
angular.extend (Angular1.xのみ)

疑似コード

function assign(base, ...args){
  for(obj of args){
      for(const key in obj) {
      if(obj.hasOwnProperty(key){
              base[key] = obj[key];
          }  
      }
  }
  return base;
}

2. Object階層:たどらない / prototype chain: たどる

lodash.assignIn
jQuery.extend

疑似コード

function assignIn(base, ...args){
  for(obj of args){
      for(const key in obj) {
          base[key] = obj[key];
      }
  }
  return base;
}

3. Object階層:たどる / prototype chain: たどらない

lodash.merge
angular.merge (Angular1.xのみ)

解説

この場合、各種オブジェクトをどう扱うかが問題になります。angularの場合、Date, RegExp, DOMElement, Arrayに対応していて、それぞれ適切な形に変換されます。これ以外の一般的なオブジェクト(自分で作ったクラスなど)は、Plain Objectになってしまいます。lodashは調べていません。ちなみに、自分で作ったクラスの__proto__も適切にコピーするためには、以下の擬似コードのような実装が適切です。ただし、組み込みオブジェクトは、別に扱う必要があり、DateとRegExp以外の組み込みオブジェクトには対応していません。

余談ですが、angular.mergeは、deprecatedで、loash.mergeを使えとドキュメントに書いてあります。個人的には、なんでangular.extendがdeprecatedにならないのかが不思議です。

疑似コード

function merge(base, ...args) {
  for(const obj of args){
    if(typeof obj == 'object' && (obj instanceof Date || obj instanceof RegExp)) {
      base = new obj.__proto__.constructor(obj)
      continue;
    }
    if(typeof base != 'object') {
      base = Object.create(obj.__proto__)
    }
    for(const key in obj){
      if(obj.hasOwnProperty(key)){
        if(typeof obj[key] == 'object'){
          base[key] = merge(base[key], obj[key])
        }else {
          base[key] = obj[key];            
        }
      }
    }
  }
  return base;
}

4. Object階層:たどる / prototype chain: たどる

jQuery.extend(true)

解説

対応しているオブジェクトは、Arrayのみで、それ以外はPlain Objectになってしまいます。

まとめ

オブジェクトを拡張 / マージする際には、

  1. Objectの階層をたどるかどうか
  2. キーの列挙の際に、prototype chain をたどってコピーするか、
  3. __proto__をコピーするか、コピーせず、Plain Objectに変換するか
  4. 組み込みオブジェクトのコピーにどこまで対応するか

といった複雑な要素が絡み合っています。これらを考慮して、適切なライブラリを選択する必要があります。

多くの場合は、Object.assignとlodash.mergeで問題ないと思いますが、古いのコードの移植の際などは、機械的に変換してしまったりすると、痛い目遭いますので、くれぐれも注意してください。

7
5
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
7
5