前端面試梳理(一):基礎(chǔ)知識點
原始類型
涉及面試題:基礎(chǔ)數(shù)據(jù)類型有哪些损谦?null是對象?
基礎(chǔ)數(shù)據(jù)類型有哪些岳颇?
在js中基礎(chǔ)數(shù)據(jù)類型:
- String
- Number
- Boolean
- undefined
- null
- Symbol
原始數(shù)據(jù)存儲的都是值照捡,是沒有方法的,比如undefined.toString().
那為什么會存在“123”.toString呢话侧?
原因:當(dāng)你對基礎(chǔ)數(shù)值調(diào)用方法的時候栗精,發(fā)生了隱式轉(zhuǎn)化,已經(jīng)被轉(zhuǎn)化成了Number對象了。“123”.toString()
== Number("123").toString()
null是對象瞻鹏?
答案:不是悲立。
也許你會反駁到 typeof null
會輸出‘Object’。但是這是js設(shè)計之初就存在的bug新博,因為typeof 判斷對象的方式是直接變量判斷內(nèi)存的是否是以000開頭的速蕊,而null在內(nèi)存中的地址是全為0毯炮,所有才會出現(xiàn)這樣的問題。
number也是問題大戶
0.1+0.2 != 0.3 // true
1111111111111111 == 11111111111111112 // true
什么呢腥放?
先說原因,因為 JS 采用 IEEE 754 雙精度版本(64位)议泵,并且只要采用 IEEE 754 的語言都有該問題驮配。
IEEE754是怎么表示的:
s eeeeeeeeeee fff...ffff
|-1-|- 11 -|- 52 -|
意義
- 1位用來表示符號位
- 11位用來表示指數(shù)
- 52位表示尾數(shù)
那么0.1在二進(jìn)制是怎么表示?
// (0011) 表示循環(huán)
0.1 >> 0.0001 1001 1001 1001…(1001無限循環(huán))
0.2 >> 0.0011 0011 0011 0011…(0011無限循環(huán))
其實不僅僅0.1,還有0.2同诫,111111111111111111等等很多數(shù)字都是以循環(huán)體存在的粤策。這本來是沒什么問題的,但是js采用的IEEE754雙精度浮點(64)標(biāo)準(zhǔn)误窖,輸出十進(jìn)制進(jìn)行四舍五入了叮盘,但是二進(jìn)制只有 0 和 1 兩個,于是變?yōu)?0 舍 1 入霹俺。這即是計算機中部分浮點數(shù)運算時出現(xiàn)誤差柔吼。
0.100000000000000002 === 0.1 // true
0.200000000000000002 === 0.2 // true
0.1 + 0.2 === 0.30000000000000004 // true
但是你可能又會問:console.log("0.1") 為什么不會輸出 “0.100000000000000002”?
因為在輸入內(nèi)容的時候,二進(jìn)制被轉(zhuǎn)換為了十進(jìn)制丙唧,十進(jìn)制又被轉(zhuǎn)換為了字符串愈魏,在這個轉(zhuǎn)換的過程中發(fā)生了取近似值的過程.
// 所以輸出 0.1
console.log(0.100000000000000002) // 0.1
那么如何處理這個問題呢?
// 小數(shù)用toFixed
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
// 大整數(shù)用BigInt
BigInt("11111111111111111") + 0n // 11111111111111111n
對象類型
涉及面試題:對象類型和基礎(chǔ)類型的不同?函數(shù)參數(shù)是對象會發(fā)生什么問題培漏?
對象類型和基礎(chǔ)類型的區(qū)別
-
內(nèi)存的分配不同
- 基本數(shù)據(jù)類型存儲在棧中溪厘。
- 復(fù)雜數(shù)據(jù)類型存儲在堆中,棧中存儲的變量牌柄,是指向堆中的引用地址畸悬。
-
訪問機制不同
- 基本數(shù)據(jù)類型是按值訪問
- 復(fù)雜數(shù)據(jù)類型按引用訪問,JS不允許直接訪問保存在堆內(nèi)存中的對象珊佣,在訪問一個對象時蹋宦,首先得到的是這個對象在棧內(nèi)存中的地址,然后再按照這個地址去獲得這個對象中的值咒锻。
-
復(fù)制變量時不同(a=b)
- 基本數(shù)據(jù)類型:a=b;是將b中保存的原始值的副本賦值給新變量a冷冗,a和b完全獨立,互不影響
- 復(fù)雜數(shù)據(jù)類型:a=b;將b保存的對象內(nèi)存的引用地址賦值給了新變量a;a和b指向了同一個堆內(nèi)存地址惑艇,其中一個值發(fā)生了改變贾惦,另一個也會改變。
下面的輸出結(jié)果:
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
a.x // 輸出敦捧?
b.x // 輸出
答案: a.x // --> undefined
b.x // --> {n: 2}
- 1须板、優(yōu)先級。.的優(yōu)先級高于=兢卵,所以先執(zhí)行a.x习瑰,堆內(nèi)存中的{n: 1}就會變成{n: 1, x: undefined},改變之后相應(yīng)的b.x也變化了秽荤,因為指向的是同一個對象甜奄。
- 2、賦值操作是從右到左窃款,所以先執(zhí)行a = {n: 2}课兄,a的引用就被改變了,然后這個返回值又賦值給了a.x晨继,需要注意的是這時候a.x是第一步中的{n: 1, x: undefined}那個對象烟阐,其實就是b.x,相當(dāng)于b.x = {n: 2}
typeof vs instanceof
涉及面試題:typeof 是否能正確判斷類型紊扬?instanceof 能正確判斷對象的原理是什么蜒茄?
- typeof 對于基礎(chǔ)類型來說,除了 null 都可以顯示正確的類型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeOf對于對象類型就無法起到作用了:
typeof {} // object
typeof [] // object
typeof String // function
- instanceof 可以正確的判斷對象的類型餐屎,因為內(nèi)部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype
function myInstanceof(left, right) {
let prototype = right.prototype
left = left.__proto__
while (true) {
if (left === null || left === undefined)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
原理:
- 首先獲取類型的原型
- 然后獲得對象的隱式原型
- 然后一直循環(huán)判斷對象的隱式原型是否等于類型的原型檀葛,直到對象隱式原型為 null,因為原型鏈最終為 null
那么如何靠譜的區(qū)分對象呢腹缩?
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) //"[object Object]"
Object.prototype.toString.call(/\./) // "[object RegExp]"
類型轉(zhuǎn)換
涉及面試題:該知識點常在筆試題
常見的隱式轉(zhuǎn)化有三種:
- 轉(zhuǎn)化為String
- 轉(zhuǎn)化為Number
- 裝化為Boolean
轉(zhuǎn)Boolean:
undefined屿聋,null空扎,“”,0润讥,+0勺卢,-0,NaN轉(zhuǎn)化為發(fā)垃圾象对,其余都是true
對象轉(zhuǎn)原始類型
- 1.對象在轉(zhuǎn)換類型的時候,會調(diào)用內(nèi)置的 [[ToPrimitive]] 函數(shù)宴抚,對于該函數(shù)來說勒魔,算法邏輯一般來說如下:
- 2.是否為原始類型了,如果是就無需轉(zhuǎn)化菇曲,如果不是就往下
- 3.調(diào)用valueOf()冠绢,如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值常潮,如果不是就往下
- 4.調(diào)用 x.toString()弟胀,如果轉(zhuǎn)換為基礎(chǔ)類型,就返回轉(zhuǎn)換的值喊式,如果不是就往下
- 5.如果都沒有返回原始類型孵户,就會報錯
當(dāng)然你也可以重寫 Symbol.toPrimitive ,該方法在轉(zhuǎn)原始類型時調(diào)用優(yōu)先級最高岔留。
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
[Symbol.toPrimitive]() {
return 2
}
}
1 + a // => 3
常見面試題:
a==1 && a==2 && a==3 ?
// 1.利用隱式類型轉(zhuǎn)換
const a = {
[Symbol.toPrimitive]: (() => {
let i =1;
return () => i++;
})()
}
const a = {
i: 1,
valueOf: () => {
return a.i++;
}
}
// 2.利用數(shù)據(jù)劫持(Proxy/Object.defineProperty)
let i = 1;
let a = new Proxy({}, {
i:1,
get: function () {
return () => this++
}
})
// 使用數(shù)組夏哭, 數(shù)組的同toString默認(rèn)調(diào)用的join
let a = [1,2,3]
a.join = a.shift
四則運算符轉(zhuǎn)化
- 運算中其中一方為字符串,那么就會把另一方也轉(zhuǎn)換為字符串
- 如果一方不是字符串或者數(shù)字献联,那么會將它轉(zhuǎn)換為數(shù)字或者字符串
- ```js
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
```
-
面試題:'a' + + 'b' // 輸出什么
因為 + 'b' 等于 NaN竖配,所以結(jié)果為 "aNaN",你可能也會在一些代碼中看到過 + '1' 的形式來快速獲取 number 類型里逆。
或者轉(zhuǎn)化為 “a” + (+“b”)
"a" + + "1" => "a1"
那么對于除了加法的運算符來說,只要其中以一方為數(shù)字进胯,那么另一方也會被轉(zhuǎn)化成數(shù)字。
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
比較運算符
- 如果是對象原押,就通過 toPrimitive 轉(zhuǎn)換對象
- 如果是字符串胁镐,就通過 unicode 字符索引來比較
let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true'