<script><br>obj1 = { a : 'a', b : 'b' };<br>obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
<p>$.extend(true, obj1, obj2);</p>
<p>alert(obj1.x.xxx) // "xxx" を取得します</p>
<p>obj2.x.xxx = 'zzz';<br>alert(obj2.x.xxx); // "zzz" を取得<br>alert(obj1.x.xxx) // "xxx" を取得する必要があります。 <br></script>
$.extend(true, obj1, obj2) は、obj2 の属性を使用してオブジェクト obj1 を拡張することを意味し、ディープ コピーを意味するために最初のパラメーターが true に設定されます。
obj1 にはもともと "x" 属性がありませんでしたが、拡張後、obj1 は "x" 属性を持つだけでなく、obj2 の "x" 属性を変更しても、obj1 の "x" 属性には影響しません。値、これはいわゆる「ディープコピー」です。
浅いコピーの実装
浅いコピーのみを実装する必要がある場合は、次のようなものを使用できます。
$ = {
extend : function(target, options) {
for (オプションの名前) {
target[name] = options[name];
}
return target;
}
};
つまり、オプションの属性をターゲットにコピーするだけです。引き続き同様のコードでテストできますが、異なる結果が得られます (JS の名前が "jquery-extend.js" であると仮定します):
obj1 には "x" が含まれています" 属性ですが、この属性はオブジェクトです。obj2 の "x" を変更すると、obj1 にも影響があり、見つけにくいエラーが発生する可能性があります。
ディープコピーの実装
「ディープコピー」を実装したい場合、コピーされたオブジェクトが配列またはオブジェクトの場合、extendを再帰的に呼び出す必要があります。次のコードは、「ディープ コピー」の単純な実装です:
$ = {
extend : function(deep, target, options) {
for (オプションの名前) {
copy = options[name];
if (ディープ && インスタンスオブをコピー)配列) {
target[name] = $.extend(deep, [], copy);
);
} else {
target[name] = options[name];
}
}
return target;
}
};
属性が配列の場合、ターゲットを初期化します。 [name] を空の配列に設定し、extend を再帰的に呼び出します。2. 属性がオブジェクトの場合、target[name] を空のオブジェクトとして初期化し、それ以外の場合は extend を再帰的に呼び出します。 、プロパティを直接コピーします。
テスト コードは次のとおりです:
コードをコピーします
コードは次のとおりです:<script></div>obj1 = { a : 'a', b : 'b ' };<div class="codebody" id="code74991"> obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };<br>$.extend(true, obj1, obj2);<br>alert (obj1.x.xxx ); // "xxx"<br>obj2.x.xxx = 'zzz';<br>alert(obj2.x.xxx); // "zzz"<br>alert( obj1.x.xxx) ; // "xxx"<br></script>
を取得します。
ディープ コピーが指定されている場合、obj2 への変更は obj1 には影響しません。ただし、「配列のインスタンス」が IE5 で互換性がないなど、このコードにはまだいくつかの問題があります。 jQuery での実装は実際にはもう少し複雑です。
より完全な実装
次の実装は jQuery の extend() に近くなります:
$ = function() {
var copyIsArray,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
class2type = {
'[オブジェクト ブール値]' : 'ブール値',
'[オブジェクト番号]' : '数値',
'[オブジェクト文字列]' : '文字列',
'[オブジェクト関数]' : '関数',
'[オブジェクト配列]' : '配列',
'[オブジェクト日付]' : '日付',
'[オブジェクト正規表現]' : 'regExp',
'[オブジェクト オブジェクト]' : 'オブジェクト'
},
type = function(obj) {
return obj == null ?文字列(obj) : class2type[toString.call(obj)] || "オブジェクト";
},
isWindow = function(obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isArray = Array.isArray || function(obj) {
return type(obj) === "配列";
},
isPlainObject = function(obj) {
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (obj のキー) {
}
リターンキー === 未定義 || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
for (オプションの名前) {
src = target[name];
copy = options[name];
if (ターゲット === コピー) { 続行; }
if (ディープ && copy
&& (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
クローン = src && isArray(src) ?ソース: [];
} else {
clone = src && isPlainObject(src) ?ソース: {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== 未定義) {
target[name] = copy;
}
}
ターゲットを返す;
};
return { extend : extend };
}();
最初は $ = function(){...}();この書法、次の面と理解できます的写法类似:
func = function(){.. .};
$ = func();
つまり、関数をすぐに実行し、結果を $ に代入します。この書き方では、関数を使用してスコープを管理し、ローカル変数やローカル関数がグローバル スコープに影響を与えるのを防ぐことができます。さらに、ユーザーが $.extend() を呼び出して内部実装された関数を非表示にすることだけを望んでおり、最終的に返されるオブジェクトには extend のみが含まれます:
return { extend : extend };
次に、extend 関数と最初に追加の文があります:
if (target == = copy) { continue; }
これは、コピーされる属性コピーがターゲットと同じである場合、つまり「self」をコピーする場合に発生する無限ループを避けるためです。 「独自の属性」に変更すると、予期されたサイクルが失敗する可能性があります。
次に、オブジェクトが配列であるかどうかを判断する方法:
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
}
ブラウザに Array が組み込まれている場合.isArray 実装。ブラウザ独自の実装を使用します。それ以外の場合、オブジェクトは String に変換され、「[object Array]」かどうかが確認されます。
最後に、isPlainObject の実装を文ごとに見てみましょう:
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
obj.nodeType が定義されている場合は、これが DOM 要素であることを示します。このコードは、次の 4 つの状況ではディープ コピーが実行されないことを示します。
1. オブジェクトが未定義です。 2. String に変換すると、「[object Object]」ではありません。
3. obj は DOM 要素です。
4. obj はウィンドウです。
DOM 要素とウィンドウがディープコピーされない理由は、それらに含まれる属性が多すぎるためである可能性があります。特にウィンドウ オブジェクトの場合、組み込み属性はもちろん、グローバル ドメインで宣言されたすべての変数がその属性になります。
次はコンストラクターに関連するテストです:
if (obj.constructor && !hasOwn.call(obj, "constructor")
🎜>
オブジェクトにコンストラクターがあるが、それが独自の属性ではない場合、それは次のことを意味します。この場合、コンストラクターはプロトタイプを通じて継承されます。ディープコピーは実行されません。これは、次のコードを組み合わせることで理解できます:
コードをコピー
}
return key === 未定義 || hasOwn.call(obj, key);
これらのコードはオブジェクトのプロパティがすべて独自のものであるかどうかを確認するために使用されます。オブジェクトのプロパティを走査するとき、オブジェクト自体のプロパティから開始するため、最後のプロパティが独自のものであるかどうかを確認するだけで済みます。
これは、オブジェクトがプロトタイプを通じてコンストラクターまたは属性を継承する場合、オブジェクトは深くコピーされないことを意味します。これは、不確実な要素の導入や多数のコピーを避けるために、そのようなオブジェクトが複雑である可能性があることも考慮している可能性があります。時間のかかる処理は、関数名から「PlainObject」のみがディープコピーを行っていることが分かります。
次のコードを使用してテストする場合:
コードをコピーします