Es 6のオブジェクトの拡張

11021 ワード

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()
// {x:1, y:10}

 
CommonJSモジュールは変数のセットを出力し、簡潔な書き方を使用するのに適しています.
let ms = {};

function getItem (key) {
  return key in ms ? ms[key] : null;
}

function setItem (key, value) {
  ms[key] = value;
}

function clear () {
  ms = {};
}

module.exports = { getItem, setItem, clear };
//    
module.exports = {
  getItem: getItem,
  setItem: setItem,
  clear: clear
};

 
ES 6は,文字通りの量でオブジェクトを定義する場合に,メソッド2(式)をオブジェクトの属性名とし,すなわち式を括弧内に置くことを可能にする.
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

次は別の例です.
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

 
列挙可能性
オブジェクトの各プロパティには、プロパティの動作を制御する記述オブジェクト(Descriptor)があります.Object.getOwnPropertyDescriptorメソッドでは、プロパティの記述オブジェクトを取得できます.
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

オブジェクトを記述するenumerableのプロパティは、「列挙可能」と呼ばれ、falseの場合、現在のプロパティが無視されるアクションがあります.
現在、enumerablefalseのプロパティを無視する4つの操作があります.
  • for...inループ:オブジェクト自体と継承された列挙可能な属性のみを巡回します.
  • Object.keys():オブジェクト自体のすべての列挙可能な属性のキー名を返します.
  • JSON.stringify():オブジェクト自体の列挙可能な属性のみをシリアル化します.
  • Object.assign():enumerablefalseの属性を無視し、オブジェクト自体の列挙可能な属性のみをコピーします.

  • この4つの操作のうち、最初の3つはES 5であり、最後のObject.assign()はES 6が追加された.このうち、for...inのみが継承された属性を返し、他の3つの方法は継承された属性を無視し、オブジェクト自体の属性のみを処理します.実際、「列挙可能」(enumerable)という概念を導入する最初の目的は、いくつかの属性がfor...in操作を回避できるようにすることであり、そうでなければすべての内部属性と方法が遍歴される.例えば、オブジェクトプロトタイプのtoString方法、および配列のlength属性は、「列挙可能」によってfor...in遍歴を回避することである.
    Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
    // false
    
    Object.getOwnPropertyDescriptor([], 'length').enumerable
    // false
    

    上記のコードでは、toStringおよびlengthの属性のenumerableはいずれもfalseであるため、for...inはプロトタイプから継承された2つの属性に遍歴しない.
    また、ES 6では、すべてのClassのプロトタイプの方法は枚挙にいとまがないと規定されています.
    Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
    // false
    

    総じて、操作に継承されたプロパティを導入すると、問題が複雑化し、多くの場合、オブジェクト自体のプロパティだけに関心を持ちます.したがって、for...inサイクルではなく、Object.keys()サイクルで代用します.
    属性の遍歴
    ES 6には,オブジェクトの属性を巡回する5つの方法がある.
    (1)for...in for...inは、オブジェクト自体と継承された列挙可能な属性(Symbol属性を含まない)をループします.
    (2)Object.keys(obj) Object.keysは、オブジェクト自体の(継承されていない)すべての列挙可能な属性(Symbol属性を含まない)のキー名を含む配列を返します.
    (3)Object.getOwnPropertyNames(obj) Object.getOwnPropertyNamesは、オブジェクト自体のすべての属性(Symbol属性は含まれないが、列挙できない属性を含む)のキー名を含む配列を返します.
    (4)Object.getOwnPropertySymbols(obj) Object.getOwnPropertySymbolsは、オブジェクト自体のすべてのSymbol属性のキー名を含む配列を返します.
    (5)Reflect.ownKeys(obj) Reflect.ownKeysは、オブジェクト自体のすべてのキー名を含む配列を返します.キー名がSymbolまたは文字列であるかどうかにかかわらず、列挙可能かどうかにかかわらず.
    以上の5つの方法は,オブジェクトのキー名を遍歴し,同じ属性遍歴の順序規則を遵守する.
  • は、最初にすべての数値キーを巡回し、数値昇順に並べます.
  • は、次に、すべての文字列キーを巡回し、加入時間の昇順に並べ替える.
  • は最後にすべてのSymbolキーを巡回し、加入時間の昇順に並べた.
  • Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
    // ['2', '10', 'b', 'a', Symbol()]
    

    上記のコードでは、Reflect.ownKeysメソッドは、パラメータオブジェクトのすべての属性を含む配列を返します.この配列の属性順序は、まず数値属性2および10であり、次いで文字列属性bおよびaであり、最後にSymbol属性である.
    superキーワードthisキーワードは常に関数が存在する現在のオブジェクトを指し、ES 6はまた、現在のオブジェクトのプロトタイプオブジェクトを指す別の類似キーワードsuperを追加したことを知っています.
    const proto = {
      foo: 'hello'
    };
    
    const obj = {
      foo: 'world',
      find() {
        return super.foo;
      }
    };
    
    Object.setPrototypeOf(obj, proto);
    obj.find() // "hello"
    

    上記のコードでは、オブジェクトobj.find()メソッドのうち、super.fooによってプロトタイプオブジェクトprotofoo属性が参照される.
    なお、superキーワードがプロトタイプオブジェクトを表す場合は、オブジェクトのメソッドにのみ使用でき、他の場所で使用するとエラーが発生します.
    //   
    const obj = {
      foo: super.foo
    }
    
    //   
    const obj = {
      foo: () => super.foo
    }
    
    //   
    const obj = {
      foo: function () {
        return super.foo
      }
    }
    

    JavaScriptエンジンでは、ここのsuperはオブジェクトのメソッドに使用されていないため、上記の3つのsuperの使い方は間違っています.1つ目の書き方はsuperが属性に使用され、2つ目と3つ目の書き方はsuperが1つの関数に使用され、fooの属性に割り当てられます.現在、JavaScriptエンジンに確認できるのは、オブジェクトメソッドの略記法のみであり、オブジェクトのメソッドを定義している.
    JavaScriptエンジンの内部では、super.fooObject.getPrototypeOf(this).foo(プロパティ)またはObject.getPrototypeOf(this).foo.call(this)(メソッド)に等しい.
    const proto = {
      x: 'hello',
      foo() {
        console.log(this.x);
      },
    };
    
    const obj = {
      x: 'world',
      foo() {
        super.foo();
      }
    }
    
    Object.setPrototypeOf(obj, proto);
    
    obj.foo() // "world"
    

    上記のコードでは、super.fooはプロトタイプオブジェクトprotofooメソッドを指すが、バインドされたthisは現在のオブジェクトobjであるため、worldが出力される.
    かいぞうわりあて
    オブジェクトのデフォーマ値は、オブジェクトから値を取得するために使用されます.ターゲットオブジェクト自体のすべての遍歴可能な(enumerable)が読み込まれていない属性に相当し、指定したオブジェクトに割り当てられます.すべてのキーと値は、新しいオブジェクトにコピーされます.
    let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
    x // 1
    y // 2
    z // { a: 3, b: 4 }
    

    上記のコードでは、変数zは、解構賦値が存在するオブジェクトである.等号の右側にあるすべての未読み取りキー(aおよびb)を取得し、値とともにコピーします.
    解構賦値は、等号の右側がオブジェクトであることを要求するため、等号の右側がundefinedまたはnullである場合、オブジェクトに変換できないため、エラーが発生します.
    let { x, y, ...z } = null; //      
    let { x, y, ...z } = undefined; //      
    

    解構賦値は最後のパラメータでなければなりません.そうしないと、エラーが発生します.
    let { ...x, y, z } = obj; //     
    let { x, ...y, ...z } = obj; //     
    

    上記のコードでは、解構付与値は最後のパラメータではないので、エラーが発生します.
    なお、解構賦値のコピーは浅いコピーであり、すなわち、キーの値が複合タイプの値(配列、オブジェクト、関数)である場合、解構賦値コピーはこの値のコピーではなく、この値の参照である.
    let obj = { a: { b: 1 } };
    let { ...x } = obj;
    obj.a.b = 2;
    x.a.b // 2
    

    上記のコードでは、xは、オブジェクトobja属性をコピーした解構賦値が存在するオブジェクトである.aプロパティは、オブジェクトを参照します.このオブジェクトの値を変更すると、オブジェクトに対する参照の解釈に影響します.
    また,拡張演算子の解構賦値は,プロトタイプオブジェクトから継承された属性をコピーすることはできない.
    let o1 = { a: 1 };
    let o2 = { b: 2 };
    o2.__proto__ = o1;
    let { ...o3 } = o2;
    o3 // { b: 2 }
    o3.a // undefined
    

    上記のコードでは、オブジェクトo3o2をコピーしたが、o2自体の属性のみがコピーされ、そのプロトタイプオブジェクトo1の属性はコピーされなかった.
    次は別の例です.
    const o = Object.create({ x: 1, y: 2 });
    o.z = 3;
    
    let { x, ...newObj } = o;
    let { y, z } = newObj;
    x // 1
    y // undefined
    z // 3
    

    上記のコードでは、変数xは単純な解構賦値であるため、オブジェクトoが継承した属性を読み取ることができる.変数yzは拡張演算子の解構賦値であり、オブジェクトo自身の属性のみを読み取ることができるため、変数zは賦値に成功し、変数yは値を取れない.ES 6では、変数宣言文では、拡張演算子の後ろには変数名が必要であり、解賦値式ではないため、上記のコードには中間変数newObjが導入されており、以下に書くとエラーが報告される.
    let { x, ...{ y, z } } = o;
    // SyntaxError: ... must be followed by an identifier in declaration contexts
    

    割り当てを解く1つの用途は、関数のパラメータを拡張し、他の操作を導入することです.
    function baseFunction({ a, b }) {
      // ...
    }
    function wrapperFunction({ x, y, ...restConfig }) {
      //    x   y       
      //           
      return baseFunction(restConfig);
    }
    

    上記のコードでは、元の関数baseFunctionabをパラメータとして受け入れ、関数wrapperFunctionbaseFunctionに基づいて拡張され、余分なパラメータを受け入れ、元の関数の動作を保持することができる.
    拡張演算子
    オブジェクトの拡張演算子(...)は、パラメータオブジェクトのすべての遍歴可能な属性を取り出し、現在のオブジェクトにコピーするために使用されます.
    let z = { a: 3, b: 4 };
    let n = { ...z };
    n // { a: 3, b: 4 }
    

    配列は特殊なオブジェクトであるため、オブジェクトの拡張演算子も配列に使用できます.
    let foo = { ...['a', 'b', 'c'] };
    foo
    // {0: "a", 1: "b", 2: "c"}

    オブジェクトの拡張演算子は、Object.assign()メソッドを使用するのと同じです.
    let aClone = { ...a };
    //    
    let aClone = Object.assign({}, a);
    

    上の例はオブジェクトインスタンスのプロパティをコピーしただけで、オブジェクトを完全にクローンし、オブジェクトのプロトタイプのプロパティをコピーしたい場合は、次のように書くことができます.
    //    
    const clone1 = {
      __proto__: Object.getPrototypeOf(obj),
      ...obj
    };
    
    //    
    const clone2 = Object.assign(
      Object.create(Object.getPrototypeOf(obj)),
      obj
    );
    
    //    
    const clone3 = Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    )
    

    上記のコードでは、書き方1の__proto__属性はブラウザ以外の環境では必ずしも配置されていないため、書き方2と書き方3を推奨します.
    拡張演算子は、2つのオブジェクトを結合するために使用できます.
    let ab = { ...a, ...b };
    //    
    let ab = Object.assign({}, a, b);
    

    ユーザーがカスタマイズしたプロパティを拡張演算子の後ろに置くと、拡張演算子の内部の同名のプロパティが上書きされます.
    let aWithOverrides = { ...a, x: 1, y: 2 };
    //    
    let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
    //    
    let x = 1, y = 2, aWithOverrides = { ...a, x, y };
    //    
    let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
    

    上記のコードでは、aオブジェクトのxプロパティとyプロパティが、新しいオブジェクトにコピーされると上書きされます.
    これにより、既存のオブジェクト部分のプロパティを変更するのに便利です.
    let newVersion = {
      ...previousVersion,
      name: 'New Name' // Override the name property
    };
    

    上記のコードでは、newVersionオブジェクトがnameプロパティをカスタマイズし、その他のプロパティはすべてpreviousVersionオブジェクトからコピーされます.
    カスタムアトリビュートを拡張演算子の前に置くと、新しいオブジェクトを設定するデフォルトのアトリビュート値になります.
    let aWithDefaults = { x: 1, y: 2, ...a };
    //    
    let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
    //    
    let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
    

    配列の拡張演算子と同様に、オブジェクトの拡張演算子の後に式を付けることができます.
    const obj = {
      ...(x > 1 ? {a: 1} : {}),
      b: 2,
    };
    

    拡張演算子の後ろに空のオブジェクトがある場合、効果はありません.
    {...{}, a: 1}
    // { a: 1 }
    

    拡張演算子のパラメータがnullまたはundefinedの場合、この2つの値は無視され、エラーは報告されません.
    let emptyObject = { ...null, ...undefined }; //    
    

    拡張演算子のパラメータオブジェクトに、値取り関数getがある場合、この関数は実行されます.
    //        ,   x        ,    
    let aWithXGetter = {
      ...a,
      get x() {
        throw new Error('not throw yet');
      }
    };
    
    //      ,   x       
    let runtimeError = {
      ...a,
      ...{
        get x() {
          throw new Error('throw now');
        }
      }
    };