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的臨時變量.
總結:
- 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
- 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制
- 深拷貝則是復制變量值,對于非基本類型的變量,則遞歸至基本類型變量后,再復制