這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. JavaScript有哪些數據類型,它們的區別? JavaScript共有八種數據類型,分別是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 其中 Symbol ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
1. JavaScript有哪些數據類型,它們的區別?
JavaScript共有八種數據類型,分別是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的數據類型:
- Symbol 代表創建後獨一無二且不可變的數據類型,它主要是為瞭解決可能出現的全局變數衝突的問題。
- BigInt 是一種數字類型的數據,它可以表示任意精度格式的整數,使用 BigInt 可以安全地存儲和操作大整數,即使這個數已經超出了 Number 能夠表示的安全整數範圍。
這些數據可以分為原始數據類型和引用數據類型:
- 棧:原始數據類型(Undefined、Null、Boolean、Number、String)
- 堆:引用數據類型(對象、數組和函數)
兩種類型的區別在於存儲位置的不同:
- 原始數據類型直接存儲在棧(stack)中的簡單數據段,占據空間小、大小固定,屬於被頻繁使用數據,所以放入棧中存儲;
- 引用數據類型存儲在堆(heap)中的對象,占據空間大、大小不固定。如果存儲在棧中,將會影響程式運行的性能;引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。
堆和棧的概念存在於數據結構和操作系統記憶體中,在數據結構中:
- 在數據結構中,棧中數據的存取方式為先進後出。
- 堆是一個優先隊列,是按優先順序來進行排序的,優先順序可以按照大小來規定。
在操作系統中,記憶體被分為棧區和堆區:
- 棧區記憶體由編譯器自動分配釋放,存放函數的參數值,局部變數的值等。其操作方式類似於數據結構中的棧。
- 堆區記憶體一般由開發著分配釋放,若開發者不釋放,程式結束時可能由垃圾回收機制回收。
2. 數據類型檢測的方式有哪些
(1)typeof
console.log(typeof 2); // number console.log(typeof true); // boolean console.log(typeof 'str'); // string console.log(typeof []); // object console.log(typeof function(){}); // function console.log(typeof {}); // object console.log(typeof undefined); // undefined console.log(typeof null); // object
其中數組、對象、null都會被判斷為object,其他判斷都正確。
(2)instanceof
instanceof
可以正確判斷對象的類型,其內部運行機制是判斷在其原型鏈中能否找到該類型的原型。
console.log(2 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true
可以看到,instanceof
只能正確判斷引用數據類型,而不能判斷基本數據類型。instanceof
運算符可以用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype
屬性。
(3) constructor
console.log((2).constructor === Number); // true console.log((true).constructor === Boolean); // true console.log(('str').constructor === String); // true console.log(([]).constructor === Array); // true console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true
constructor
有兩個作用,一是判斷數據的類型,二是對象實例通過 constrcutor
對象訪問它的構造函數。需要註意,如果創建一個對象來改變它的原型,constructor
就不能用來判斷數據類型了:
function Fn(){}; Fn.prototype = new Array(); var f = new Fn(); console.log(f.constructor===Fn); // false console.log(f.constructor===Array); // true
(4)Object.prototype.toString.call()
Object.prototype.toString.call()
使用 Object 對象的原型方法 toString 來判斷數據類型:
var a = Object.prototype.toString; console.log(a.call(2)); console.log(a.call(true)); console.log(a.call('str')); console.log(a.call([])); console.log(a.call(function(){})); console.log(a.call({})); console.log(a.call(undefined)); console.log(a.call(null));
同樣是檢測對象obj調用toString方法,obj.toString()的結果和Object.prototype.toString.call(obj)的結果不一樣,這是為什麼?
這是因為toString是Object的原型方法,而Array、function等類型作為Object的實例,都重寫了toString方法。不同的對象類型調用toString方法時,根據原型鏈的知識,調用的是對應的重寫之後的toString方法(function類型返回內容為函數體的字元串,Array類型返回元素組成的字元串…),而不會去調用Object上原型toString方法(返回對象的具體類型),所以採用obj.toString()不能得到其對象類型,只能將obj轉換為字元串類型;因此,在想要得到對象的具體類型時,應該調用Object原型上的toString方法。
3. 判斷數組的方式有哪些
- 通過Object.prototype.toString.call()做判斷
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
- 通過原型鏈做判斷
obj.__proto__ === Array.prototype;
- 通過ES6的Array.isArray()做判斷
Array.isArrray(obj);
- 通過instanceof做判斷
obj instanceof Array
- 通過Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
4. null和undefined區別
首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。
undefined 代表的含義是未定義,null 代表的含義是空對象。一般變數聲明瞭但還沒有定義的時候會返回 undefined,null主要用於賦值給一些可能會返回對象的變數,作為初始化。
undefined 在 JavaScript 中不是一個保留字,這意味著可以使用 undefined 來作為一個變數名,但是這樣的做法是非常危險的,它會影響對 undefined 值的判斷。我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。
當對這兩種類型使用 typeof 進行判斷時,Null 類型化會返回 “object”,這是一個歷史遺留的問題。當使用雙等號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。
5. typeof null 的結果是什麼,為什麼?
typeof null 的結果是Object。
在 JavaScript 第一個版本中,所有值都存儲在 32 位的單元中,每個單元包含一個小的 類型標簽(1-3 bits) 以及當前要存儲值的真實數據。類型標簽存儲在每個單元的低位中,共有五種數據類型:
000: object - 當前存儲的數據指向一個對象。 1: int - 當前存儲的數據是一個 31 位的有符號整數。 010: double - 當前存儲的數據指向一個雙精度的浮點數。 100: string - 當前存儲的數據指向一個字元串。 110: boolean - 當前存儲的數據是布爾值。
如果最低位是 1,則類型標簽標誌位的長度只有一位;如果最低位是 0,則類型標簽標誌位的長度占三位,為存儲其他四種數據類型提供了額外兩個 bit 的長度。
有兩種特殊數據類型:
- undefined的值是 (-2)30(一個超出整數範圍的數字);
- null 的值是機器碼 NULL 指針(null 指針的值全是 0)
那也就是說null的類型標簽也是000,和Object的類型標簽一樣,所以會被判定為Object。
6. intanceof 操作符的實現原理及實現
instanceof 運算符用於判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。
function myInstanceof(left, right) { // 獲取對象的原型 let proto = Object.getPrototypeOf(left) // 獲取構造函數的 prototype 對象 let prototype = right.prototype; // 判斷構造函數的 prototype 對象是否在對象的原型鏈上 while (true) { if (!proto) return false; if (proto === prototype) return true; // 如果沒有找到,就繼續從其原型上找,Object.getPrototypeOf方法用來獲取指定對象的原型 proto = Object.getPrototypeOf(proto); } }
7. 為什麼0.1+0.2 ! == 0.3,如何讓其相等
在開發過程中遇到類似這樣的問題:
let n1 = 0.1, n2 = 0.2 console.log(n1 + n2) // 0.30000000000000004
這裡得到的不是想要的結果,要想等於0.3,就要把它進行轉化:
(n1 + n2).toFixed(2) // 註意,toFixed為四捨五入
toFixed(num)
方法可把 Number 四捨五入為指定小數位數的數字。那為什麼會出現這樣的結果呢?
電腦是通過二進位的方式存儲數據的,所以電腦計算0.1+0.2的時候,實際上是計算的兩個數的二進位的和。0.1的二進位是0.0001100110011001100...
(1100迴圈),0.2的二進位是:0.00110011001100...
(1100迴圈),這兩個數的二進位都是無限迴圈的數。那JavaScript是如何處理無限迴圈的二進位小數呢?
一般我們認為數字包括整數和小數,但是在 JavaScript 中只有一種數字類型:Number,它的實現遵循IEEE 754標準,使用64位固定長度來表示,也就是標準的double雙精度浮點數。在二進位科學表示法中,雙精度浮點數的小數部分最多只能保留52位,再加上前面的1,其實就是保留53位有效數字,剩餘的需要捨去,遵從“0舍1入”的原則。
根據這個原則,0.1和0.2的二進位數相加,再轉化為十進位數就是:0.30000000000000004
。
下麵看一下雙精度數是如何保存
- 第一部分(藍色):用來存儲符號位(sign),用來區分正負數,0表示正數,占用1位
- 第二部分(綠色):用來存儲指數(exponent),占用11位
- 第三部分(紅色):用來存儲小數(fraction),占用52位
對於0.1,它的二進位為:
0.00011001100110011001100110011001100110011001100110011001 10011...
轉為科學計數法(科學計數法的結果就是浮點數):
1.1001100110011001100110011001100110011001100110011001*2^-4
可以看出0.1的符號位為0,指數位為-4,小數位為:
1001100110011001100110011001100110011001100110011001
那麼問題又來了,指數位是負數,該如何保存呢?
IEEE標準規定了一個偏移量,對於指數部分,每次都加這個偏移量進行保存,這樣即使指數是負數,那麼加上這個偏移量也就是正數了。由於JavaScript的數字是雙精度數,這裡就以雙精度數為例,它的指數部分為11位,能表示的範圍就是0~2047,IEEE固定雙精度數的偏移量為1023。
- 當指數位不全是0也不全是1時(規格化的數值),IEEE規定,階碼計算公式為 e-Bias。 此時e最小值是1,則1-1023= -1022,e最大值是2046,則2046-1023=1023,可以看到,這種情況下取值範圍是
-1022~1013
。 - 當指數位全部是0的時候(非規格化的數值),IEEE規定,階碼的計算公式為1-Bias,即1-1023= -1022。
- 當指數位全部是1的時候(特殊值),IEEE規定這個浮點數可用來表示3個特殊值,分別是正無窮,負無窮,NaN。 具體的,小數位不為0的時候表示NaN;小數位為0時,當符號位s=0時表示正無窮,s=1時候表示負無窮。
對於上面的0.1的指數位為-4,-4+1023 = 1019 轉化為二進位就是:1111111011
.
所以,0.1表示為:
0 1111111011 1001100110011001100110011001100110011001100110011001
說了這麼多,是時候該最開始的問題了,如何實現0.1+0.2=0.3呢?
對於這個問題,一個直接的解決方法就是設置一個誤差範圍,通常稱為“機器精度”。對JavaScript來說,這個值通常為2-52,在ES6中,提供了Number.EPSILON
屬性,而它的值就是2-52,只要判斷0.1+0.2-0.3
是否小於Number.EPSILON
,如果小於,就可以判斷為0.1+0.2 ===0.3
function numberepsilon(arg1,arg2){ return Math.abs(arg1 - arg2) < Number.EPSILON; } console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
8. 如何獲取安全的 undefined 值?
因為 undefined 是一個標識符,所以可以被當作變數來使用和賦值,但是這樣會影響 undefined 的正常判斷。表達式 void ___ 沒有返回值,因此返回結果是 undefined。void 並不改變表達式的結果,只是讓表達式不返回值。因此可以用 void 0 來獲得 undefined。
9. typeof NaN 的結果是什麼?
NaN 指“不是一個數字”(not a number),NaN 是一個“警戒值”(sentinel value,有特殊用途的常規值),用於指出數字類型中的錯誤情況,即“執行數學運算沒有成功,這是失敗後返回的結果”。
typeof NaN; // "number"
NaN 是一個特殊值,它和自身不相等,是唯一一個非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 為 true。
10. isNaN 和 Number.isNaN 函數的區別?
- 函數 isNaN 接收參數後,會嘗試將這個參數轉換為數值,任何不能被轉換為數值的的值都會返回 true,因此非數字值傳入也會返回 true ,會影響 NaN 的判斷。
- 函數 Number.isNaN 會首先判斷傳入參數是否為數字,如果是數字再繼續判斷是否為 NaN ,不會進行數據類型的轉換,這種方法對於 NaN 的判斷更為準確。
11. == 操作符的強制類型轉換規則?
對於 ==
來說,如果對比雙方的類型不一樣,就會進行類型轉換。假如對比 x
和 y
是否相同,就會進行如下判斷流程:
- 首先會判斷兩者類型是否相同,相同的話就比較兩者的大小;
- 類型不相同的話,就會進行類型轉換;
- 會先判斷是否在對比
null
和undefined
,是的話就會返回true
- 判斷兩者類型是否為
string
和number
,是的話就會將字元串轉換為number
1 == '1' ↓ 1 == 1
- 判斷其中一方是否為
boolean
,是的話就會把boolean
轉為number
再進行判斷
'1' == true ↓ '1' == 1 ↓ 1 == 1
- 判斷其中一方是否為
object
且另一方為string
、number
或者symbol
,是的話就會把object
轉為原始類型再進行判斷
'1' == { name: 'js' } ↓ '1' == '[object Object]'
其流程圖如下:
12. 其他值到字元串的轉換規則?
- Null 和 Undefined 類型 ,null 轉換為 "null",undefined 轉換為 "undefined",
- Boolean 類型,true 轉換為 "true",false 轉換為 "false"。
- Number 類型的值直接轉換,不過那些極小和極大的數字會使用指數形式。
- Symbol 類型的值直接轉換,但是只允許顯式強制類型轉換,使用隱式強制類型轉換會產生錯誤。
- 對普通對象來說,除非自行定義 toString() 方法,否則會調用 toString()(Object.prototype.toString())來返回內部屬性 [[Class]] 的值,如"[object Object]"。如果對象有自己的 toString() 方法,字元串化時就會調用該方法並使用其返回值。
13. 其他值到數字值的轉換規則?
- Undefined 類型的值轉換為 NaN。
- Null 類型的值轉換為 0。
- Boolean 類型的值,true 轉換為 1,false 轉換為 0。
- String 類型的值轉換如同使用 Number() 函數進行轉換,如果包含非數字值則轉換為 NaN,空字元串為 0。
- Symbol 類型的值不能轉換為數字,會報錯。
- 對象(包括數組)會首先被轉換為相應的基本類型值,如果返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換為數字。
為了將值轉換為相應的基本類型值,抽象操作 ToPrimitive 會首先(通過內部操作 DefaultValue)檢查該值是否有valueOf()方法。如果有並且返回基本類型值,就使用該值進行強制類型轉換。如果沒有就使用 toString() 的返回值(如果存在)來進行強制類型轉換。
如果 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
14. 其他值到布爾類型的值的轉換規則?
以下這些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• ""
假值的布爾強制類型轉換結果為 false。從邏輯上說,假值列表以外的都應該是真值。
15. || 和 && 操作符的返回值?
|| 和 && 首先會對第一個操作數執行條件判斷,如果其不是布爾值就先強制轉換為布爾類型,然後再執行條件判斷。
- 對於 || 來說,如果條件判斷結果為 true 就返回第一個操作數的值,如果為 false 就返回第二個操作數的值。
- && 則相反,如果條件判斷結果為 true 就返回第二個操作數的值,如果為 false 就返回第一個操作數的值。
|| 和 && 返回它們其中一個操作數的值,而非條件判斷的結果
16. Object.is() 與比較操作符 “===”、“==” 的區別?
- 使用雙等號(==)進行相等判斷時,如果兩邊的類型不一致,則會進行強制類型轉化後再進行比較。
- 使用三等號(===)進行相等判斷時,如果兩邊的類型不一致時,不會做強制類型準換,直接返回 false。
- 使用 Object.is 來進行相等判斷時,一般情況下和三等號的判斷相同,它處理了一些特殊的情況,比如 -0 和 +0 不再相等,兩個 NaN 是相等的。
17. 什麼是 JavaScript 中的包裝類型?
在 JavaScript 中,基本類型是沒有屬性和方法的,但是為了便於操作基本類型的值,在調用基本類型的屬性或方法時 JavaScript 會在後臺隱式地將基本類型的值轉換為對象,如:
const a = "abc"; a.length; // 3 a.toUpperCase(); // "ABC"
在訪問'abc'.length
時,JavaScript 將'abc'
在後臺轉換成String('abc')
,然後再訪問其length
屬性。
JavaScript也可以使用Object
函數顯式地將基本類型轉換為包裝類型:
var a = 'abc' Object(a) // String {"abc"}
也可以使用valueOf
方法將包裝類型倒轉成基本類型:
var a = 'abc' var b = Object(a) var c = b.valueOf() // 'abc'
看看如下代碼會列印出什麼:
var a = new Boolean( false ); if (!a) { console.log( "Oops" ); // never runs }
答案是什麼都不會列印,因為雖然包裹的基本類型是false
,但是false
被包裹成包裝類型後就成了對象,所以其非值為false
,所以迴圈體中的內容不會運行。
18. JavaScript 中如何進行隱式類型轉換?
首先要介紹ToPrimitive
方法,這是 JavaScript 中每個值隱含的自帶的方法,用來將值 (無論是基本類型值還是對象)轉換為基本類型值。如果值為基本類型,則直接返回值本身;如果值為對象,其看起來大概是這樣:
/** * @obj 需要轉換的對象 * @type 期望的結果類型 */ ToPrimitive(obj,type)
type
的值為number
或者string
。
(1)當type
為number
時規則如下:
- 調用
obj
的valueOf
方法,如果為原始值,則返回,否則下一步; - 調用
obj
的toString
方法,後續同上; - 拋出
TypeError
異常。
(2)當type
為string
時規則如下:
- 調用
obj
的toString
方法,如果為原始值,則返回,否則下一步; - 調用
obj
的valueOf
方法,後續同上; - 拋出
TypeError
異常。
可以看出兩者的主要區別在於調用toString
和valueOf
的先後順序。預設情況下:
- 如果對象為 Date 對象,則
type
預設為string
; - 其他情況下,
type
預設為number
。
總結上面的規則,對於 Date 以外的對象,轉換為基本類型的大概規則可以概括為一個函數:
var objToNumber = value => Number(value.valueOf().toString()) objToNumber([]) === 0 objToNumber({}) === NaN
而 JavaScript 中的隱式類型轉換主要發生在+、-、*、/
以及==、>、<
這些運算符之間。而這些運算符只能操作基本類型值,所以在進行這些運算前的第一步就是將兩邊的值用ToPrimitive
轉換成基本類型,再進行操作。
以下是基本類型的值在不同操作符的情況下隱式轉換的規則 (對於對象,其會被ToPrimitive
轉換成基本類型,所以最終還是要應用基本類型轉換規則):
+
操作符+
操作符的兩邊有至少一個string
類型變數時,兩邊的變數都會被隱式轉換為字元串;其他情況下兩邊的變數都會被轉換為數字。
1 + '23' // '123' 1 + false // 1 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number '1' + false // '1false' false + true // 1
-
、*
、\
操作符NaN
也是一個數字
1 * '23' // 23 1 * false // 0 1 / 'aa' // NaN
- 對於
==
操作符
操作符兩邊的值都儘量轉成number
:
3 == true // false, 3 轉為number為3,true轉為number為1 '0' == false //true, '0'轉為number為0,false轉為number為0 '0' == 0 // '0'轉為number為0
- 對於
<
和>
比較符
如果兩邊都是字元串,則比較字母表順序:
'ca' < 'bd' // false 'a' < 'b' // true
其他情況下,轉換為數字再比較:
'12' < 13 // true false > -1 // true
以上說的是基本類型的隱式轉換,而對象會被ToPrimitive
轉換為基本類型再進行轉換:
var a = {} a > 2 // false
其對比過程如下:
a.valueOf() // {}, 上面提到過,ToPrimitive預設type為number,所以先valueOf,結果還是個對象,下一步 a.toString() // "[object Object]",現在是一個字元串了 Number(a.toString()) // NaN,根據上面 < 和 > 操作符的規則,要轉換成數字 NaN > 2 //false,得出比較結果
又比如:
var a = {name:'Jack'} var b = {age: 18} a + b // "[object Object][object Object]"
運算過程如下:
a.valueOf() // {},上面提到過,ToPrimitive預設type為number,所以先valueOf,結果還是個對象,下一步 a.toString() // "[object Object]" b.valueOf() // 同理 b.toString() // "[object Object]" a + b // "[object Object][object Object]"
19. +
操作符什麼時候用於字元串的拼接?
根據 ES5 規範,如果某個操作數是字元串或者能夠通過以下步驟轉換為字元串的話,+ 將進行拼接操作。如果其中一個操作數是對象(包括數組),則首先對其調用 ToPrimitive 抽象操作,該抽象操作再調用 [[DefaultValue]],以數字作為上下文。如果不能轉換為字元串,則會將其轉換為數字類型來進行計算。
簡單來說就是,如果 + 的其中一個操作數是字元串(或者通過以上步驟最終得到字元串),則執行字元串拼接,否則執行數字加法。
那麼對於除了加法的運算符來說,只要其中一方是數字,那麼另一方就會被轉為數字。
20. 為什麼會有BigInt的提案?
JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全數字,計算結果是9007199254740991,即在這個數範圍內不會出現精度丟失(⼩數除外)。但是⼀旦超過這個範圍,js就會出現計算不准確的情況,這在⼤數計算的時候不得不依靠⼀些第三⽅庫進⾏解決,因此官⽅提出了BigInt來解決此問題。
21. object.assign和擴展運演算法是深拷貝還是淺拷貝,兩者區別
擴展運算符:
let outObj = { inObj: {a: 1, b: 2} } let newObj = {...outObj} newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}}
Object.assign():
let outObj = { inObj: {a: 1, b: 2} } let newObj = Object.assign({}, outObj) newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}}
可以看到,兩者都是淺拷貝。
- Object.assign()方法接收的第一個參數作為目標對象,後面的所有參數作為源對象。然後把所有的源對象合併到目標對象中。它會修改了一個對象,因此會觸發 ES6 setter。
- 擴展操作符(…)使用它時,數組或對象中的每一個值都會被拷貝到一個新的數組或對象中。它不複製繼承的屬性或類的屬性,但是它會複製ES6的 symbols 屬性。
22. 如何判斷一個對象是空對象
- 使用JSON自帶的.stringify方法來判斷:
if(Json.stringify(Obj) == '{}' ){ console.log('空對象'); }
- 使用ES6新增的方法Object.keys()來判斷:
if(Object.keys(Obj).length < 0){ console.log('空對象'); }