成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

dailybird / 766人閱讀

摘要:棧內(nèi)存與堆內(nèi)存淺拷貝與深拷貝,可以說(shuō)是前端程序員的內(nèi)功,要知其然,知其所以然。棧內(nèi)存與堆內(nèi)存中的變量分為基本類型和引用類型。

前言
想寫(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í)。

定義

后進(jìn)者先出,先進(jìn)者后出,簡(jiǎn)稱 后進(jìn)先出(LIFO),這就是典型的結(jié)構(gòu)。

新添加的或待刪除的元素都保存在棧的末尾,稱作棧頂,另一端就叫棧底。

在棧里,新元素都靠近棧頂,舊元素都接近棧底。

從棧的操作特性來(lái)看,是一種 操作受限的線性表,只允許在一端插入和刪除數(shù)據(jù)。

不包含任何元素的棧稱為空棧。

棧也被用在編程語(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 中的變量分為基本類型和引用類型。

基本類型是保存在棧內(nèi)存中的簡(jiǎn)單數(shù)據(jù)段,它們的值都有固定的大小,保存在??臻g,通過(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 都是基本類型。

引用類型(如對(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 是引用類型。

結(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ì)象存在于堆中

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

從棧中獲取該對(duì)象的地址引用

再?gòu)亩褍?nèi)存中取得我們需要的數(shù)據(jù)

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

let a = 20;
let b = a;
b = 30;
console.log(a); // 20

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

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

let a = { x: 10, y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5

引用類型的復(fù)制,同樣為新的變量 b 分配一個(gè)新的值,保存在棧內(nèi)存中,不同的是,這個(gè)值僅僅是引用類型的一個(gè)地址指針。

他們兩個(gè)指向同一個(gè)值,也就是地址指針相同,在堆內(nèi)存中訪問(wèn)到的具體對(duì)象實(shí)際上是同一個(gè)。

因此改變 b.x 時(shí),a.x 也發(fā)生了變化,這就是引用類型的特性。

結(jié)合下圖理解

總結(jié)

| 棧內(nèi)存 | 堆內(nèi)存 |
| :------: | :------: |
| 存儲(chǔ)基礎(chǔ)數(shù)據(jù)類型 | 存儲(chǔ)引用數(shù)據(jù)類型 |
| 按值訪問(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ù)引用直接獲取 |

淺拷貝與深拷貝

上面講的引用類型的復(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

數(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: 2, 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: 1, 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/biaochenxu... |
| 線性表(數(shù)組、鏈表、棧、隊(duì)列) | https://github.com/biaochenxu... |
| 實(shí)現(xiàn)一個(gè)前端路由,如何實(shí)現(xiàn)瀏覽器的前進(jìn)與后退 ?| https://github.com/biaochenxu... |
| 棧內(nèi)存與堆內(nèi)存 、淺拷貝與深拷貝 | https://github.com/biaochenxu... |
| 非線性表(樹(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. 最后

文章中的代碼已經(jīng)全部放在了我的 github 上,如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

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

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

參考文章:

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105239.html

相關(guān)文章

  • 優(yōu)秀程序員都應(yīng)該學(xué)習(xí)的 GitHub 上開(kāi)源的數(shù)據(jù)結(jié)構(gòu)算法項(xiàng)目

    摘要:強(qiáng)烈推薦上值得前端學(xué)習(xí)的數(shù)據(jù)結(jié)構(gòu)與算法項(xiàng)目,包含圖的演示過(guò)程與視頻講解。該倉(cāng)庫(kù)包含了多種基于的算法與數(shù)據(jù)結(jié)構(gòu),提供進(jìn)一步閱讀的解釋和鏈接。數(shù)據(jù)結(jié)構(gòu)和算法必知必會(huì)的個(gè)代碼實(shí)現(xiàn)。 showImg(https://segmentfault.com/img/bVbvpYZ); 前言 算法為王。想學(xué)好前端,先練好內(nèi)功,內(nèi)功不行,就算招式練的再花哨,終究成不了高手;只有內(nèi)功深厚者,前端之路才會(huì)走得...

    cheukyin 評(píng)論0 收藏0
  • JavaScript 數(shù)據(jù)結(jié)構(gòu)算法之美 - 非線性表中的樹(shù)、堆是干嘛用的 ?其數(shù)據(jù)結(jié)構(gòu)是怎樣的 ?

    摘要:筆者寫(xiě)的數(shù)據(jù)結(jié)構(gòu)與算法之美系列用的語(yǔ)言是,旨在入門(mén)數(shù)據(jù)結(jié)構(gòu)與算法和方便以后復(fù)習(xí)。非線性表中的樹(shù)堆是干嘛用的其數(shù)據(jù)結(jié)構(gòu)是怎樣的希望大家?guī)е@兩個(gè)問(wèn)題閱讀下文。其中,前中后序,表示的是節(jié)點(diǎn)與它的左右子樹(shù)節(jié)點(diǎn)遍歷訪問(wèn)的先后順序。 showImg(https://segmentfault.com/img/bVbuvnj?w=900&h=250); 1. 前言 想學(xué)好前端,先練好內(nèi)功,內(nèi)功不行,...

    singerye 評(píng)論0 收藏0
  • JavaScript 數(shù)據(jù)結(jié)構(gòu)算法之美 - 歸并排序、快速排序、希爾排序、堆排序

    摘要:之所以把歸并排序快速排序希爾排序堆排序放在一起比較,是因?yàn)樗鼈兊钠骄鶗r(shí)間復(fù)雜度都為。歸并排序是一種穩(wěn)定的排序方法。因此,快速排序并不穩(wěn)定。希爾排序思想先將整個(gè)待排序的記錄序列分割成為若干子序列。 showImg(https://segmentfault.com/img/bVbvpYZ?w=900&h=250); 1. 前言 算法為王。 想學(xué)好前端,先練好內(nèi)功,只有內(nèi)功深厚者,前端之路才...

    haitiancoder 評(píng)論0 收藏0
  • JavaScript 數(shù)據(jù)結(jié)構(gòu)算法之美 - 冒泡排序、插入排序、選擇排序

    摘要:之所以把冒泡排序選擇排序插入排序放在一起比較,是因?yàn)樗鼈兊钠骄鶗r(shí)間復(fù)雜度都為。其中,冒泡排序就是原地排序算法。所以冒泡排序是穩(wěn)定的排序算法。選擇排序思路選擇排序算法的實(shí)現(xiàn)思路有點(diǎn)類似插入排序,也分已排序區(qū)間和未排序區(qū)間。 showImg(https://segmentfault.com/img/bVbuvnj?w=900&h=250); 1. 前言 算法為王。 想學(xué)好前端,先練好內(nèi)功,...

    canger 評(píng)論0 收藏0
  • JavaScript拷貝拷貝

    摘要:引用數(shù)據(jù)類型是存放在堆內(nèi)存中的,變量實(shí)際上是一個(gè)存放在棧內(nèi)存的指針,這個(gè)指針指向堆內(nèi)存中的地址。棧和堆的區(qū)別其實(shí)淺拷貝和深拷貝的主要區(qū)別就是數(shù)據(jù)在內(nèi)存中的存儲(chǔ)類型不同。這里,對(duì)存在子對(duì)象的對(duì)象進(jìn)行拷貝的時(shí)候,就是深拷貝了。 數(shù)據(jù)類型 在開(kāi)始拷貝之前,我們從JavaScript的數(shù)據(jù)類型和內(nèi)存存放地址講起。數(shù)據(jù)類型分為基本數(shù)據(jù)類型 和引用數(shù)據(jù)類型 基本數(shù)據(jù)類型主要包括undefin...

    娣辯孩 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<