javaScript中的淺拷貝和深拷貝

1、javaScript的變量類型

(1)基本類型:
5種基本數(shù)據(jù)類型Undefined噪生、Null裆赵、Boolean、Number 和 String跺嗽,變量是直接按值存放的战授,存放在棧內(nèi)存中的簡單數(shù)據(jù)段,可以直接訪問桨嫁。
(2)引用類型:
存放在堆內(nèi)存中的對(duì)象植兰,變量保存的是一個(gè)指針,這個(gè)指針指向另一個(gè)位置璃吧。當(dāng)需要訪問引用類型(如對(duì)象楣导,數(shù)組等)的值時(shí),首先從棧中獲得該對(duì)象的地址指針畜挨,然后再從堆內(nèi)存中取得所需的數(shù)據(jù)筒繁。

JavaScript存儲(chǔ)對(duì)象都是存地址的彬坏,所以淺拷貝會(huì)導(dǎo)致 obj1 和obj2 指向同一塊內(nèi)存地址。改變了其中一方的內(nèi)容膝晾,都是在原來的內(nèi)存上做修改會(huì)導(dǎo)致拷貝對(duì)象和源對(duì)象都發(fā)生改變栓始,而深拷貝是開辟一塊新的內(nèi)存地址,將原對(duì)象的各個(gè)屬性逐個(gè)復(fù)制進(jìn)去血当。對(duì)拷貝對(duì)象和源對(duì)象各自的操作互不影響幻赚。

例如:數(shù)組拷貝

//淺拷貝,雙向改變,指向同一片內(nèi)存空間
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " );   //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " );   //shallow copy: change,2,3

2臊旭、淺拷貝的實(shí)現(xiàn)

2.1落恼、簡單的引用復(fù)制
function shallowClone(copyObj) {
  var obj = {};
  for ( var i in copyObj) {
    obj[i] = copyObj[i];
  }
  return obj;
}
var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);     // true
2.2、Object.assign()

Object.assign() 方法可以把任意多個(gè)的源對(duì)象自身的可枚舉屬性拷貝給目標(biāo)對(duì)象离熏,然后返回目標(biāo)對(duì)象佳谦。Object.assign會(huì)跳過那些值為 null 或 undefined 的源對(duì)象。

var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);     // true

3滋戳、深拷貝的實(shí)現(xiàn)

3.1钻蔑、Array的slice和concat方法

Array的slice和concat方法不修改原數(shù)組,只會(huì)返回一個(gè)淺復(fù)制了原數(shù)組中的元素的一個(gè)新數(shù)組奸鸯。之所以把它放在深拷貝里咪笑,是因?yàn)樗雌饋硐袷巧羁截悺6鴮?shí)際上它是淺拷貝娄涩。原數(shù)組的元素會(huì)按照下述規(guī)則拷貝:

  • 如果該元素是個(gè)對(duì)象引用 (不是實(shí)際的對(duì)象)窗怒,slice 會(huì)拷貝這個(gè)對(duì)象引用到新的數(shù)組里。兩個(gè)對(duì)象引用都引用了同一個(gè)對(duì)象蓄拣。如果被引用的對(duì)象發(fā)生改變扬虚,則新的和原來的數(shù)組中的這個(gè)元素也會(huì)發(fā)生改變。
  • 對(duì)于字符串球恤、數(shù)字及布爾值來說(不是 String辜昵、Number 或者 Boolean 對(duì)象),slice 會(huì)拷貝這些值到新的數(shù)組里碎捺。在別的數(shù)組里修改這些字符串或數(shù)字或是布爾值路鹰,將不會(huì)影響另一個(gè)數(shù)組贷洲。

如果向兩個(gè)數(shù)組任一中添加了新元素收厨,則另一個(gè)不會(huì)受到影響。例子如下:

var array = [1,2,3]; 
var array_shallow = array; 
var array_concat = array.concat(); 
var array_slice = array.slice(0); 
console.log(array === array_shallow); //true 
console.log(array === array_slice); //false优构,“看起來”像深拷貝
console.log(array === array_concat); //false诵叁,“看起來”像深拷貝

可以看出,concat和slice返回的不同的數(shù)組實(shí)例钦椭,這與直接的引用復(fù)制是不同的拧额。而從另一個(gè)例子可以看出Array的concat和slice并不是真正的深復(fù)制碑诉,數(shù)組中的對(duì)象元素(Object,Array等)只是復(fù)制了引用。如下:

var array = [1, [1,2,3], {name:"array"}]; 
var array_concat = array.concat();
var array_slice = array.slice(0);
array_concat[1][0] = 5;  //改變array_concat中數(shù)組元素的值 
console.log(array[1]); //[5,2,3] 
console.log(array_slice[1]); //[5,2,3] 
array_slice[2].name = "array_slice"; //改變array_slice中對(duì)象元素的值 
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice
3.2侥锦、JSON對(duì)象的parse和stringify

JSON對(duì)象是ES5中引入的新的類型(支持的瀏覽器為IE8+)进栽,JSON對(duì)象parse方法可以將JSON字符串反序列化成JS對(duì)象,stringify方法可以將JS對(duì)象序列化成JSON字符串恭垦,借助這兩個(gè)方法快毛,也可以實(shí)現(xiàn)對(duì)象的深拷貝。

//例1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target";  //改變target的name屬性
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; //改變target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
//例2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
//例3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}

這種方法使用較為簡單番挺,可以滿足基本的深拷貝需求唠帝,而且能夠處理JSON格式能表示的所有數(shù)據(jù)類型,但是對(duì)于正則表達(dá)式類型玄柏、函數(shù)類型等無法進(jìn)行深拷貝(而且會(huì)直接丟失相應(yīng)的值)襟衰。還有一點(diǎn)不好的地方是它會(huì)拋棄對(duì)象的constructor。也就是深拷貝之后粪摘,不管這個(gè)對(duì)象原來的構(gòu)造函數(shù)是什么瀑晒,在深拷貝之后都會(huì)變成Object。同時(shí)如果對(duì)象中存在循環(huán)引用的情況也無法正確處理徘意。

4瑰妄、jQuery.extend()方法源碼實(shí)現(xiàn)

jQuery的源碼 - src/core.js #L121源碼及分析如下:

jQuery.extend = jQuery.fn.extend = function() { //給jQuery對(duì)象和jQuery原型對(duì)象都添加了extend擴(kuò)展方法
var options, name, src, copy, copyIsArray, clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;
    //以上其中的變量:options是一個(gè)緩存變量,用來緩存arguments[i]映砖,name是用來接收將要被擴(kuò)展對(duì)象的key间坐,src改變之前target對(duì)象上每個(gè)key對(duì)應(yīng)的value。
    //copy傳入對(duì)象上每個(gè)key對(duì)應(yīng)的value邑退,copyIsArray判定copy是否為一個(gè)數(shù)組竹宋,clone深拷貝中用來臨時(shí)存對(duì)象或數(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的時(shí)候地技,擴(kuò)展jQuery對(duì)象自身蜈七。
if ( length === i ) {
    target = this;
    --i;
}

for ( ; i < length; i++ ) {
    if ( (options = arguments[ i ]) != null ) {
        // 擴(kuò)展基礎(chǔ)對(duì)象
        for ( name in options ) {
            src = target[ name ];
            copy = options[ name ];

            // 防止永無止境的循環(huán),這里舉個(gè)例子莫矗,如var i = {};i.a = i;$.extend(true,{},i);如果沒有這個(gè)判斷變成死循環(huán)了
            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存在且是對(duì)象的話就讓clone副本等于src否則等于空數(shù)組。
                }

                // 遞歸拷貝
                target[ name ] = jQuery.extend( deep, clone, copy );

            } else if ( copy !== undefined ) {
                target[ name ] = copy;// 若原對(duì)象存在name屬性作谚,則直接覆蓋掉三娩;若不存在,則創(chuàng)建新的屬性妹懒。
            }
        }
    }
}

// 返回修改的對(duì)象
return target;
};

jQuery的extend方法使用基本的遞歸思路實(shí)現(xiàn)了淺拷貝和深拷貝雀监,但是這個(gè)方法也無法處理源對(duì)象內(nèi)部循環(huán)引用,例如:

var a={"name":"aaa"};
var b={"name":"bbb"};
a.child=b;
b.parent=a;
$.extend(true,{},a);//直接報(bào)了棧溢出。Uncaught RangeError: Maximum call stack size exceeded

5会前、自己動(dòng)手實(shí)現(xiàn)一個(gè)拷貝方法

var $ = (function(){
  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 $;
})();//類型判斷

function copy(obj,deep){ 
  if(obj === null || typeof obj !== "object"){ 
     return obj; 
  }      
  var name, target = $.isArray(obj) ? [] : {}, value; 
  for(name in obj){ 
     value = obj[name]; 
     if(value === obj) {
        continue;
     }
     if(deep && ($.isArray(value) || $.isObject(value))){
       target[name] = copy(value,deep);
     }else{
       target[name] = value;
     } 
  } 
  return target;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末好乐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瓦宜,更是在濱河造成了極大的恐慌蔚万,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件临庇,死亡現(xiàn)場離奇詭異笛坦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苔巨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門版扩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侄泽,你說我怎么就攤上這事礁芦。” “怎么了悼尾?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵柿扣,是天一觀的道長。 經(jīng)常有香客問我闺魏,道長未状,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任析桥,我火速辦了婚禮司草,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泡仗。我一直安慰自己埋虹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布娩怎。 她就那樣靜靜地躺著搔课,像睡著了一般。 火紅的嫁衣襯著肌膚如雪截亦。 梳的紋絲不亂的頭發(fā)上爬泥,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音崩瓤,去河邊找鬼袍啡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谷遂,可吹牛的內(nèi)容都是我干的葬馋。 我是一名探鬼主播卖鲤,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼肾扰,長吁一口氣:“原來是場噩夢啊……” “哼畴嘶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起集晚,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤窗悯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后偷拔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒋院,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年莲绰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欺旧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛤签,死狀恐怖辞友,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情震肮,我是刑警寧澤称龙,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站戳晌,受9級(jí)特大地震影響鲫尊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沦偎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一疫向、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豪嚎,春花似錦鸿捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妄荔,卻和暖如春泼菌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啦租。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工哗伯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篷角。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓焊刹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虐块,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在 JavaScript 中俩滥,對(duì)于Object和Array這類引用類型值,當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型值時(shí)...
    杰哥長得帥閱讀 86評(píng)論 0 0
  • 認(rèn)識(shí)對(duì)象的深拷貝和淺拷貝 在js的使用過程中, 賦值操作是最常見的一環(huán), 而在賦值操作中, 會(huì)有一個(gè)容易被初學(xué)者忽...
    _敏訥閱讀 184評(píng)論 0 0
  • 淺拷貝 淺拷貝是處理?xiàng)?nèi)存贺奠。 不同形式的淺拷貝示例: 基本類型如果是基本類型:number霜旧、string、bool...
    回調(diào)的幸福時(shí)光閱讀 432評(píng)論 0 2
  • underscore 的源碼中儡率,有很多地方用到了 Array.prototype.slice() 方法挂据,但是并沒有...
    theCoder閱讀 599評(píng)論 0 1
  • 越來越熟悉的環(huán)境,越來越熟的同事儿普,隨之而來的懈怠崎逃,我不知所措了。像馬老師說的眉孩,缺少激情婚脱。
    憶江南1991閱讀 257評(píng)論 0 0