Object.assignの原理とその実現方式

38279 ワード

この文は最初にhttps://lijing0906.github.io 先週のまとめでは、赋価と深さのコピーについて、Object.assignというような浅いコピー方式に言及しました.今週はその原理と実現方法を話します.
浅いコピーObject.assign
前の記事では、その定義と使用法について述べましたが、主にエニュメレート・プロパティーのすべての値を一つまたは複数のソースオブジェクトから対象オブジェクトにコピーし、対象オブジェクトに戻ります.文法は以下の通りです
Object.assign(target,  ...source)
targetはターゲットオブジェクトであり、...sourceはソースオブジェクトであり、修正後のオブジェクトを返すことができる.ターゲットオブジェクトとソースオブジェクトが同じ属性を持っている場合、ターゲットオブジェクトの属性はソースオブジェクトの同じ属性によってカバーされ、以降のソースオブジェクトの属性は同様に最初の属性をカバーします.
例1
浅いコピーはコピー対象の第一層の基本的なタイプの値であり、第一層の参照タイプのアドレスであることを知っています.
//    
let a = {
    name: "Kitty",
    age: 18
}
let b = {
    name: "Jane",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "Jane",
//     age: 18,
//     book: {title: "You Don't Know JS", price: "45"}
// } 
console.log(a === c); // true

//    
b.name = "change";
b.book.price = "55";
console.log(b);
// {
//     name: "change",
//     book: {title: "You Don't Know JS", price: "55"}
// } 

//    
console.log(a);
// {
//     name: "Jane",
//     age: 18,
//     book: {title: "You Don't Know JS", price: "55"}
// } 
1、第1のステップでは、Object.assignを用いてソースオブジェクトbの属性をオブジェクトaにコピーし、変更後のオブジェクトをcと定義し、bがaの同じ属性の値を置換することがわかる.上のコードで注意したいのは、オブジェクトcに戻るのがターゲットaです.2、第2のステップでは、ソースオブジェクトbの基本タイプ値(name)と参照タイプ値(book)が修正される.3、第3のステップでは、浅いコピー後のターゲットaの基本的なタイプの値は変更されていないが、参照タイプの値はObject.assignコピーが属性値であるため、属性値がオブジェクトへの参照である場合、コピーされたその参照アドレスが変更される.
例2StringタイプおよびSymbolタイプの属性はいずれもコピーされ、nullまたはundefinedの属性はスキップされない.
let a = {
   name: "Jane",
   age: 20
}
let b = {
   b1: Symbol("Jane"),
   b2: null,
   b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "Jane",
//     age: 20,
//     b1: Symbol(Jane),
//     b2: null,
//     b3: undefined
// } 
console.log(a === c); // true
Object.assignシミュレーション実現Object.assignを実現するための大まかな考え方は以下の通りである.
1、原生Objectがこの関数をサポートしているかどうかを判断し、存在しない場合は関数assignを作成し、Object.definePropertyを使用してObjectに結合する.2、パラメータが正しいかどうかを判断します.(ターゲットは空ではいけません.直接に「-」を設定して送ることができますが、値を設定しなければなりません.)Object()を用いてオブジェクトに変換し、toに保存し、最後にこのオブジェクトtoに戻る.4、for...inを使用して、エニュメレート・エニュメレート・属性を巡回巡回した.新しいオブジェクトにコピーします(hasOwnPropertyは、非プロトタイプチェーン上の属性を返します).検証を容易にするために、assign2を使用してassignの代わりに、以下のシミュレーションがsymbol属性をサポートしていないことに注意してください.ES5にはsymbolがまったくないので、実現コードは以下の通りです.
if (typeof Object.assign2 != 'function') {
   Object.defineProperty(Object, 'assign2', { //    1
       value: function(target) {
           'use strict';
           if (target == null) { //    2
               throw new Error('Cannot convert undefined or null to object');
           }
           var to = Object(target); //    3
           for (var index = 1; index < arguments.length; index++) {
               var nextSource = arguments[index];
               if (nextSource != null) { //    2
                   //    4
                   for (var nextKey in nextSource) {
                       if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                           to[nextKey] = nextSource[nextKey];
                       }
                   }
               }
           }
           return to;
       },
       writable: true,
       configurable: true
   })
}
テストします
let a = {
   name: "advanced",
   age: 18
}
let b = {
   name: "Jane",
   book: {
       title: "You Don't Know JS",
       price: "45"
   }
}
let c = Object.assign2(a, b);
console.log(c);
// {
//     name: "Jane",
//     age: 18,
//     book: {title: "You Don't Know JS", price: "45"}
// } 
console.log(a === c); // true
注意点1エニュメレート・タイプ
生の場合、Objectにマウントされた属性は列挙できないが、Objectに直接マウントされた属性aの後は列挙可能であるので、Object.definePropertyを使用し、enumerable: falsewritable: true, configurable: trueを設置しなければならない.以下のコード説明Object上の属性は列挙できない:
for (var i in Object) {
   console.log(Object[i]);
}
//    
Object.keys(Object); // []
Object.assignまたはObject.getOwnPropertyDescriptorを使用して、与えられた属性名がオブジェクトに直接存在するかどうかを確認し、Object.propertyIsEnumerableを満たす2つの方法を使用して、エニュメレート・オブスクランブルを確認することができる.具体的な使い方は以下の通りです.
Object.getOwnPropertyDescriptor(Object, 'assign');
// {
//     value: ƒ, 
//     writable: true,     //   
//     enumerable: false,  //     ,      false
//     configurable: true  //    
// }
Object.propertyIsEnumerable('assign'); // false
propertyIsEnumerable(...)に直接に属性enumerable: trueをマウントした後に列挙できる場合を見てみよう.
Object.a = function() {
    console.log('log a');
}
Object.getOwnpropertyDescriptor(Object, 'a');
// {
//      value: ƒ, 
//      writable: true, 
//      enumerable: true,  //       true
//      configurable: true
// }
Object.propertyIsEnumerable('a'); // true
Objectはエニュメレーションできないので、直接マウントされた方式(エニュメレーション可能)でシミュレーションすることはできません.aObject.assignを設定しなければなりません.もちろん、デフォルトはObject.definePropertyです.
Object.defineProperty(Object, 'b', {
    value: function() {
        console.log('log b');
    }
})
Object.getOwnPropertyDescriptor(Object, 'b');
// {
//     value: ƒ, 
//     writable: false,     //   
//     enumerable: false,  //     ,      false
//     configurable: false  //    
// }
注意点2はパラメータが正しいかどうかを判断します.writable: true, enumerable: false, configurable: truefalseは等しいので、undefinednullに戻ります.次のように判断するだけでいいです.
if (target == null) { // TypeError if undefined or null
    throw new TypeError('Cannot convert undefined or null to object');
}
注意点3オリジナルタイプは対象として包装されています.
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');
var obj = Object.assgin({}, v1, null, v2, undefined, v3, v4);
//         ,null undefined    
//   ,                     
console.log(obj); // {'0': 'a', '1': 'b', '2': 'c'}
上のコードは、v 1、v 2、v 3が実際に無視されていることが分かります.なぜなら、彼ら自身が列挙可能な属性を持っていないからです.
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');

// Object.keys()       ,         
//              ,   [[Prototype]] 
Object.keys( v1 ); // [ '0', '1', '2' ]
Object.keys( v2 ); // []
Object.keys( v3 ); // []
Object.keys( v4 ); // []
Object.keys( v5 ); // TypeError: Cannot convert undefined or null to object

// Object.getOwnPropertyNames(..)       ,      ,         
//              ,   [[Prototype]] 
Object.getOwnPropertyNames( v1 ); // [ '0', '1', '2', 'length' ]
Object.getOwnPropertyNames( v2 ); // []
Object.getOwnPropertyNames( v3 ); // []
Object.getOwnPropertyNames( v4 ); // []
Object.getOwnPropertyNames( v5 ); // TypeError: Cannot convert undefined or null to object
ただし、下記のコードは実行可能です.
var a = 'abc';
var b = {
    v1: 'def',
    v2: true,
    v3: 10,
    v4: Symbol('foo'),
    v5: null,
    v6: undefined
}
var obj = Objec.assign(a, b);
console.log(obj);
// {
//     [String: 'abc']
//     v1: 'def',
//     v2: true,
//     v3: 10,
//     v4: Symbol('foo'),
//     v5: null,
//     v6: undefined
// }
理由は簡単であるが、このときはundefined == nulltrueなどがオブジェクトとして適切ではなく、オブジェクトundefinedの属性値であり、オブジェクトtrueは列挙可能である.
Object.keys(b); // [ 'v1', 'v2', 'v3', 'v4', 'v5', 'v6' ]
ここでは、ターゲットがオリジナルのタイプであれば、対象として包装され、上記のコードに対応するのがターゲットbbに包装されるという問題が見られますが、シミュレーションが実現された場合はどうすればいいですか?簡単です.aを使えばいいです.
var a = 'abc';
console.log(Object(a)); // [String: 'abc']
ここまで多くの知識を紹介しました.もうちょっと伸ばしてみましょう.
var a = 'abc';
var b = 'def';
Object.assign(a, b); // TypeError: Cannot assign to read only property '0' of object '[object String]'
エラーの原因は[String: 'abc']の場合、その属性記述子は書き込み不可、すなわちObject()です.
var myObject = Object('abc');
Object.getOwnPropertyNames(myObject);
// [ '0', '1', '2', 'length' ]

Object.getOwnPropertyDescriptor(myObject, '0');
// { 
//   value: 'a',
//   writable: false, //     
//   enumerable: true,
//   configurable: false 
// }
注意点4の存在性
属性値にアクセスしない場合、オブジェクトに属性があるかどうかを判断するにはどうすればいいですか?
var anotherObject = {
    a: 1
};

//        anotherObject   
var myObject = Object.create(anotherObject);
myObject.b = 2;

('a' in myObject); // true
('b' in myObject); // true

myObject.hasOwnProperty('a'); // false
myObject.hasOwnProperty('b'); // true
上記はObject.assgin()オペレータとwritable: false方法で、次のように区別されています.
  • inオペレータは、属性がオブジェクトおよびhasOwnPropertyプロトタイプチェーン上にあるかどうかを確認する.
  • inは、属性が[[Prototype]]オブジェクトの中にあるかどうかをチェックするだけで、プロトタイプチェーンhasOwnProperty()の方法がプロトタイプチェーンの属性をコピーしないことは間違いないと判断しないので、シミュレーションを実行するにはmyObjectで処理下を判断する必要がありますが、[[Prototype]]を直接使用するのは問題があります.(例えばObject.assignによって作成される)この場合、hasOwnProperty()を使用すると失敗します.
  • var myObject = Object.create(null);
    myObject.b = 2;
    
    ('b' in myObject); // true
    
    myObject.hasOwnProperty( 'b' ); // TypeError: myObject.hasOwnProperty is not a function
    
    解決方法も簡単です.前に紹介したmyObject.hasOwnProperty()を使えばいいです.次のように使います.
    var myObject = Object.create(null);
    myObject.b = 2;
    
    Object.prototype.hasOwnProperty.call(myObject, 'b'); // true
    
    参考文献