栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!
詳解棧在前端中的應(yīng)用
- 一、棧是什么
- 二、棧的應(yīng)用場(chǎng)景
- 三、前端與棧:深拷貝與淺拷貝
- 1、JS數(shù)據(jù)類型
- (1)js數(shù)據(jù)類型的分類
- (2)js數(shù)據(jù)類型的定義和存儲(chǔ)方式
- (3)js數(shù)據(jù)類型的判斷方式
- 2、深究淺拷貝和深拷貝
- (1)淺拷貝
- (2)深拷貝
- 四、前端與棧:函數(shù)調(diào)用堆棧
- 五、寫在最后
棧 在日常生活中的應(yīng)用非常廣泛,比如我們最熟悉不過的十進(jìn)制轉(zhuǎn)二進(jìn)制、迷宮求解等等問題。同時(shí),它在前端中的應(yīng)用也非常廣泛,很多小伙伴都會(huì)誤以為 棧 在前端中的應(yīng)用很少,但殊不知的是,我們寫的每一個(gè)程序,基本上都會(huì)用到 棧 這個(gè)數(shù)據(jù)結(jié)構(gòu)。比如,函數(shù)調(diào)用堆棧、數(shù)據(jù)的深拷貝和淺拷貝……。
所以呢,對(duì)于一個(gè)前端工程師來說, 棧 結(jié)構(gòu)是一個(gè)必學(xué)的知識(shí)點(diǎn)。在接下來的這篇文章中,將講解關(guān)于 棧 在前端中的應(yīng)用。
一、棧是什么
- 棧是一種只能在表的一端(棧頂)進(jìn)行插入和刪除運(yùn)算的線性表;
- 只能在棧頂運(yùn)算,且訪問結(jié)點(diǎn)時(shí)依照后進(jìn)先出 (LIFO) 或先進(jìn)后出 (FILO) 的原則。
二、棧的應(yīng)用場(chǎng)景
- 需要后進(jìn)先出的場(chǎng)景;
- 比如:十進(jìn)制轉(zhuǎn)二進(jìn)制、迷宮求解、馬踏棋盤、判斷字符串是否有效、函數(shù)調(diào)用堆棧……。
三、前端與棧:深拷貝與淺拷貝
1、JS數(shù)據(jù)類型
談到堆棧,我們需要先來了解一下關(guān)于 js 的兩種數(shù)據(jù)類型。
(1)js數(shù)據(jù)類型的分類
首先,JavaScript中的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。
了解完分類以后,相信很多小伙伴心里有一個(gè)疑惑:這兩個(gè)數(shù)據(jù)類型是什么呢?且在內(nèi)存中是存放在哪里呢?
(2)js數(shù)據(jù)類型的定義和存儲(chǔ)方式
基本數(shù)據(jù)類型:
基本數(shù)據(jù)類型,是指 Numer 、 Boolean 、 String 、 null 、 undefined 、 Symbol(ES6新增的) 、 BigInt(ES2020) 等值,它們?cè)趦?nèi)存中都是存儲(chǔ)在 棧 中的,即直接訪問該變量就可以得到存儲(chǔ)在 棧 中的對(duì)應(yīng)該變量的值。
若將一個(gè)變量的值賦值給另一個(gè)變量,則這兩個(gè)變量在內(nèi)存中是獨(dú)立的,修改其中任意一個(gè)變量的值,不會(huì)影響另一個(gè)變量。這就是基本數(shù)據(jù)類型。
引用數(shù)據(jù)類型:
那引用數(shù)據(jù)類型呢,是指 Object 、 Array 、 Function 等值,他們?cè)趦?nèi)存中是存在于 棧和堆 當(dāng)中的,即我們要訪問到引用類型的值時(shí),需要先訪問到該變量在 棧 中的地址(這個(gè)地址指向堆中的值),然后再通過這個(gè)地址,訪問到存放在 堆 中的數(shù)據(jù)。這就是引用數(shù)據(jù)類型。
這樣說可能有點(diǎn)抽象,讓我們用一張圖來理解一下。
從上圖中可以看到, name 和 age 的值都是基本數(shù)據(jù)類型,所以他們指向程序中 棧 的位置。而 like 是數(shù)組類型,也就是引用數(shù)據(jù)類型,所以在 棧 中,它先存放了一個(gè) like 的地址,之后再把 like 對(duì)應(yīng)的值,存放到 堆 當(dāng)中。
了解完數(shù)據(jù)類型和其存儲(chǔ)方式后,在面試中,還有可能被問到如何判斷某一個(gè)數(shù)據(jù)的類型是什么?什么意思呢?比如說,給你一個(gè)數(shù)字 7 ,需要你來判斷它是什么,我們都知道它是Number類型,但很多時(shí)候止步于如何做才能判斷它是一個(gè)Number類型。接下來將詳細(xì)介紹三種判斷數(shù)據(jù)類型的方法。
(3)js數(shù)據(jù)類型的判斷方式
常用判斷方式:typeof、instanceof、===
1)typeof:
定義:返回?cái)?shù)據(jù)類型的字符串表達(dá)(小寫)
用法:typeof + 變量
可以判斷:
- undefined / 數(shù)值 / 字符串 / 布爾值 / function ( 返回 undefined / number / string / boolean / function )
- null 、 object 與 array (null、array、object都會(huì)返回 object )
以下給出代碼演示:
<script type="text/javascript">console.log(typeof "Tony"); // 返回 string console.log(typeof 5.01); // 返回 numberconsole.log(typeof false); // 返回 booleanconsole.log(typeof undefined); // 返回 undefinedconsole.log(typeof null); // 返回 objectconsole.log(typeof [1,2,3,4]); // 返回 objectconsole.log(typeof {name:'John', age:34}); // 返回 object </script>2)instanceof:
定義:判斷對(duì)象的具體類型
用法:b instanceof A → b是否是A的實(shí)例對(duì)象
可以判斷:
-
專門用來判斷對(duì)象數(shù)據(jù)的類型: Object , Array 與 Function
-
判斷 String ,Number ,Boolean 這三種類型的數(shù)據(jù)時(shí),直接賦值為 false ,調(diào)用構(gòu)造函數(shù)創(chuàng)建的數(shù)據(jù)為 true
以下給出代碼演示:
<script type="text/javascript">let str = new String("hello world") //console.log(str instanceof String); → truestr = "hello world" //console.log(str instanceof String); → falselet num = new Number(44) //console.log(num instanceof Number); → truenum = 44 //console.log(num instanceof Number); → falselet bool = new Boolean(true) //console.log(bool instanceof Boolean); → truebool = true //console.log(bool instanceof Boolean); → false</script> <script type="text/javascript">let items = []; let object = {}; function reflect(value) {return value; } console.log(items instanceof Array); // true console.log(items instanceof Object); // true console.log(object instanceof Object); // true console.log(object instanceof Array); // false console.log(reflect instanceof Function); // true console.log(reflect instanceof Object); // true </script>3)===:
可以判斷:undefined,null
以下給出代碼演示:
<script type="text/javascript">let str;console.log(typeof str, str === undefined); //'undefined', truelet str2 = null;console.log(typeof str2, str2 === null); // 'object', true</script>講到這里,我們了解了js的兩種數(shù)據(jù)類型,以及兩種數(shù)據(jù)類型相關(guān)的存儲(chǔ)方式和判斷方式。那么,接下來將講解他們?cè)谇岸酥谐R姷膽?yīng)用,深拷貝和淺拷貝。
2、深究淺拷貝和深拷貝
(1)淺拷貝
1)定義
所謂淺拷貝,就是一個(gè)變量賦值給另一個(gè)變量,其中一個(gè)變量的值改變,則兩個(gè)變量的值都變了,即對(duì)于淺拷貝來說,是數(shù)據(jù)在拷貝后,新拷貝的對(duì)象內(nèi)部 仍然有一部分?jǐn)?shù)據(jù) 會(huì)隨著源對(duì)象的變化而變化。
2)代碼演示
// 淺拷貝-分析 function shallowCopy(obj){let copyObj = {};for(let i in obj){copyObj[i] = obj[i];}return copyObj; }// 淺拷貝-實(shí)例 let a = {name: '張三',age: 19,like: ['打籃球', '唱歌', '跳舞'] }//將a拷貝給b let b = shallowCopy(a);a.name = '李四'; a.like[0] = '打乒乓球'; console.log(a); /* *{name: '李四',age: 19,like: ['打乒乓球', '唱歌', '跳舞']} */ console.log(b); /* *{name: '張三',age: 19,like: ['打乒乓球', '唱歌', '跳舞']} */3)圖例
從上面中的代碼可以看到,我們明明把 a 對(duì)象拷貝給 b 了,但是 b 最終打印出來的結(jié)果部分?jǐn)?shù)據(jù)不變,部分?jǐn)?shù)據(jù)卻變了。這個(gè)時(shí)候很多小伙伴就很疑惑了,這究竟是為什么呢?
我們回顧上面所說到的關(guān)于 引用數(shù)據(jù)類型 的知識(shí)點(diǎn),上述代碼中的 b 中的 like ,是一個(gè)數(shù)組,也就是引用數(shù)據(jù)類型。我們都知道,引用數(shù)據(jù)類型的數(shù)據(jù)是存放于 棧和堆 當(dāng)中的,所以上述中的 like 數(shù)組,我們將它視為一個(gè)地址,這個(gè)地址存放于 棧 當(dāng)中,同時(shí),這個(gè)地址里面的數(shù)據(jù),就指向于 堆 當(dāng)中。我們來看一下圖例。
從上圖中可以看到,當(dāng)對(duì) a 中 like 的數(shù)據(jù)進(jìn)行改變時(shí),它對(duì)應(yīng)的數(shù)據(jù)在 堆 中改變。而 b 拷貝后的 like 地址所指向的數(shù)據(jù),也是跟 a 一樣在 堆 中的位置。也就是說,a 和 b 中的 like 地址,它們的數(shù)據(jù)指向 堆 中的同一個(gè)位置,所以 b 在拷貝完數(shù)據(jù)以后,部分?jǐn)?shù)據(jù)會(huì)隨著 a 的變化而變化。這就是淺拷貝。
講完淺拷貝,接下來來了解深拷貝。
(2)深拷貝
1)定義:深拷貝就是,新拷貝的對(duì)象內(nèi)部所有數(shù)據(jù)都是 獨(dú)立存在 的,不會(huì)隨著源對(duì)象的改變而改變。
2)深拷貝有兩種方式:遞歸拷貝和利用 JSON 函數(shù)進(jìn)行深拷貝。
- 遞歸拷貝的實(shí)現(xiàn)原理是:對(duì)變量中的每個(gè)元素進(jìn)行獲取,若遇到基本類型值,直接獲取;若遇到引用類型值,則繼續(xù)對(duì)該值內(nèi)部的每個(gè)元素進(jìn)行獲取。
- JSON深拷貝的實(shí)現(xiàn)原理是:將變量的值轉(zhuǎn)為字符串形式,然后再轉(zhuǎn)化為對(duì)象賦值給新的變量。
3)局限性:深拷貝的局限性在于,會(huì)忽略u(píng)ndefined,不能序列化函數(shù),不能解決循環(huán)引用的對(duì)象。
4)代碼演示
// 深拷貝-遞歸函數(shù)方法分析 function deepCopy(obj){// 判斷是否為引用數(shù)據(jù)類型if(typeof obj === 'object'){let result = obj.constructor === Array ? [] : {};for(let i in obj){result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];}return result;}// 為基本數(shù)據(jù)類型,直接賦值返回else{return obj;} }// 深拷貝-遞歸函數(shù)方法實(shí)例 let c = {name:'張三',age:12,like:['打籃球','打羽毛球','打太極'] }let d = deepCopy(c);c.name = '李四'; c.like[0] = '打乒乓球'; console.log(c); /* *{name: '李四',age: 19,like: ['打乒乓球', '打羽毛球', '打太極']} */ console.log(d); /* *{name: '張三',age: 19,like: ['打籃球', '打羽毛球', '打太極']} */ // 深拷貝-JSON函數(shù)方法實(shí)例 let c = {name: '張三',age: 19,like:['打籃球', '唱歌', '跳舞'] }let d = JSON.parse(JSON.stringify(c));// 注意: JSON函數(shù)做深度拷貝時(shí)不能拷貝正則表達(dá)式,Date,方法函數(shù)等c.name = '李四'; c.like[0] = '打乒乓球';console.log(c); /* *{name: '李四',age: 19,like: ['打乒乓球', '唱歌', '跳舞']} */ console.log(d); /* *{name: '張三',age: 19,like: ['打籃球', '唱歌', '跳舞']} */從上述代碼中可以看到,深拷貝后的數(shù)據(jù)各自都是獨(dú)立存在的,不會(huì)隨著源對(duì)象的變化而變化,這就是深拷貝。不過值得注意的是,在我們平常的開發(fā)中,用的更多的是遞歸函數(shù)來進(jìn)行深拷貝,原因在于遞歸函數(shù)方法的靈活性會(huì)更強(qiáng)一點(diǎn)。而 JSON 函數(shù)方法有很多局限性,在做深度拷貝時(shí)不能拷貝正則表達(dá)式、Date、方法函數(shù)等。
四、前端與棧:函數(shù)調(diào)用堆棧
在我們平常的開發(fā)中,經(jīng)常會(huì)寫很多函數(shù),那函數(shù)在執(zhí)行過程中,其實(shí)就是一個(gè)調(diào)用堆棧。接下來我們用一段代碼來演示。
const func1 = () => {func2();console.log(3); }const func2 = () => {func3();console.log(4); }const func3 = () => {console.log(5); }func1(); //5 4 3看到這里,很多小伙伴心中可能已經(jīng)在構(gòu)思整段代碼的執(zhí)行順序是什么樣的。接下來用一張圖來展示。
我們都知道, JavaScript 的執(zhí)行環(huán)境是單線程的。所謂單線程是指一次只能完成一個(gè)任務(wù),如果有多個(gè)任務(wù),就必須排隊(duì),只有當(dāng)前面一個(gè)任務(wù)完成時(shí),才能執(zhí)行后面一個(gè)任務(wù),以此類推。上圖中所演示的,即每調(diào)用一個(gè)函數(shù),如果里面還有新的函數(shù),那么就先把它放到調(diào)用堆棧里,等到所有任務(wù)都放滿以后,開始依次執(zhí)行。
而函數(shù)調(diào)用堆棧是一個(gè)典型的棧的數(shù)據(jù)結(jié)構(gòu),遵循后進(jìn)先出原則,當(dāng) func1 , func2 , func3 依次放進(jìn)調(diào)用棧后, 遵循后進(jìn)先出原則 ,那么 func3 函數(shù)的內(nèi)容會(huì)先被執(zhí)行,之后是 func2 ,最后是 func1 。這就是函數(shù)調(diào)用堆棧。
五、寫在最后
棧在前端中的應(yīng)用就講到這里啦!棧在我們平常的開發(fā)中無處不在,我們寫的每一個(gè)程序,基本上都會(huì)用到函數(shù)調(diào)用堆棧。且在前端的面試中,面試官也很喜歡問深拷貝和淺拷貝,大家可以對(duì)這塊知識(shí)多回顧多實(shí)踐。
如果有不理解或者有誤的地方也歡迎私聊我或加我微信指正~
- 公眾號(hào):星期一研究室
- 微信:MondayLaboratory
創(chuàng)作不易,如果這篇文章對(duì)你有用,記得點(diǎn)個(gè) Star 哦~
總結(jié)
以上是生活随笔為你收集整理的栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 『软件测试3』八大典型的黑盒测试方法已来
- 下一篇: 详解链表在前端的应用,顺便再弄懂原型和原