Object.assignの原理とその実現方式
38279 ワード
この文は最初にhttps://lijing0906.github.io 先週のまとめでは、赋価と深さのコピーについて、
浅いコピーObject.assign
前の記事では、その定義と使用法について述べましたが、主にエニュメレート・プロパティーのすべての値を一つまたは複数のソースオブジェクトから対象オブジェクトにコピーし、対象オブジェクトに戻ります.文法は以下の通りです
例1
浅いコピーはコピー対象の第一層の基本的なタイプの値であり、第一層の参照タイプのアドレスであることを知っています.
例2
1、原生
生の場合、
属性値にアクセスしない場合、オブジェクトに属性があるかどうかを判断するにはどうすればいいですか?
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
コピーが属性値であるため、属性値がオブジェクトへの参照である場合、コピーされたその参照アドレスが変更される.例2
String
タイプおよび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: false
とwritable: 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
はエニュメレーションできないので、直接マウントされた方式(エニュメレーション可能)でシミュレーションすることはできません.a
でObject.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: true
とfalse
は等しいので、undefined
はnull
に戻ります.次のように判断するだけでいいです.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 == null
、true
などがオブジェクトとして適切ではなく、オブジェクトundefined
の属性値であり、オブジェクトtrue
は列挙可能である.Object.keys(b); // [ 'v1', 'v2', 'v3', 'v4', 'v5', 'v6' ]
ここでは、ターゲットがオリジナルのタイプであれば、対象として包装され、上記のコードに対応するのがターゲットb
がb
に包装されるという問題が見られますが、シミュレーションが実現された場合はどうすればいいですか?簡単です.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
参考文献