JavaScript有兩種數(shù)據(jù)類型
基礎(chǔ)數(shù)據(jù)類型(Undefined佩厚、Null声功、Boolean惰聂、Number 和 String). 變量是直接按值存放的,存放在棧內(nèi)存中的簡單數(shù)據(jù)段芝薇,可以直接訪問。
引用數(shù)據(jù)類型(Array作儿、Object)洛二,變量保存的是一個指針。當(dāng)需要訪問引用類型時,先獲得地址指針灭红,在從內(nèi)存中取得數(shù)據(jù)侣滩。
JavaScript存儲對象都是存地址的,所以淺拷貝會導(dǎo)致 obj1 和obj2 指向同一塊內(nèi)存地址变擒。改變了其中一方的內(nèi)容君珠,都是在原來的內(nèi)存上做修改會導(dǎo)致拷貝對象和源對象都發(fā)生改變,而深拷貝是開辟一塊新的內(nèi)存地址娇斑,將原對象的各個屬性逐個復(fù)制進(jìn)去策添。對拷貝對象和源對象各自的操作互不影響。
淺復(fù)制:
淺復(fù)制是復(fù)制引用毫缆,復(fù)制后的引用都是指向同一個對象的實例唯竹,彼此之間的操作會互相影響。(對于直接賦值是不會影響的)
如圖:
Object.assign() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標(biāo)對象苦丁,然后返回目標(biāo)對象浸颓。
對于深度克隆,我們需要使用其他替代方法旺拉,因為Object.assign()復(fù)制屬性值产上。如果源值是對象的引用,則它僅復(fù)制該引用值蛾狗。 ?
深復(fù)制:
Array的slice和concat方法
不修改原數(shù)組晋涣,只會返回一個淺復(fù)制了原數(shù)組中的元素的一個新數(shù)組。之所以把它放在深拷貝里沉桌,是因為它看起來像是深拷貝谢鹊。而實際上它是淺拷貝。 類似于Object.assign()
JSON對象的parse和stringify
JSON對象parse方法可以將JSON字符串反序列化成JS對象留凭,stringify方法可以將JS對象序列化成JSON字符串佃扼,借助這兩個方法,也可以實現(xiàn)對象的深拷貝冰抢。
但是對于正則表達(dá)式類型、函數(shù)類型等無法進(jìn)行深拷貝(而且會直接丟失相應(yīng)的值)挎扰。還有一點不好的地方是它會拋棄對象的constructor翠订。也就是深拷貝之后,不管這個對象原來的構(gòu)造函數(shù)是什么遵倦,在深拷貝之后都會變成Object尽超。同時如果對象中存在循環(huán)引用的情況也無法正確處理。
4梧躺、jQuery.extend()方法源碼實現(xiàn)
jQuery的源碼 - src/core.js #L121源碼及分析如下:
jQuery.extend = jQuery.fn.extend = function() { //給jQuery對象和jQuery原型對象都添加了extend擴(kuò)展方法
? var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
? i = 1,
? length = arguments.length,
? deep = false;
? //以上其中的變量:options是一個緩存變量似谁,用來緩存arguments[i]傲绣,name是用來接收將要被擴(kuò)展對象的key,src改變之前target對象上每個key對應(yīng)的value巩踏。
? //copy傳入對象上每個key對應(yīng)的value秃诵,copyIsArray判定copy是否為一個數(shù)組,clone深拷貝中用來臨時存對象或數(shù)組的src塞琼。
? // 處理深拷貝的情況
? if (typeof target === "boolean") {
? ? deep = target;
? ? target = arguments[1] || {};
? ? //跳過布爾值和目標(biāo)
? ? i++;
? }
? // 控制當(dāng)target不是object或者function的情況
? if (typeof target !== "object" && !jQuery.isFunction(target)) {
? ? target = {};
? }
? // 當(dāng)參數(shù)列表長度等于i的時候菠净,擴(kuò)展jQuery對象自身。
? if (length === i) {
? ? target = this; --i;
? }
? for (; i < length; i++) {
? ? if ((options = arguments[i]) != null) {
? ? ? // 擴(kuò)展基礎(chǔ)對象
? ? ? for (name in options) {
? ? ? ? src = target[name];
? ? ? ? copy = options[name];
? ? ? ? // 防止永無止境的循環(huán)彪杉,這里舉個例子毅往,
? ? ? ? ? ? // 如 var a = {name : b};
? ? ? ? ? ? // var b = {name : a}
? ? ? ? ? ? // var c = $.extend(a, b);
? ? ? ? ? ? // console.log(c);
? ? ? ? ? ? // 如果沒有這個判斷變成可以無限展開的對象
? ? ? ? ? ? // 加上這句判斷結(jié)果是 {name: undefined}
? ? ? ? if (target === copy) {
? ? ? ? ? continue;
? ? ? ? }
? ? ? ? if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
? ? ? ? ? if (copyIsArray) {
? ? ? ? ? ? copyIsArray = false;
? ? ? ? ? ? clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是數(shù)組的話就讓clone副本等于src否則等于空數(shù)組。
? ? ? ? ? } else {
? ? ? ? ? ? clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是對象的話就讓clone副本等于src否則等于空數(shù)組派近。
? ? ? ? ? }
? ? ? ? ? // 遞歸拷貝
? ? ? ? ? target[name] = jQuery.extend(deep, clone, copy);
? ? ? ? } else if (copy !== undefined) {
? ? ? ? ? target[name] = copy; // 若原對象存在name屬性攀唯,則直接覆蓋掉;若不存在渴丸,則創(chuàng)建新的屬性侯嘀。
? ? ? ? }
? ? ? }
? ? }
? }
? // 返回修改的對象
? return target;
};
jQuery的extend方法使用基本的遞歸思路實現(xiàn)了淺拷貝和深拷貝,但是這個方法也無法處理源對象內(nèi)部循環(huán)引用曙强,例如:
var a = {"name":"aaa"};
var b = {"name":"bbb"};
a.child = b;
b.parent = a;
$.extend(true,{},a);//直接報了棧溢出残拐。Uncaught RangeError: Maximum call stack size exceeded
5、自己動手實現(xiàn)一個拷貝方法
(function ($) {
? ? 'use strict';
? ? var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');
function type () {
? return Object.prototype.toString.call(this).slice(8, -1);
}
for (var i = types.length; i--;) {
? ? $['is' + types[i]] = (function (self) {
? ? ? ? return function (elem) {
? ? ? ? ? return type.call(elem) === self;
? ? ? ? };
? ? })(types[i]);
}
? ? return $;
})(window.$ || (window.$ = {}));//類型判斷
function copy (obj,deep) {
? ? if ($.isFunction(obj)) {
? ? return new Function("return " + obj.toString())();
? ? } else if (obj === null || (typeof obj !== "object")) {
? ? ? ? return obj;
? ? } else {
? ? ? ? var name, target = $.isArray(obj) ? [] : {}, value;
? ? ? ? for (name in obj) {
? ? ? ? ? ? value = obj[name];
? ? ? ? ? ? if (value === obj) {
? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? if (deep) {
? ? ? ? ? ? ? ? if ($.isArray(value) || $.isObject(value)) {
? ? ? ? ? ? ? ? ? ? target[name] = copy(value,deep);
? ? ? ? ? ? ? ? } else if ($.isFunction(value)) {
? ? ? ? ? ? ? ? ? ? target[name] = new Function("return " + value.toString())();
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? target[name] = value;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? target[name] = value;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return target;
? ? } ? ? ? ?
}