堆棧內(nèi)存篇之?dāng)?shù)據(jù)類型與類型轉(zhuǎn)換
前言
堆棧內(nèi)存篇將以JavaScript的數(shù)據(jù)類型為引,然后一步步刨析堆棧內(nèi)存平挑,以此來(lái)了解JS的運(yùn)行機(jī)制游添。在這個(gè)過(guò)程中,我們也會(huì)學(xué)習(xí)到一些經(jīng)常出現(xiàn)的面試知識(shí)點(diǎn)弹惦,比如this指向否淤、閉包等。
數(shù)據(jù)類型分類
JS的數(shù)據(jù)類型分為兩大類棠隐,一類是基本數(shù)據(jù)類型石抡,一類是引用數(shù)據(jù)類型。兩種數(shù)據(jù)類型最大的區(qū)別在于:基礎(chǔ)數(shù)據(jù)類型是按值訪問的(實(shí)際操作的是值本身)助泽;而引用數(shù)據(jù)類型是按引用訪問的(實(shí)際操作的是地址)啰扛。
基本數(shù)據(jù)類型一共有七種:String
,Boolean
嗡贺,Number
隐解,Bigint
,Symbol
诫睬,Null
煞茫,Undefinded
引用數(shù)據(jù)類型有很多種,常見的有:Object
,Array
续徽,Function
蚓曼,Date
,RegExp
等钦扭。
引用數(shù)據(jù)類型可以統(tǒng)稱為一種纫版,即Object
,因?yàn)樵贘S中客情,一切都是基于對(duì)象的其弊。所以也有一些書籍在介紹JS的數(shù)據(jù)類型分類時(shí),是介紹為一共有八種數(shù)據(jù)類型膀斋,包括七種基本數(shù)據(jù)類型和一種復(fù)雜數(shù)據(jù)類型的梭伐。
我們?cè)谄匠J褂玫倪^(guò)程中可能會(huì)疑惑:為什么String
,Boolean
概页,Number
是基本數(shù)據(jù)類型籽御,但卻擁有其他引用類型一樣的特點(diǎn),比如:它們的實(shí)例同樣擁有屬性方法惰匙。這是因?yàn)檫@三個(gè)基本數(shù)據(jù)類型是極其特殊的,它們被稱為原始值包裝類型铃将。在每次用到某個(gè)原始值的屬性或方法時(shí)项鬼,后臺(tái)都會(huì)創(chuàng)建一個(gè)相對(duì)應(yīng)的對(duì)象,以此來(lái)暴露那些屬性方法劲阎,對(duì)于用戶來(lái)說(shuō)绘盟,就像在操作引用數(shù)據(jù)類型一樣。
let str1 = 'this is a test'
let str2 = str1.substring(2)
比如以上的代碼悯仙,當(dāng)執(zhí)行到第二句的時(shí)候龄毡。后臺(tái)執(zhí)行了以下三步:1.創(chuàng)建一個(gè)String類型的實(shí)例;2.調(diào)用實(shí)例上的特定方法锡垄;3.銷毀創(chuàng)建的實(shí)例
可以注意到這里創(chuàng)建的實(shí)例對(duì)象在執(zhí)行完語(yǔ)句之后就被銷毀了沦零,這也是引用類型和原始值包裝類型的不同之處:引用類型實(shí)例化的對(duì)象在離開作用域時(shí)才被銷毀,而原始值包裝類型自動(dòng)實(shí)例化的對(duì)象只存在于訪問它的那行代碼的執(zhí)行期間货岭,當(dāng)這行代碼運(yùn)行結(jié)束路操,該對(duì)象就被銷毀了。這意味著:在運(yùn)行時(shí)給和原始值包裝類型的變量添加屬性和方法是無(wú)效的
let str1 = 'test test'
str1.name = 'str1'
console.log(str1.name) // undefined
類型檢測(cè)
typeof
typeof
操作符是類型檢測(cè)最基礎(chǔ)的方法千贯,對(duì)一個(gè)變量使用typeof
操作符時(shí)屯仗,將會(huì)返回其對(duì)應(yīng)的數(shù)據(jù)類型
console.log(typeof 'string') // string
console.log(typeof 123) // number
console.log(typeof true) // boolean
console.log(typeof 123n) // bigInt
console.log(typeof Symbol()) // symbol
console.log(typeof a) // undefined
console.log(type null) // object
console.log(typeof new Object()) // object
console.log(typeof new Array()) // objecct
console.log(typeof new Function()) //function
注意:基本數(shù)據(jù)類型中除了null
之外都會(huì)返回對(duì)應(yīng)的數(shù)據(jù)類型,而null
返回的是object
搔谴,這是因?yàn)?code>null表示的是一個(gè)空對(duì)象魁袜,因此是一個(gè)對(duì)象。而引用數(shù)據(jù)類型中除了function
之外都返回object
,因?yàn)?code>function雖然也是對(duì)象峰弹,但因?yàn)槠涮厥庑缘炅浚跃桶阉推渌麑?duì)象區(qū)分開來(lái)。
instanceof
由上可知垮卓,當(dāng)對(duì)引用數(shù)據(jù)類型使用typeof
時(shí)垫桂,除了function
之外,其他的都是返回object
粟按,這就不能使用它來(lái)區(qū)分Array
诬滩,Date
等類型了。
而instanceof
的功能就是用來(lái)判斷一個(gè)對(duì)象是否為另一個(gè)構(gòu)造函數(shù)(類)的實(shí)例灭将。
語(yǔ)法為object instanceof constructor
疼鸟。該運(yùn)算符是通過(guò)判斷對(duì)象的原型鏈上是否存在著構(gòu)造函數(shù)的原型對(duì)象(本文不著重介紹關(guān)于原型鏈和原型對(duì)象的細(xì)節(jié)),當(dāng)結(jié)果為是時(shí)返回true
庙曙,否則返回false
let a = []
a instanceof Array // true
a instanceof Object // true
a instanceof String // false
且該修飾符不局限于JS自帶的引用數(shù)據(jù)類型空镜,它也可用于自定義的構(gòu)造函數(shù)
function Foo() {} // 自定義的構(gòu)造函數(shù)
let bar = new Foo()
bar instanceof Foo // true
bar instanceof object // true
constructor
我們知道原始值包裝類型使用起來(lái)與引用數(shù)據(jù)類型并沒有多大不同,其在被訪問其屬性和方法時(shí)也會(huì)進(jìn)行實(shí)例化一個(gè)對(duì)象捌朴。因此使用字面量方式和構(gòu)造函數(shù)創(chuàng)建的基本數(shù)據(jù)類型也有著不同
let a = 123
let b = Number(321)
a instanceof Number // false
b instanceof Number // true
當(dāng)遇到這種問題時(shí)吴攒,就可以使用constroctor
來(lái)進(jìn)行檢測(cè)
let a = 123
let b = Number(321)
console.log(a.constructor === Number)// true
console.log(b.constructor === Number)// true
但這個(gè)方法也不是最優(yōu)解,畢竟constructor
砂蔽,__proto__
等屬性是可能會(huì)被改寫的
Object.prototype.toString.call()
該方法是目前最常用也是最準(zhǔn)確的方式洼怔。
每一個(gè)對(duì)象都有著一個(gè)toString
方法,當(dāng)該方法沒有被重寫時(shí)左驾,其執(zhí)行結(jié)果返回[object type]
镣隶,其中type
表示對(duì)象的類型。而許多數(shù)據(jù)類型為了實(shí)現(xiàn)特定的功能都對(duì)其進(jìn)行了重寫诡右,比如Array
安岂,String
,Date
等帆吻,所以在進(jìn)行類型檢測(cè)時(shí)域那,都會(huì)使用Object.prototype.toString.call(object)
的方式,通過(guò)改寫其this
的綁定桅锄,來(lái)調(diào)用Object
原型上的toString
let a = [1,2,3,4]
console.log(a.toString()) // "1,2,3,4"
console.log(Object.prototype.toString.call(a)) // "[object Array]"
類型轉(zhuǎn)換
在一些情況下琉雳,數(shù)據(jù)類型之間會(huì)發(fā)生自動(dòng)轉(zhuǎn)換。比如在流程控制語(yǔ)句之中友瘤,+
性運(yùn)算符翠肘,==
運(yùn)算符等。JS也提供了一些函數(shù)方法用于主動(dòng)轉(zhuǎn)換類型辫秧。
轉(zhuǎn)為布爾類型
JS提供了Boolean()
轉(zhuǎn)型函數(shù)來(lái)進(jìn)行其他類型到布爾類型的轉(zhuǎn)變束倍。
下表為不同類數(shù)據(jù)類型與布爾類型的轉(zhuǎn)換規(guī)則:
數(shù)據(jù)類型 | 轉(zhuǎn)為true | 轉(zhuǎn)為false |
---|---|---|
Boolean | true | false |
String | 非空字符串 | 空字符串 |
Number | 非0數(shù)值 | 0、NaN |
Object | 任意對(duì)象 | null |
Undefined | N/A | undefined |
轉(zhuǎn)為數(shù)字類型
JS提供了三個(gè)函數(shù)來(lái)將非數(shù)值轉(zhuǎn)換為數(shù)值:Number()
,parseInt()
绪妹,parseFloat()
甥桂。
Number()
是主要的轉(zhuǎn)型函數(shù),可用于任何數(shù)據(jù)類型邮旷。而后兩個(gè)函數(shù)主要用于將字符串轉(zhuǎn)為數(shù)值黄选。
參數(shù)數(shù)據(jù)類型 | Number() | parseInt() | parseFLoat() |
---|---|---|---|
布爾值 | true 轉(zhuǎn)換為 1,false 轉(zhuǎn)換為 0 | 返回NaN | 返回NaN |
數(shù)值 | 直接返回 | 直接返回 | 直接返回 |
null | 返回 0 | 返回NaN | 返回NaN |
undefined | 返回 NaN | 返回 NaN | 返回 NaN |
字符串 | 只包含數(shù)值字符(前面帶正負(fù)號(hào)的數(shù)婶肩、浮點(diǎn)型办陷、有效的十六進(jìn)制)轉(zhuǎn)化成相應(yīng)的數(shù)值;空字符串返回0律歼;包含上述情況之外的字符返回NaN | 與Number()不同點(diǎn):空字符串返回NaN;只要第一個(gè)字符是數(shù)值字符民镜、加號(hào)、減號(hào)险毁,則會(huì)依次檢測(cè)每個(gè)字符(即后面包含其他字符也會(huì)返回前面的數(shù)值) | 與parseInt()基本相同制圈,但只有第一個(gè)小數(shù)點(diǎn)有效 |
對(duì)象 | 調(diào)用 valueOf()方法,并按照上述規(guī)則轉(zhuǎn)換返回的值畔况。如果轉(zhuǎn)換結(jié)果是 NaN鲸鹦,則調(diào)用 toString()方法,再按照轉(zhuǎn)換字符串的規(guī)則轉(zhuǎn)換跷跪。 | 調(diào)用 valueOf()方法亥鬓,并按照上述規(guī)則轉(zhuǎn)換返回的值。如果轉(zhuǎn)換結(jié)果是 NaN域庇,則調(diào)用 toString()方法,再按照轉(zhuǎn)換字符串的規(guī)則轉(zhuǎn)換覆积。 | 調(diào)用 valueOf()方法听皿,并按照上述規(guī)則轉(zhuǎn)換返回的值。如果轉(zhuǎn)換結(jié)果是 NaN宽档,則調(diào)用 toString()方法尉姨,再按照轉(zhuǎn)換字符串的規(guī)則轉(zhuǎn)換。 |
// 1.布爾值
Number(true) // 1
parseInt(true) // NaN
parseFloat(true) // NaN
// 2.數(shù)值
Number(123) // 123
parseInt(123) // 123
parseFloat(123.321) //123.321
// 3.null
Number(null) // 0
parseInt(null) // NaN
parseFloat(null) // NaN
// 4.undefined
Number(undefined) // NaN
parseInt(undefined) // NaN
parseFloat(undefined) // NaN
// 5.字符串
let str = '123abc'
Number(str) // NaN
parseInt(str) // 123
parseFloat(str) // 123
let str1 = ''
Number(str1) // 0
parseInt(str1) // NaN
parseFloat(str1) // NaN
// 6.對(duì)象
let a = [1,2,3]
a.valueOf() // [1,2,3]
a.toString() // "1,2,3"
Number(a) // NaN,因?yàn)榘ǘ禾?hào)
parseInt(a) // 1
parseFloat(a) // 1
Number()
和其他兩個(gè)函數(shù)最主要的區(qū)別在于處理字符串時(shí)不同吗冤。比如轉(zhuǎn)換空字符串時(shí)又厉,Number()
返回的是0,而其他兩個(gè)則返回NaN
椎瘟。而且當(dāng)?shù)谝粋€(gè)字符為數(shù)字覆致,且后面包括其他字符時(shí),Number()
則直接返回NaN
肺蔚,其他兩個(gè)則會(huì)忽略其他字符煌妈。
而因?yàn)檗D(zhuǎn)換字符串時(shí)的區(qū)別所以也間接導(dǎo)致轉(zhuǎn)換對(duì)象時(shí)的不同。
轉(zhuǎn)為字符串類型
JS提供了toString()
和String()
來(lái)將其他類型轉(zhuǎn)換為字符串。
之前我們介紹類型轉(zhuǎn)換時(shí)璧诵,已經(jīng)介紹過(guò)了toString()
汰蜘,則是每一個(gè)對(duì)象都有的方法,但可能經(jīng)過(guò)了重寫之宿。而且還有一個(gè)小操作:在對(duì)數(shù)值調(diào)用該方法時(shí)族操,可以傳入一個(gè)參數(shù)來(lái)以什么進(jìn)制來(lái)輸出數(shù)值的字符串表示。
但并不是所有對(duì)象都有toString()
比被,比如null
色难,undefined
。此時(shí)就只能使用String()
轉(zhuǎn)型函數(shù)姐赡。當(dāng)不確定一個(gè)值是否為null
或undefined
時(shí)莱预,使用該方法將會(huì)遵循以下規(guī)則:1.如果值有toString()
,則直接調(diào)用 2.如果值為null
项滑,則返回'null'
3.如果值為undefined依沮,則返回'undefined'
let value1 = 10;
let value2 = true;
let value3 = null;
let value4;
console.log(String(value1)); // "10"
console.log(String(value2)); // "true"
console.log(String(value3)); // "null"
console.log(String(value4)); // "undefined"
加性操作符
當(dāng)兩個(gè)操作數(shù)都為數(shù)值時(shí),則進(jìn)行加法運(yùn)算枪狂。
當(dāng)其中一個(gè)操作數(shù)為字符串時(shí)危喉,若另一個(gè)也為字符串則進(jìn)行拼接,否則則將另一個(gè)操作數(shù)轉(zhuǎn)換為字符串州疾,然后拼接辜限。
console.log(1 + 1) // 2
console.log(1 + '1') // 11
console.log('1' + { a:123 }) // 1[object Object]
等于與不等于操作符
當(dāng)任意一個(gè)操作數(shù)為布爾類型,則將其轉(zhuǎn)為數(shù)值再進(jìn)行比較严蓖。
當(dāng)一個(gè)操作數(shù)為字符串薄嫡,另一個(gè)為數(shù)值時(shí),則將字符串轉(zhuǎn)為數(shù)值再進(jìn)行比較颗胡。
當(dāng)一個(gè)操作數(shù)為對(duì)象毫深,另一個(gè)不是,則使用對(duì)象的valueOf()
取值毒姨,然后再進(jìn)行比較哑蔫。
如果兩個(gè)操作數(shù)都是對(duì)象,則比較它們是不是同一個(gè)對(duì)象弧呐。
null
等于undefined
闸迷,兩者都不能轉(zhuǎn)為其他類型。
當(dāng)任意一個(gè)操作數(shù)為NaN
俘枫,則相等操作符返回 false腥沽,不相等操作符返回 true。因?yàn)榧词箖蓚€(gè)操作數(shù)都是 NaN崩哩,相等操作符也返回 false巡球,因?yàn)榘凑找?guī)則言沐,NaN 不等于 NaN。
流程控制語(yǔ)句
在JS的流程控制語(yǔ)句中酣栈,比如if
险胰,while
,do-while
之中矿筝。其條件(可能是表達(dá)式或數(shù)據(jù)類型)的結(jié)果不必都為布爾類型起便,因?yàn)樵趦?nèi)部會(huì)自動(dòng)將其值轉(zhuǎn)換為布爾類型(調(diào)用Boolean()
方法)
堆棧內(nèi)存
上面我們介紹了數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,其最大的區(qū)別在于訪問方式窖维。那為什么會(huì)有這種不同呢榆综?
首先我們需要知道JS的運(yùn)行的內(nèi)存主要分為棧內(nèi)存和堆內(nèi)存。棧內(nèi)存即執(zhí)行棧铸史,是任務(wù)主要的執(zhí)行環(huán)境鼻疮。但因?yàn)闂5目臻g有限,不能用來(lái)直接保存對(duì)象琳轿,所以就使用了堆內(nèi)存用來(lái)保存真正的對(duì)象判沟,然后在棧上保存指向堆內(nèi)存中對(duì)應(yīng)的地址,該地址即為引用崭篡。
因此可知:基本數(shù)據(jù)類型是保存在棧上的挪哄,操作時(shí)是對(duì)實(shí)際的值進(jìn)行操作;而引用數(shù)據(jù)類型是保存在堆上的琉闪,對(duì)其進(jìn)行操作時(shí)實(shí)際上是通過(guò)地址(引用)來(lái)對(duì)堆內(nèi)存對(duì)應(yīng)區(qū)域內(nèi)的對(duì)象進(jìn)行操作迹炼。
其示意圖如下,關(guān)于執(zhí)行上下文的內(nèi)容將在下一篇文章進(jìn)行介紹颠毙。