浅談Object.assign


Object.assignはES 6に新しく追加されたインターフェースで、主な用途は複数のJavaScriptを統合するためのオブジェクトです.
Object.assignインターフェースは複数のパラメータを受信できます.最初のパラメータは対象となり、後はソースオブジェクトとなります.assign方法は複数の元のオブジェクトの属性と方法を対象対象とした上に統合しています.この過程で同名の属性が現れたら、後で結合する属性は前の同名の属性を上書きします.
assignの基本的な使い方は以下の通りです.
var target  = {a : 1}; //    
var source1 = {b : 2}; //   1
var source2 = {c : 3}; //   2
var source3 = {c : 4}; //   3, source2         c
Object.assign(target,source1,source2,source3);
//    :
//{a:1,b:2,c:4}
assignの設計目的はインターフェースを結合するためのものですので、最初のパラメータは対象であるべきです.対象でないと内部でオブジェクトに変換されますので、nullやundefinedのようなオブジェクトに変換できない値にぶつかると、assignはエラーとなります.ただし、ソースオブジェクトのパラメータ位置が、オブジェクトに変換できないパラメータを受信すると、このソースオブジェクトのパラメータは無視されます.
ここで例を見ます.
const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
どうしてこの結果が現れましたか?まず、最初のパラメータ位置はオブジェクトであるため、エラーは発生しません.次に文字列をオブジェクトに変換すると、文字列の各文字を属性として扱いますので、abc 3文字は「0」、「1」、「2」の3つの属性が統合されます.しかし、ブール値と数値は変換対象時にも成功しました.ただし、属性は列挙できないので、属性は合併されませんでした.ここで覚えなければなりません.
「assignは列挙できない属性を統合しません」
Object(true) // {[[PrimitiveValue]]: true}
Object(10)  //  {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
上では、ブール値、数値、文字列をオブジェクトに変換した結果である.
同様に、Object.assignコピーの属性には制限があり、コピー対象自体の属性(引き継ぎ属性がコピーされない)だけで、列挙できない属性もコピーされない.ただし、属性名はSymbol値であり、Object.assignにコピーされても良い.assignが一つのオブジェクトだけをパラメータとして受信した場合、ソースがないオブジェクトを対象として統合すると、そのまま対象を返します.
知識のポイント:
  • Object.assignのコピーは浅いコピーです.つまり、コピーした属性の値が対象などの複合属性であれば、一つの参照しかコピーできません.
    const obj1 = {a: {b: 1}};
    const obj2 = Object.assign({}, obj1);
    
    obj1.a.b = 2;
    obj2.a.b // 2
    は、浅いコピーであるため、属性aの内部にいかなる変化があっても、ターゲットオブジェクトに現れます.
  • Object.assignを統合すると、同名の属性に触れると上書き現象が発生します.だから注意して使ってください.
  • Object.assignはObject向けに開発されたAPIであり、ソースオブジェクトのパラメータが未知であると他のタイプのパラメータが受信されると、タイプ変換を試みる.配列タイプの場合、タイプ変換の結果、各配列メンバーの値を属性キーとし、配列メンバーの配列中の位置を属性キーとします.複数の配列構成パラメータが一緒に入ってくるとカバーされます.具体的な例は、
    Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
    上のコードの中で、assignは、配列を属性名0、1、2のオブジェクトとして扱うので、ソース配列の0番属性4は、ターゲット配列の0番属性1をカバーしている.
  • Object.assignは属性値のみをコピーできます.属性値が一つのget(取得関数)であれば、先に値を求めてコピーします.
    //    
    const source = {
       //       
       get foo(){return 1}
    };
    //    
    const target = {};
    Object.assign(target,source);
    //{foo ; 1}    foo   get       
    Object.assignメソッドの一般的な用途        
  • オブジェクト追加属性
    class Point{
       constructor(x,y){
          Object.assign(this,{x,y});
       }
    }
    上の方法は、オブジェクトPointクラスの例示的なオブジェクトに属性xと属性yを追加することができる.
  • は、オブジェクト追加方法
    //       
    //                
    //            
    Object.assign(SomeClass.prototype,{
        someMethod(arg1,arg2){...},
        anotherMethod(){...}
    });
    としてクラスのプロトタイプオブジェクトに方法を追加した後、クラスのインスタンスは、この2つの方法を継承することができる.
  • クローンオブジェクト
    //       
    function clone(origin){
        //  origin     
        let originProto = Obejct.getPrototypeOf(origin);
        //      ,       , assign
        return Object.assign(Object.create(originProto),origin);
    }
  • は属性指定のデフォルト値
    //      
    const DEFAULTS = {
       logLevel : 0,
       outputFormat : 'html'
    };
    
    //   assign          ,     ,  options      ,       
    function processContent(options){
       options = Object.assign({},DEFAULTS,options);
       console.log(options);
       //...
    }
    で、assignの浅いコピーの懸念があり、DEFALTSオブジェクトとoptionsオブジェクトのこの時の値は最もシンプルな値である方がいいです.そうでなければ、関数は無効になります.
    const DEFAULTS = {
      url: {
        host: 'example.com',
        port: 7070
      },
    };
    
    processContent({ url: {port: 8000} })
    // {
    //   url: {port: 8000}
    // }
    上のコードは、urlが対象タイプであるため、デフォルト値のurlが上書きされましたが、内部にhost属性が欠けていて、bugとなりました.