原生js對象的淺拷貝和深拷貝的總結


title: 原生js對象的淺拷貝和深拷貝的總結

這里是說明.


在此之前我們先復習兩個知識點.

第一個知識點:對象方括號表示法

  • 一般來說,訪問對象屬性時使用的都是點表示法,這也是很多面向對象語言中通用的語法.不過在Javascript也可以用方括號表示法來訪問對象的屬性.在使用方括號語法時,應該將要訪問的屬性以字符串的形式放在方括號中
var person = {
    name : "gay"
}
alert(person["name"]); // "gay"

alert(person.name); // "gay

優點

可以通過變量來訪問屬性,例如:

var propertyName = "name";

alert(person[propertyName]);  // "gay"

如果屬性名中包含會導致語法錯誤的字符,或者屬性名使用的是關鍵字火保留字,也可以使用方括號表示法.例如:

person["first name"] = "gay";

由于"first name"中包含一個空格,所以不能使用點表示發來訪問它.然而,屬性名中是可以包含非字母非數字的,這時候就可以使用方括號表示法來訪問它們.

  • 通常,除非必須使用變量來訪問屬性,否則我們建議使用點表示法.

第二個知識點:函數傳遞參數:

  • ECMAScript中所有的函數都是按值傳遞!!!! -----高程第三版P70
  • ECMAScript中所有的函數都是按值傳遞!!!! -----高程第三版P70
  • ECMAScript中所有的函數都是按值傳遞!!!! -----高程第三版P70
  • 函數內部聲明的變量都是臨時變量!在函數執行完之后也會被銷毀!!!
  • 函數內部聲明的變量都是臨時變量!在函數執行完之后也會被銷毀!!!
  • 函數內部聲明的變量都是臨時變量!在函數執行完之后也會被銷毀!!!
  • 在JS中,如果一個引用類型賦值給一個變量,那么這個變量裝的是這個對象的地址!!!
  • 在JS中,如果一個引用類型賦值給一個變量,那么這個變量裝的是這個對象的地址!!!
  • 在JS中,如果一個引用類型賦值給一個變量,那么這個變量裝的是這個對象的地址!!!
    其實記住上面三句話,可以理解好多問題
    幾個例子解決你疑惑

第一個例子:

function addTen(num){
  num += 10;
}
var count = 20;
addTen(count);
console.log(count);//20
console.log(num);//"ReferenceError: num is not defined

分析一下函數 :
這個函數里面聲明了一個臨時變量num,,然后這個臨時變量會在函數結束后消失.
所以當我們即使把count的值傳給num,也不會影響count的值.
而最后輸出num的值也會出現未定義錯誤.

第二個例子

function addTen(num){
  num += 10;
  return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20
console.log(result);//30

分析:
如果我們想要函數里面的那個臨時變量num的值該怎么辦呢? 那么我們就要把它return出來,
最后可以看出 count輸出20 ,result(也就是臨時變量num的值,num變量在函數調用后已經消失,它的唯一作用就是臨死前告訴result它的值是多少)是30.

第三個例子

var person = {
  name : "wsy"
}
function setName(obj){
   obj.name = "gay"
}
setName(person);
console.log(person.name);// "gay"
console.log(obj.name);//"ReferenceError: obj is not defined

分析:
有人可能會說,你不是說是值傳遞么,為什么還會改變原來對象name的值.
可是我還說了一句話

  • 在JS中,如果一個引用類型賦值給一個變量,那么這個變量裝的是這個對象的地址!!!
    setName(person);
    進入函數后,我們定義一個臨時變量obj,這個obj里裝的是person對象的地址.注意是地址.學過c語言的同學肯定好理解,這個obj說白了就是一個指針變量唄.
    所以,當我們obj.name = "gay"改變的就是原來那個對象的name,因為他們共享一個地址.所以console.log(person.name);// "gay".
    又因為obj只是一個臨時變量,所以在函數外輸出obj.name肯定找不到了.因為obj已經掛了.

第四個例子

function setName(obj){
   obj.name = "gay"
   obj = new Object();
   obj.name = "les"
}
var person = new Object();
setName(person);
console.log(person.name);//"gay"

分析:
我們定義一個person對象.
setName(person);
然后進入函數,首先給臨時變量obj給一個值(person的地址).然后obj.name = "gay",因為obj和person共享一個地址,所以person的name屬性也變成了"gay".
然后 obj = new Object(); .注意這里 重新new了一個對象,(也就是重新在堆內存里分配了一塊地址)給臨時變量obj.此時,obj里裝的地址和person的地址并不是一個值.也就
是說改變obj.name并不會影響到person.

最后一個例子

function setName(obj){
   obj.name = "gay"
   obj = new Object();
   obj.name = "les"
   return obj;
}
var person = new Object();
var person1 = setName(person);
console.log(person.name);//"gay"
console.log(person1.name);//"les"

這個例子無非就是想把這個新開辟的obj返回出來.so easy~

總結

其實我們發現,紅皮書說的真好,js函數傳遞就是值傳遞.可為什么傳遞引用類型時會改變原來的值呢?是因為傳引用對象時,其實傳遞的是他的地址.所以他們共享地址了.
這就是傳說中的 call by shareing!
OK,讓我們進入正題!

淺拷貝:

簡單講,淺拷貝就是復制一份引用,所有引用對象都指向一份數據,并且都可以修改這份數據

var person = {

  name : "wsy"

}

var person1 = person;

person1.name = "yxy";

console.log(person.name); // yxy

從上述代碼中我們可以發現,改變person1的name值然后person的值也跟著改變.
內存分析圖:

再看一段代碼:

var Chinese = {
    name : "China"
}
function extendCopy(p) {
    var c = {};
    for (var i in p) {
        c[i] = p[i];
    }
    return c;
}
var Doctor = extendCopy(Chinese);
alert(Doctor.name);//China
Doctor.name = "USA"
alert(Chinese.name);//China

解釋下這個函數,創建一個c對象,然后 c["name"] = p["name"];
但是 改變c的name屬性并不影響p的屬性.
再往下看

Chinese.birthPlaces = ['北京','上海','香港'];
var Doctor = extendCopy(Chinese);
Doctor.birthPlaces.push('廈門');
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門
alert(Chinese.birthPlaces); //北京, 上海, 香港, 廈門

假如我們給Chinese添加一個屬性,這個屬性為一個數組對象.
然后再進行extendCopy函數賦值給Doctor.我們會發現改變Doctor的值會影響Chinese的值.
也就是說Doctor.birthPlaces 和 ChinesePlaces指向了同一塊內存.

經實驗,我們發現在extendCopy(p)函數中:
如果參數p的某一個屬性為基本類型.則為值傳遞(也就是僅僅簡簡單單的賦值)
如果參數p的某一個屬性為引用類型(對象),則為引用傳遞(這倆個對象的這個屬性指向同一塊內存)
所以,extendCopy() 只是拷貝了基本類型的數據,我們把這種拷貝叫做“淺拷貝”。

知乎用戶MickeyHong : Javascript 對于復制的問題其實有些模糊 不過總的來說 你只要記住Object在Javascript里是pass by reference的 其余的都是pass一個復制值 (我知道有人會吵>javascript都是pass by value的 而obj的value就是reference什么什么的)

深拷貝

  • 所謂”深拷貝”,就是能夠實現真正意義上的數組和對象的拷貝。它的實現并不難.深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
  • 所謂”深拷貝”,就是能夠實現真正意義上的數組和對象的拷貝。它的實現并不難.深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
  • 所謂”深拷貝”,就是能夠實現真正意義上的數組和對象的拷貝。它的實現并不難.深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
    直接擼代碼:
var Chinese = {
    birthPlaces : ['北京','上海','香港']
}
function deepCopy(p,c) {
    var c = c || {};
    for (var i in p) {
        if (typeof p[i] === 'object') {
            c[i] = (p[i].constructor === Array) ? [] : {};
            //alert(i);  // i = birthPlace
            //alert( c[i]);//空對象
            //alert(p[i]);//['北京','上海','香港'];
            deepCopy(p[i], c[i]);
        } else {
            c[i] = p[i];
        }
    }
    return c;
}
var Doctor = deepCopy(Chinese);
Doctor.birthPlaces.push('廈門');
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門
alert(Chinese.birthPlaces); //北京, 上海, 香港

這里我們實現了就算這個對象的某一個屬性為Object類型的,我們可以讓這兩個對象的這個屬性指向不同的內存.

  • 所謂”深拷貝”,就是能夠實現真正意義上的數組和對象的拷貝。它的實現并不難.深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制

ok,分析一下函數:

function deepCopy(p,c) {
    var c = c || {};
    for (var i in p) {
        if (typeof p[i] === 'object') {
            c[i] = (p[i].constructor === Array) ? [] : {};
            //alert(i);  // i = "birthPlace"
            //alert( c[i]);//空對象
            //alert(p[i]);//['北京','上海','香港'];
            deepCopy(p[i], c[i]);
        } else {
            c[i] = p[i];
        }
    }
    return c;
}

分析一下運行過程:
var Chinese = {
birthPlaces : ['北京','上海','香港']
}

現在Chinese只有一個屬性,這個屬性的值是一個數組(Array)
一步一步分析:

var Doctor = deepCopy(Chinese);
給deepCopy傳進入一個參數Chineese.

var c = c || {};
定義一個變量 c, 這個c的值是怎么計算的呢? 其實這里用了||的特性,如果傳進來的c不為null那么新定義的c的值就是傳進來的c的值,否則新定義的c等于一個空對象({ })
.而我們第一次調用deepCopy時,只傳進來一個一個參數,所以. 這里 var c = {}; 也就是定義c是一個空對象.\

for (var i in p)
這里由于p是一個對象,所以這里面的i值是循環p的屬性.由于Chinese只有birthPlaces一個屬性,所以只循環一次,i的值就是 "birthPlaces"(string類型).

if (typeof p[i] === 'object')
判斷p[i]是不是Object類型的.這里面p[i]就是p["birthPlaces"] 那么肯定是Object啊(這里用到的前面復習的第一個知識點::對象方括號表示法)

c[i] = (p[i].constructor === Array) ? [] : {};
判斷p[i]到底是哪個Object類型的,如果是數組那么;c["birthPlaces"]為空數組,如果是對象那么:c["birthPlaces"]為空對象.

deepCopy(p[i], c[i]);
也就是deepCopy(p["birthPlaces"],c["birthPlaces"])
也就是deepCopy(['北京','上海','香港'],[])

ok,我們再進入一遍這個函數
注意,剛才我們傳進去倆個參數,p = ['北京','上海','香港'],c = [];

var c = c || {};
然后 c = [];

for (var i in p)
這里p一個數組,所以i是這個數組的三個索引為 "0", "1" "2"所以進行三次循環 (注意這個索引是string類型的 )

if (typeof p[i] === 'object')
當i = "0"時,p["0"] = "北京", 而北京是一個string類型的.依次類推,這三次循環永遠不會進入這個if語句里.

else {

            c[i] = p[i];
        }

循環三次后,因為c本來就是一個數組.所以最后 c = ['北京','上海','香港']因為這個c和c["birthPlaces"]共享地址,所以c["birthPlaces"] = ['北京','上海','香港'];

return c;
在函數外var Doctor = deepCopy(Chinese);來接受這個我們在函數內新var的臨時變量.
總結:

  • 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
  • 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
  • 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容