Js的淺拷貝與深拷貝

堆和棧的區別

堆和棧都是內存中劃分出來的用于存儲的區域。

深拷貝與淺拷貝的區別就是其在內存中存儲的類型不同。

棧(stack)為自動分配的內存空間,它由系統自動釋放;而堆(heap)則是動態分配的內存,大小不定也不會自動釋放。


ECMAScript的數據類型

基本數據類型 (number string boolean undefined null)。

上面5個是ECMAScript中的5種基本數據類型

基本數據類型存放在棧中

存放在棧內存中的簡單數據段,數據大小確定,內存空間大小可以分配,是直接按值存放的,所以可以直接訪問。

基本數據類型數據值不可改變

javascript中的原始值(undefined、null、布爾值、數字和字符串)與對象(包括數組和函數)有著根本區別。原始值是不可更改的:任何方法都無法更改(或“突變”)一個原始值。對數字和布爾值來說顯然如此 —— 改變數字的值本身就說不通,而對字符串來說就不那么明顯了,因為字符串看起來像由字符組成的數組,我們期望可以通過指定索引來假改字符串中的字符。實際上,javascript 是禁止這樣做的。字符串中所有的方法看上去返回了一個修改后的字符串,實際上返回的是一個新的字符串值。

例如:

//  基本類型的值不可改變

var str='html'
str[0]='c'
console.log(str)    //  html

基本類型的比較是值的比較

基本類型的比較是值的比較,只要它們相等就認為它們是相等的。

//  基本類型的比較是值的比較

var a='html'
var b='html'

console.log(a===b)    //  true

但是注意,比較的時候請使用 === 三個等號,表示嚴格等于。

比如:

//  js會默認做類型轉換

var a=1
var b=true

console.log(a==b)    //  true



引用類型

引用類型(object)是存放在堆內存中的,變量實際上是一個存放在棧內存的指針,這個指針指向堆內存中的地址。 每個空間大小不一樣,要根據情況進行特定的分配,例如。

var person1 = {name:'apple'};
var person2 = {name:'xiaomi'};
var person3 = {name:'huawei'};
object

引用類型值可變

//  引用類型值可變

var arr=[1,2,3,4,5]
arr[0]=10

console.log(arr)    //  [10, 2, 3, 4, 5]
引用類型的比較是引用的比較

所以每次我們對js中的引用類型進行操作的時候,都是操作其對象的引用。(保存在棧內存中的指針),所以比較兩個引用類型,是看其引用是否指向同一個對象。 例如:

 var a = [1,2,3];
 var b = [1,2,3];
 console.log(a === b); // false

雖然變量 a 和變量 b 都是表示一個內容為 1,2,3 的數組,但是其在內存中的位置不一樣,也就是說變量 a 和變量 b 指向的不是同一個對象,所以他們是不相等的。

它們兩個是不相等的



傳值與傳址

在我們進行賦值操作的時候,基本數據類型的賦值(=)是在內存中新開辟一段棧內存,然后再把再將值賦值到新的棧中。例如:

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10
基本數據類型的賦值

所以說,基本類型的賦值的兩個變量是兩個獨立相互不影響的變量。

但是引用類型的賦值是傳址。只是改變指針的指向,例如,也就是說引用類型的賦值是對象保存在棧中的地址的賦值,這樣的話兩個變量就指向同一個對象,因此兩者之間操作互相有影響。例如:

//  引用類型的賦值

var a={}
var b=a

a.name='jozo'

console.log(a.name)    //  jozo
console.log(b.name)    //  jozo

引用類型的賦值



淺拷貝

上面的=只是引用,不是淺拷貝。

賦值(=)與淺拷貝的區別

下面是一個例子:

//  賦值(=)與淺拷貝的區別

var obj1={
    name: 'xiaomi',
    age: 13,
    language:['English','Chinese','Germany']
}


var obj2=obj1

var obj3=shallowcopy(obj1)

function shallowcopy(src){
    var dst={}
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            dst[prop]=src[prop]
        }
    }
    return dst
}


obj2.name="huawei"
obj3.age=18

obj2.language='Germany'
obj3.language='Greek'


console.log(obj1)
/*
{
    age: 13
    language: (3) ["Greek", "French", "Germany"]
    name: "huawei"
    __proto__: Object
}
*/


console.log(obj2)
/*
    {
        age: 13
        language: Array(3)
        0: "Greek"
        1: "French"
        2: "Germany"
        length: 3
        __proto__: Array(0)
      name: "huawei"
    }
*/


console.log(obj3)
/* 
    {
        age: 18
        language: Array(3)
        0: "Greek"
        1: "French"
        2: "Germany"
        length: 3
        __proto__: Array(0)
      name: "xiaomi"
    }
*/

先定義個一個原始的對象 obj1,然后使用賦值得到第二個對象 obj2,然后通過淺拷貝,將 obj1 里面的屬性都賦值到obj3 中。也就是說:

  • obj1:原始數據
  • obj2:賦值操作得到
  • obj3:淺拷貝得到

然后我們改變 obj2name 屬性和 obj3name 屬性,可以看到,改變賦值得到的對象 obj2 同時也會改變原始值 obj1,而改變淺拷貝得到的的 obj3 則不會改變原始對象 obj1。這就可以說明賦值得到的對象 obj2 只是將指針改變,其引用的仍然是同一個對象,而淺拷貝得到的的 obj3 則是重新創建了新對象。

然而,我們接下來來看一下改變引用類型會是什么情況呢,我又改變了賦值得到的對象 obj2 和淺拷貝得到的 obj3 中的 language 屬性的第二個值和第三個值(language 是一個數組,也就是引用類型)。結果見輸出,可以看出來,無論是修改賦值得到的對象 obj2 和淺拷貝得到的 obj3 都會改變原始數據。

這是因為淺拷貝只復制一層對象的屬性,并不包括對象里面的為引用類型的數據。所以就會出現改變淺拷貝得到的 obj3 中的引用類型時,會使原始數據得到改變。

深拷貝:將 B 對象拷貝到 A 對象中,包括 B 里面的子對象,

淺拷貝:將 B 對象拷貝到 A 對象中,但不包括 B 里面的子對象

深拷貝與淺拷貝的區別



深拷貝

深拷貝是對對象以及對象的所有子對象進行拷貝。

怎么進行深拷貝呢?

思路就是遞歸調用剛剛的淺拷貝,把所有屬于對象的屬性類型都遍歷賦給另一個對象即可。我們直接來看一下 Zepto 中深拷貝的代碼:


    // 內部方法:用戶合并一個或多個對象到第一個對象
    // 參數:
    // target 目標對象  對象都合并到target里
    // source 合并對象
    // deep 是否執行深度合并
    function extend(target, source, deep) {
        for (key in source)
            if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
                // source[key] 是對象,而 target[key] 不是對象, 則 target[key] = {} 初始化一下,否則遞歸會出錯的
                if (isPlainObject(source[key]) && !isPlainObject(target[key]))
                    target[key] = {}

                // source[key] 是數組,而 target[key] 不是數組,則 target[key] = [] 初始化一下,否則遞歸會出錯的
                if (isArray(source[key]) && !isArray(target[key]))
                    target[key] = []
                // 執行遞歸
                extend(target[key], source[key], deep)
            }
            // 不滿足以上條件,說明 source[key] 是一般的值類型,直接賦值給 target 就是了
            else if (source[key] !== undefined) target[key] = source[key]
    }

    // Copy all but undefined properties from one or more
    // objects to the `target` object.
    $.extend = function(target){
        var deep, args = slice.call(arguments, 1);

        //第一個參數為boolean值時,表示是否深度合并
        if (typeof target == 'boolean') {
            deep = target;
            //target取第二個參數
            target = args.shift()
        }
        // 遍歷后面的參數,都合并到target上
        args.forEach(function(arg){ extend(target, arg, deep) })
        return target
    }

在 Zepto 中的 $.extend 方法判斷的第一個參數傳入的是一個布爾值,判斷是否進行深拷貝。

$.extend 方法內部,只有一個形參 target,這個設計你真的很巧妙。因為形參只有一個,所以 target 就是傳入的第一個參數的值,并在函數內部設置一個變量 args 來接收去除第一個參數的其余參數,如果該值是一個布爾類型的值的話,說明要啟用深拷貝,就將 deep 設置為 true,并將 target 賦值為 args 的第一個值(也就是真正的 target)。如果該值不是一個布爾類型的話,那么傳入的第一個值仍為 target 不需要進行處理,只需要遍歷使用 extend 方法就可以。

而在 extend 的內部,是拷貝的過程。

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

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,121評論 1 32
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔,今天18年5月份再次想寫文章,發現簡書還為我保存起的...
    Jenaral閱讀 2,808評論 2 9
  • 在復雜數據類型(object,Array)中,數據存儲在堆內存中,那么對于數據的拷貝就出現了兩種情況:拷貝引用和拷...
    樓下的黑貓不太冷閱讀 131評論 0 0
  • 每天都是睡會就醒來,醒來之后就沒有辦法在繼續睡,
    Ms_張閱讀 227評論 0 0
  • DNS服務器,一個關機重啟絕對修不好的網絡定位器。 平時大家上網總會遇到各種各樣的問題,可能是網線沒插好,也可能是...
    HeyBensir閱讀 981評論 0 4