JavaScript 數(shù)據(jù)結(jié)構(gòu)與算法之美 - 棧內(nèi)存與堆內(nèi)存 帐偎、淺拷貝與深拷貝

JavaScript 數(shù)據(jù)結(jié)構(gòu)與算法之美

前言

想寫(xiě)好前端逐纬,先練好內(nèi)功。

棧內(nèi)存與堆內(nèi)存 肮街、淺拷貝與深拷貝风题,可以說(shuō)是前端程序員的內(nèi)功,要知其然嫉父,知其所以然沛硅。

筆者寫(xiě)的 JavaScript 數(shù)據(jù)結(jié)構(gòu)與算法之美 系列用的語(yǔ)言是 JavaScript ,旨在入門(mén)數(shù)據(jù)結(jié)構(gòu)與算法和方便以后復(fù)習(xí)绕辖。

定義

  1. 后進(jìn)者先出摇肌,先進(jìn)者后出,簡(jiǎn)稱(chēng) 后進(jìn)先出(LIFO)仪际,這就是典型的結(jié)構(gòu)围小。
  2. 新添加的或待刪除的元素都保存在棧的末尾,稱(chēng)作棧頂树碱,另一端就叫棧底肯适。
  3. 在棧里,新元素都靠近棧頂成榜,舊元素都接近棧底框舔。
  4. 從棧的操作特性來(lái)看,是一種 操作受限的線性表赎婚,只允許在一端插入和刪除數(shù)據(jù)刘绣。
  5. 不包含任何元素的棧稱(chēng)為空棧

棧也被用在編程語(yǔ)言的編譯器和內(nèi)存中保存變量挣输、方法調(diào)用等纬凤,比如函數(shù)的調(diào)用棧。

定義

  • 堆數(shù)據(jù)結(jié)構(gòu)是一種樹(shù)狀結(jié)構(gòu)撩嚼。
    它的存取數(shù)據(jù)的方式停士,與書(shū)架與書(shū)非常相似。我們不關(guān)心書(shū)的放置順序是怎樣的完丽,只需知道書(shū)的名字就可以取出我們想要的書(shū)了向瓷。
    好比在 JSON 格式的數(shù)據(jù)中,我們存儲(chǔ)的 key-value 是可以無(wú)序的舰涌,只要知道 key,就能取出這個(gè) key 對(duì)應(yīng)的 value你稚。

堆與棧比較

  • 堆是動(dòng)態(tài)分配內(nèi)存瓷耙,內(nèi)存大小不一朱躺,也不會(huì)自動(dòng)釋放。
  • 棧是自動(dòng)分配相對(duì)固定大小的內(nèi)存空間搁痛,并由系統(tǒng)自動(dòng)釋放长搀。
  • 棧,線性結(jié)構(gòu)鸡典,后進(jìn)先出源请,便于管理。
  • 堆彻况,一個(gè)混沌谁尸,雜亂無(wú)章,方便存儲(chǔ)和開(kāi)辟內(nèi)存空間纽甘。

棧內(nèi)存與堆內(nèi)存

JavaScript 中的變量分為基本類(lèi)型和引用類(lèi)型良蛮。

  • 基本類(lèi)型是保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,它們的值都有固定的大小悍赢,保存在椌鐾空間,通過(guò)按值訪問(wèn)左权,并由系統(tǒng)自動(dòng)分配和自動(dòng)釋放皮胡。
    這樣帶來(lái)的好處就是,內(nèi)存可以及時(shí)得到回收赏迟,相對(duì)于堆來(lái)說(shuō)屡贺,更加容易管理內(nèi)存空間。
    JavaScript 中的 Boolean瀑梗、Null烹笔、Undefined、Number抛丽、String谤职、Symbol 都是基本類(lèi)型。

  • 引用類(lèi)型(如對(duì)象亿鲜、數(shù)組允蜈、函數(shù)等)是保存在堆內(nèi)存中的對(duì)象,值大小不固定蒿柳,棧內(nèi)存中存放的該對(duì)象的訪問(wèn)地址指向堆內(nèi)存中的對(duì)象饶套,JavaScript 不允許直接訪問(wèn)堆內(nèi)存中的位置,因此操作對(duì)象時(shí)垒探,實(shí)際操作對(duì)象的引用妓蛮。
    JavaScript 中的 Object、Array圾叼、Function蛤克、RegExp捺癞、Date 是引用類(lèi)型。

結(jié)合實(shí)例說(shuō)明

let a1 = 0; // 棧內(nèi)存
let a2 = "this is string" // 棧內(nèi)存
let a3 = null; // 棧內(nèi)存
let b = { x: 10 }; // 變量 b 存在于棧中构挤,{ x: 10 } 作為對(duì)象存在于堆中
let c = [1, 2, 3]; // 變量 c 存在于棧中髓介,[1, 2, 3] 作為對(duì)象存在于堆中
棧/堆內(nèi)存空間

當(dāng)我們要訪問(wèn)堆內(nèi)存中的引用數(shù)據(jù)類(lèi)型時(shí)

    1. 從棧中獲取該對(duì)象的地址引用
    1. 再?gòu)亩褍?nèi)存中取得我們需要的數(shù)據(jù)

基本類(lèi)型發(fā)生復(fù)制

let a = 20;
let b = a;
b = 30;
console.log(a); // 20
基本類(lèi)型發(fā)生復(fù)制過(guò)程

在棧內(nèi)存中的數(shù)據(jù)發(fā)生復(fù)制行為時(shí),系統(tǒng)會(huì)自動(dòng)為新的變量分配一個(gè)新值筋现,最后這些變量都是 相互獨(dú)立唐础,互不影響的

引用類(lèi)型發(fā)生復(fù)制

let a = { x: 10, y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5
  • 引用類(lèi)型的復(fù)制矾飞,同樣為新的變量 b 分配一個(gè)新的值一膨,保存在棧內(nèi)存中,不同的是凰慈,這個(gè)值僅僅是引用類(lèi)型的一個(gè)地址指針汞幢。
  • 他們兩個(gè)指向同一個(gè)值,也就是地址指針相同微谓,在堆內(nèi)存中訪問(wèn)到的具體對(duì)象實(shí)際上是同一個(gè)森篷。
  • 因此改變 b.x 時(shí),a.x 也發(fā)生了變化豺型,這就是引用類(lèi)型的特性仲智。

結(jié)合下圖理解

引用類(lèi)型(淺拷貝)的復(fù)制過(guò)程

總結(jié)

棧內(nèi)存 堆內(nèi)存
存儲(chǔ)基礎(chǔ)數(shù)據(jù)類(lèi)型 存儲(chǔ)引用數(shù)據(jù)類(lèi)型
按值訪問(wèn) 按引用訪問(wèn)
存儲(chǔ)的值大小固定 存儲(chǔ)的值大小不定,可動(dòng)態(tài)調(diào)整
由系統(tǒng)自動(dòng)分配內(nèi)存空間 由代碼進(jìn)行指定分配
空間小姻氨,運(yùn)行效率高 空間大钓辆,運(yùn)行效率相對(duì)較低
先進(jìn)后出,后進(jìn)先出 無(wú)序存儲(chǔ)肴焊,可根據(jù)引用直接獲取

淺拷貝與深拷貝

上面講的引用類(lèi)型的復(fù)制就是淺拷貝前联,復(fù)制得到的訪問(wèn)地址都指向同一個(gè)內(nèi)存空間。所以修改了其中一個(gè)的值娶眷,另外一個(gè)也跟著改變了似嗤。

深拷貝:復(fù)制得到的訪問(wèn)地址指向不同的內(nèi)存空間,互不相干届宠。所以修改其中一個(gè)值烁落,另外一個(gè)不會(huì)改變。

平時(shí)使用數(shù)組復(fù)制時(shí)豌注,我們大多數(shù)會(huì)使用 =伤塌,這只是淺拷貝,存在很多問(wèn)題轧铁。比如:

let arr = [1,2,3,4,5];
let arr2 = arr;
console.log(arr) //[1, 2, 3, 4, 5]
console.log(arr2) //[1, 2, 3, 4, 5]
arr[0] = 6;
console.log(arr) //[6, 2, 3, 4, 5]
console.log(arr2) //[6, 2, 3, 4, 5]
arr2[4] = 7;
console.log(arr) //[6, 2, 3, 4, 7]
console.log(arr2) //[6, 2, 3, 4, 7]

很明顯每聪,淺拷貝下,拷貝和被拷貝的數(shù)組會(huì)相互受到影響。

所以熊痴,必須要有一種不受影響的方法他爸,那就是深拷貝。

深拷貝的的復(fù)制過(guò)程

let a = { x: 10, y: 20 }
let b = JSON.parse(JSON.stringify(a));
b.x = 5;
console.log(a.x); // 10
console.log(b.x); // 5
復(fù)制前
復(fù)制后
b.x 修改為 5 后

數(shù)組

一果善、for 循環(huán)

//for 循環(huán) copy
function copy(arr) {
    let cArr = []
    for(let i = 0; i < arr.length; i++){
      cArr.push(arr[i])
    }
    return cArr;
}
let arr3 = [1,2,3,4];
let arr4 = copy(arr3) //[1,2,3,4]
console.log(arr4) //[1,2,3,4]
arr3[0] = 5;
console.log(arr3) //[5,2,3,4]
console.log(arr4) //[1,2,3,4]

二、slice 方法

//slice實(shí)現(xiàn)深拷貝
let arr5 = [1,2,3,4];
let arr6 = arr5.slice(0);
arr5[0] = 5;
console.log(arr5); //[5,2,3,4]
console.log(arr6); //[1,2,3,4]

三系谐、concat 方法

//concat實(shí)現(xiàn)深拷貝
let arr7 = [1,2,3,4];
let arr8 = arr7.concat();
arr7[0] = 5;
console.log(arr7); //[5,2,3,4]
console.log(arr8); //[1,2,3,4]

四巾陕、es6 擴(kuò)展運(yùn)算

//es6 擴(kuò)展運(yùn)算實(shí)現(xiàn)深拷貝
let arr9 = [1,2,3,4];
let [...arr10] = arr9;
arr9[0] = 5;
console.log(arr9) //[5,2,3,4]
console.log(arr10) //[1,2,3,4]

五、JSON.parse 與 JSON.stringify

let arr9 = [1,2,3,4];
let arr10 = JSON.parse(JSON.stringify(arr9))
arr9[0] = 5;
console.log(arr9) //[5,2,3,4]
console.log(arr10) //[1,2,3,4]

注意:該方法在數(shù)據(jù)量比較大時(shí)纪他,會(huì)有性能問(wèn)題鄙煤。

對(duì)象

一、對(duì)象的循環(huán)

//  循環(huán) copy 對(duì)象
let obj = {
    id:'0',
    name:'king',
    sex:'man'
}
let obj2 = copy2(obj)
function copy2(obj) {
    let cObj = {};
    for(var key in obj){
      cObj[key] = obj[key]
    }
    return cObj
}
obj2.name = "king2"
console.log(obj) // {id: "0", name: "king", sex: "man"}
console.log(obj2) // {id: "0", name: "king2", sex: "man"}

二茶袒、JSON.parse 與 JSON.stringify

var obj1 = {
    x: 1, 
    y: {
        m: 1
    },
    a:undefined,
    b:function(a,b){
      return a+b
    },
    c:Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ?, c: Symbol(foo)}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}, a: undefined, b: ?, c: Symbol(foo)}
console.log(obj2) //{x: 1, y: {m: 2}}

可實(shí)現(xiàn)多維對(duì)象的深拷貝梯刚。

注意:進(jìn)行JSON.stringify() 序列化的過(guò)程中,undefined薪寓、任意的函數(shù)以及 symbol 值亡资,在序列化過(guò)程中會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí))或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))。

三向叉、es6 擴(kuò)展運(yùn)算

let obj = {
    id:'0',
    name:'king',
    sex:'man'
}
let {...obj4} = obj
obj4.name = "king4"
console.log(obj) //{id: "0", name: "king", sex: "man"}
console.log(obj4) //{id: "0", name: "king4", sex: "man"}

四锥腻、Object.assign()

Object.assign() 只能實(shí)現(xiàn)一維對(duì)象的深拷貝。

var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) // {x: 1, y: 2}
console.log(obj2) // {x: 1, y: 2}

obj2.x = 2; // 修改 obj2.x
console.log(obj1) // {x: 1, y: 2}
console.log(obj2) // {x: 2, y: 2}

var obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) // {x: 1, y: {m: 1}}
console.log(obj2) // {x: 1, y: {m: 1}}

obj2.y.m = 2; // 修改 obj2.y.m
console.log(obj1) // {x: 1, y: {m: 2}}
console.log(obj2) // {x: 2, y: {m: 2}}

通用深拷貝方法

簡(jiǎn)單版

let clone = function (v) {
    let o = v.constructor === Array ? [] : {};
    for(var i in v){
      o[i] = typeof v[i] === "object" ? clone(v[i]) : v[i];
    }
    return o;
}
// 測(cè)試
let obj = {
    id:'0',
    name:'king',
    sex:'man'
}
let obj2 = clone(obj)
obj2.name = "king2"
console.log(obj) // {id: "0", name: "king", sex: "man"}
console.log(obj2) // {id: "0", name: "king2", sex: "man"}

let arr3 = [1,2,3,4];
let arr4 = clone(arr3) // [1,2,3,4]
arr3[0] = 5;
console.log(arr3) // [5,2,3,4]
console.log(arr4) // [1,2,3,4]

但上面的深拷貝方法遇到循環(huán)引用母谎,會(huì)陷入一個(gè)循環(huán)的遞歸過(guò)程瘦黑,從而導(dǎo)致爆棧,所以要避免奇唤。

let obj1 = {
    x: 1, 
    y: 2
};
obj1.z = obj1;
let obj2 = clone(obj1);
console.log(obj2) 

結(jié)果如下:

爆棧

總結(jié):深刻理解 javascript 的深淺拷貝幸斥,可以靈活的運(yùn)用數(shù)組與對(duì)象,并且可以避免很多 bug咬扇。

文章輸出計(jì)劃

JavaScript 數(shù)據(jù)結(jié)構(gòu)與算法之美 的系列文章甲葬,堅(jiān)持 3 - 7 天左右更新一篇,暫定計(jì)劃如下表冗栗。

標(biāo)題 鏈接
時(shí)間和空間復(fù)雜度 https://github.com/biaochenxuying/blog/issues/29
線性表(數(shù)組演顾、鏈表、棧隅居、隊(duì)列) https://github.com/biaochenxuying/blog/issues/34
實(shí)現(xiàn)一個(gè)前端路由挠乳,如何實(shí)現(xiàn)瀏覽器的前進(jìn)與后退 ? https://github.com/biaochenxuying/blog/issues/30
棧內(nèi)存與堆內(nèi)存 钱豁、淺拷貝與深拷貝 https://github.com/biaochenxuying/blog/issues/35
非線性表(樹(shù)瓜喇、堆) 精彩待續(xù)
遞歸 精彩待續(xù)
冒泡排序 精彩待續(xù)
插入排序 精彩待續(xù)
選擇排序 精彩待續(xù)
歸并排序 精彩待續(xù)
快速排序 精彩待續(xù)
計(jì)數(shù)排序 精彩待續(xù)
基數(shù)排序 精彩待續(xù)
桶排序 精彩待續(xù)
希爾排序 精彩待續(xù)
堆排序 精彩待續(xù)
十大經(jīng)典排序匯總 精彩待續(xù)

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正涕蚤,十分感謝宪卿。

7. 最后

關(guān)注我的公眾號(hào)的诵,第一時(shí)間接收最新的精彩博文。

文章可以轉(zhuǎn)載佑钾,但須注明作者及出處西疤,需要轉(zhuǎn)載到公眾號(hào)的,喊我加下白名單就行了休溶。

參考文章:

JavaScript棧內(nèi)存和堆內(nèi)存
JavaScript實(shí)現(xiàn)淺拷貝與深拷貝的方法分析
淺拷貝與深拷貝(JavaScript)

筆芯
全棧修煉
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末代赁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兽掰,更是在濱河造成了極大的恐慌芭碍,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽尽,死亡現(xiàn)場(chǎng)離奇詭異窖壕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杉女,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)瞻讽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人宠纯,你說(shuō)我怎么就攤上這事卸夕。” “怎么了婆瓜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵快集,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我廉白,道長(zhǎng)个初,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任猴蹂,我火速辦了婚禮院溺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磅轻。我一直安慰自己珍逸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布聋溜。 她就那樣靜靜地躺著谆膳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撮躁。 梳的紋絲不亂的頭發(fā)上漱病,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼杨帽。 笑死漓穿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的注盈。 我是一名探鬼主播晃危,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼当凡!你這毒婦竟也來(lái)了山害?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沿量,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后冤荆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體朴则,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年钓简,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乌妒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡外邓,死狀恐怖撤蚊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情损话,我是刑警寧澤侦啸,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站丧枪,受9級(jí)特大地震影響光涂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拧烦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一忘闻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恋博,春花似錦齐佳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至秦士,卻和暖如春缺厉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工提针, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留命爬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓辐脖,卻偏偏與公主長(zhǎng)得像饲宛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗜价,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353