原生JavaScript靈魂拷問,你能答上多少
點(diǎn)擊上方?程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
前言
當(dāng)下的前端開發(fā),三大框架三分天下,框架的簡(jiǎn)單、強(qiáng)大讓我們欲罷不能,使用原生 JavaScript 越來越少。
但我認(rèn)為 JavaScript 作為每一個(gè)前端工程師的立身之本,不止要學(xué)會(huì),還要學(xué)好、學(xué)精,學(xué)再多遍都不為過。
另一方面,前端面試中,越來越重視原生 JavaScript 的考察,其所占比例也越來越高。
我抓取了??蜕辖衲甑木€上面試題和面經(jīng),大約 500 左右道題,原生 JavaScript 的難點(diǎn)(閉包,eventLoop,this,手撕原生JS)考察的頻率非常高。
完整的分析我還正在趕工中,希望大家到時(shí)候可以來支持一下。
因此我決定整理JavaScript中容易忽視或者混淆的知識(shí)點(diǎn),寫一系列篇文章,以靈魂拷問的方式,系統(tǒng)且完整的帶大家遨游原生 JavaScript 的世界,希望能給大家?guī)硪恍┦斋@。
JS類型之問——概念與檢測(cè)篇
1.JS中的數(shù)據(jù)類型有哪些?
基本數(shù)據(jù)類型:共有7種
Boolean?Number?String?undefined?null?Bigint?Symbol
復(fù)制代碼
Symbol :ES6 引入的一種新的原始值,表示獨(dú)一無二的值,主要為了解決屬性名沖突問題。
Bigint :ES2020 新增加,是比 Number 類型的整數(shù)范圍更大。
引用數(shù)據(jù)類型:1種
Object對(duì)象(包括普通Object、Function、Array、Date、RegExp、Math)
復(fù)制代碼
2.你真的懂typeof嗎?
typeof的作用?區(qū)分?jǐn)?shù)據(jù)類型,可以返回7種數(shù)據(jù)類型:
number、string、boolean、undefined、object、function,以及ES6新增的symboltypeof能正確區(qū)分?jǐn)?shù)據(jù)類型嗎?不能。對(duì)于原始類型,除
null都可以正確判斷;對(duì)于引用類型,除function外,都會(huì)返回"object"typeof注意事項(xiàng)typeof返回值為string格式,注意類似這種考題:typeof(typeof(undefined)) -> "string"typeof未定義的變量不會(huì)報(bào)錯(cuò),返回"undefiend"typeof(null) -> "object": 遺留已久的bugtypeof無法區(qū)別數(shù)組與普通對(duì)象:typeof([]) -> "object"typeof(NaN) -> "number"習(xí)題
console.log(typeof(b));
console.log(typeof(undefined));?
console.log(typeof(NaN));?
console.log(typeof(null));?
var?a?=?'123abc';?
console.log(typeof(+a));?
console.log(typeof(!!a));?
console.log(typeof(a?+?""));?
console.log(typeof(typeof(null)));
console.log(typeof(typeof({})));
復(fù)制代碼
答案
undefined?//?b未定義,返回undefined
undefined
number?//?NaN?為number類型
object
number?//?+a?類型轉(zhuǎn)換為NaN
boolean
string
string?//?typeof(null)?->?"object";?typeof("object")?->?"string"
string
復(fù)制代碼
3.什么是instanceof?你能模擬實(shí)現(xiàn)一個(gè)instanceof嗎?
`instanceof` 判斷對(duì)象的原型鏈上是否存在構(gòu)造函數(shù)的原型。只能判斷引用類型。`instanceof` 常用來判斷 `A` 是否為 `B` 的實(shí)例
//?A是B的實(shí)例,返回true,否則返回false
//?判斷A的原型鏈上是否有B的原型
A?instaceof?B
復(fù)制代碼
模擬實(shí)現(xiàn) instanceof
思想:沿原型鏈往上查找
function?instance_of(Case,?Constructor)?{
????//?基本數(shù)據(jù)類型返回false
????//?兼容一下函數(shù)對(duì)象
????if?((typeof(Case)?!=?'object'?&&?typeof(Case)?!=?'function')?||?Case?==?'null')?return?false;
????let?CaseProto?=?Object.getPrototypeOf(Case);
????while?(true)?{
????????//?查到原型鏈頂端,仍未查到,返回false
????????if?(CaseProto?==?null)?return?false;
????????//?找到相同的原型
????????if?(CaseProto?===?Constructor.prototype)?return?true;
????????CaseProto?=?Object.getPrototypeOf(CaseProto);
????}
}
復(fù)制代碼
測(cè)試:
console.log(instance_of(Array,?Object))?//?true
function?User(name){
????this.name?=?name;
}
const?user?=?new?User('zc');
const?vipUser?=?Object.create(user);
console.log(instance_of(vipUser,?User))?//?true
復(fù)制代碼
4.如何區(qū)分?jǐn)?shù)組與對(duì)象?使用instanceof判斷數(shù)組可靠嗎?
`ES6` 提供的新方法 `Array.isArray()`如果不存在`Array.isArray()`呢?可以借助`Object.prototype.toString.call()` 進(jìn)行判斷,此方式兼容性最好
if?(!Array.isArray)?{
????Array.isArray?=?function(o)?{
????????return?typeof(o)?===?'object'?
???????????????&&?Object.prototype.toString.call(o)?===?'[object?Array]';
????}
}
復(fù)制代碼
instanceof判斷
判斷方式
//?如果為true,則arr為數(shù)組
arr?instanceof?Array
復(fù)制代碼
instanceof 判斷數(shù)組類型如此之簡(jiǎn)單,為何不推薦使用那?
instanceof 操作符的問題在于,如果網(wǎng)頁(yè)中存在多個(gè) iframe ,那便會(huì)存在多個(gè) Array 構(gòu)造函數(shù),此時(shí)判斷是否是數(shù)組會(huì)存在問題。
更詳細(xì)的內(nèi)容可以參考博文:JavaScript為啥不用instanceof檢測(cè)數(shù)組[4]
5.如何判斷一個(gè)數(shù)是否為NaN?
NaN 有個(gè)非常特殊的特性, NaN 與任何值都不相等,包括它自身
NaN?===?NaN?//?false
NaN?==?NaN?//?false
復(fù)制代碼
鑒于這個(gè)獨(dú)特的特性,可以手撕一個(gè)比較簡(jiǎn)單的判斷函數(shù)
function?isNaN(x)?{
????return?x?!=?x;
}
復(fù)制代碼
全局函數(shù) isNaN方法:不推薦使用。MDN對(duì)它的介紹是:isNaN函數(shù)內(nèi)包含一些非常有趣的規(guī)則。
但為了避免一些面試官出一些冷門題目,咱們來稍微了解一下 isNaN 的有趣機(jī)制:會(huì)先判斷參數(shù)是不是 Number 類型,如果不是 Number 類型會(huì)嘗試將這個(gè)參數(shù)轉(zhuǎn)換為 Number 類型,之后再去判斷是不是 NaN 。
舉個(gè)例子:
//?為什么對(duì)象會(huì)帶來三種不同的結(jié)果
//?是不是很有趣
//?具體原因可以參考類型轉(zhuǎn)換篇
console.log(isNaN([]))?//?false
console.log(isNaN([1]))?//?false
console.log(isNaN([1,?2]))?//?true?
console.log(isNaN(null))?//?false
console.log(isNaN(undefined))?//?true
復(fù)制代碼
isNaN 的結(jié)果很大程度上取決于 Number() 類型轉(zhuǎn)換的結(jié)果,關(guān)于 Number 的轉(zhuǎn)換結(jié)果,后面會(huì)專門有一部分來介紹。
Number.isNaN(推薦使用)
與 isNaN() 相比,Number.isNaN() 不會(huì)自行將參數(shù)轉(zhuǎn)換成數(shù)字,只有在參數(shù)是值為 NaN 的數(shù)字時(shí),才會(huì)返回 true。
6.如何實(shí)現(xiàn)一個(gè)功能完善的類型判斷函數(shù)?
Object.prototype.toString.call([value]) ,可以精準(zhǔn)判斷數(shù)據(jù)類型,因此可以根據(jù)這個(gè)原理封裝一個(gè)自己的 type 方法。
toString.call(()=>{})???????//?[object?Function]
toString.call({})???????????//?[object?Object]
toString.call([])???????????//?[object?Array]
toString.call('')???????????//?[object?String]
toString.call(22)???????????//?[object?Number]
toString.call(undefined)????//?[object?undefined]
toString.call(null)?????????//?[object?null]
toString.call(new?Date)?????//?[object?Date]
toString.call(Math)?????????//?[object?Math]
toString.call(window)???????//?[object?Window]
復(fù)制代碼
JS類型之問——類型轉(zhuǎn)換篇
7.toString 和 valueOf 方法有什么區(qū)別?
基礎(chǔ):這兩個(gè)方法屬于 Object對(duì)象,是為了解決JavaScript值運(yùn)算與顯示的問題。為了更適合自身功能,很多JavaScript內(nèi)置對(duì)象都重寫了這兩個(gè)方法。toString(): 返回當(dāng)前對(duì)象的字符串形式;valueOf(): 返回該對(duì)象的原始值各個(gè)類型下兩個(gè)方法返回值情況對(duì)比
| 類型 | valueOf | toString |
|---|---|---|
| Array[1,2,3] | 數(shù)組本身[1, 2, 3] | 1,2,3 |
| Object | 對(duì)象本身 | [object Object] |
| Boolean類型 | Boolean值 | "true"或"false" |
| Function | 函數(shù)本身 | function fnName(){code} |
| Number | 數(shù)值 | 數(shù)值的字符換表示 |
| Date | 毫米格式時(shí)間戳 | GMT格式時(shí)間字符串 |
調(diào)用優(yōu)先級(jí)
隱式轉(zhuǎn)換時(shí)會(huì)自動(dòng)調(diào)用
toString和valueOf方法,兩者優(yōu)先級(jí)如下:強(qiáng)制轉(zhuǎn)化為字符串類型時(shí),優(yōu)先調(diào)用 toString方法強(qiáng)制轉(zhuǎn)換為數(shù)值類型時(shí),優(yōu)先調(diào)用 valueOf方法使用運(yùn)算符操作符情況下, valueOf優(yōu)先級(jí)高于toStirng對(duì)象的類型轉(zhuǎn)換見下一問。
8.你知道對(duì)象轉(zhuǎn)換成原始值是什么流程嗎 (ToPrimitive)?
對(duì)象轉(zhuǎn)換成原始類型,會(huì)調(diào)用內(nèi)置的 [ToPrimitive]函數(shù)
(參考博客: 從ECMA規(guī)范徹底理解 JavaScript 類型轉(zhuǎn)換[5])
ToPrimitive方法接受兩個(gè)參數(shù),一個(gè)是輸入的值input,一個(gè)是期望轉(zhuǎn)換的類型PreferredType如果未傳入 PreferredType參數(shù),讓hint等于'default',后面會(huì)將hint修改為'number'如果 PreferredType是hint String,讓hint等于'string'如果 PreferredType是hint Number,讓hint等于'number'返回 OrdinaryToPrimitive(input, hint)OrdinaryToPrimitive(input, hint)如果 hint是'string',那么就將methodNames設(shè)置為toString、valueOf如果 hint是'number',那么就將methodNames設(shè)置為valueOf、toString
methodName存儲(chǔ)的就是當(dāng)前preferredType下的調(diào)用優(yōu)先級(jí),如果全部調(diào)用完畢仍然未轉(zhuǎn)化為原始值,會(huì)發(fā)生報(bào)錯(cuò)。
9.你能做出下面這個(gè)題嗎?
const?a?=?{x:1};
const?b?=?{x:2};
const?obj?=?{};
obj[a]?=?100;
obj[b]?=?200;
console.log(obj[a]);
console.log(obj[b]);
復(fù)制代碼
有了第七問和第八問的知識(shí),這個(gè)題目就不難了。JavaScript 對(duì)象的鍵必須是字符串,因此分別需要將對(duì)象 a 和 b 轉(zhuǎn)換為 string 類型。具體轉(zhuǎn)換流程:
//?1.執(zhí)行ToPrimitive
//?hint?為?string
ToPrimitive(a,?'hint?String')
//?2.執(zhí)行OrdinaryToPrimitive
OrdinaryToPrimitive(a,?'string')
//?3.返回methodNames
methodNames?=?['toString',?'valueOf']
//?4.調(diào)用methodNames里方法
//?調(diào)用toString
a.toString()?//?返回[object?Object]
復(fù)制代碼
對(duì)象 a 和 b 轉(zhuǎn)換后的結(jié)果都是 [object Object],obj 對(duì)象上只添加了一個(gè)屬性 [object Object]。
答案
200
200
復(fù)制代碼
10.你能理清類型轉(zhuǎn)換嗎?
首先需要知道:在JavaScript中,只有三種類型的轉(zhuǎn)換
轉(zhuǎn)換為 Number類型:Number() / parseFloat() / parseInt()轉(zhuǎn)化為 String類型:String() / toString()轉(zhuǎn)化為 Boolean類型:Boolean()
因此遇到類型轉(zhuǎn)換問題,只需要弄清楚在什么場(chǎng)景之下轉(zhuǎn)換成那種類型即可。
轉(zhuǎn)換為boolean
顯式: Boolean方法可以顯式將值轉(zhuǎn)換為布爾類型隱式:通常在邏輯判斷或者有邏輯運(yùn)算符時(shí)觸發(fā)( || && !)
Boolean(1)???//?顯式類型轉(zhuǎn)換
if?(1)?{}????//?邏輯判斷類型觸發(fā)隱式轉(zhuǎn)換
!!1??????????//?邏輯運(yùn)算符觸發(fā)隱式轉(zhuǎn)換
1?||?'hello'?//?邏輯運(yùn)算符觸發(fā)隱式轉(zhuǎn)換
復(fù)制代碼
boolean 類型只有 true 和 false 兩種值。
除值 0,-0,null,NaN,undefined,或空字符串("") 為 false 外,其余全為 true
轉(zhuǎn)化為string
顯式: String方法可以顯式將值轉(zhuǎn)換為字符串隱式: +運(yùn)算符有一側(cè)操作數(shù)為string類型時(shí)
轉(zhuǎn)化為 string 類型的本質(zhì):需要轉(zhuǎn)換為string的部分調(diào)用自身的toString方法(null/undefined返回字符串格式的null和undefined)
當(dāng)被轉(zhuǎn)換值為對(duì)象時(shí),相當(dāng)于執(zhí)行
ToPrimitive(input, 'hint String')
String([1,2,3])?//?1,2,3
String({x:1})?//?[object?Object]
1?+?'1'?//?11
1?+?{}?//?1[object?Object]
復(fù)制代碼
轉(zhuǎn)化為number
顯式: Number方法可以顯式將值轉(zhuǎn)化為數(shù)字類型
Number 的具體規(guī)則,ES5 規(guī)范中給了一個(gè)對(duì)應(yīng)的結(jié)果表[6]
| 類型 | 結(jié)果 |
|---|---|
| undefined | NaN |
| null | +0 |
| Boolean | NaN |
| undefined | 參數(shù)為true返回1;false返回+0 |
| Number | 返回與之相等的值 |
| String | 有些復(fù)雜,舉例說明 |
| Object | 先執(zhí)行ToPrimitive方法,在執(zhí)行Number類型轉(zhuǎn)換 |
`String`: 空字符串返回 `0`,出現(xiàn)任何一個(gè)非有效數(shù)字字符,返回 `NaN`
console.log(Number("1?3"))?//?NaN
console.log(Number("abc"))?//?NaN
console.log(Number("1a"))?//?NaN
console.log(Number("0x11"))?//?17
console.log(Number("123"))?//?123
console.log(Number("-123"))?//?-123
console.log(Number("1.2"))?//?1.2
復(fù)制代碼
隱式: number的隱式類型轉(zhuǎn)換比較復(fù)雜,對(duì)需要隱式轉(zhuǎn)換的部分執(zhí)行Number:比較操作( <, >, <=, >=)按位操作( | & ^ ~)算數(shù)操作( + - * / %) 注意:+的操作數(shù)存在字符串時(shí),為string轉(zhuǎn)換一元 +-操作
11.== 的隱式轉(zhuǎn)換規(guī)則
==: 只需要值相等,無需類型相等;null, undefined在==下互相等且自身等==的轉(zhuǎn)換規(guī)則:
| 被比較數(shù)B |
|---|
| 比較數(shù)A |
| Number |
| String |
| Boolean |
| Object |
在上面的表格中,ToNumber(A) 嘗試在比較前將參數(shù) A 轉(zhuǎn)換為數(shù)字。ToPrimitive(A) 將參數(shù) A 轉(zhuǎn)換為原始值( Primitive )。
12.1 + {} 與 {} + 1的輸出結(jié)果分別是什么?
通過上面的學(xué)習(xí),當(dāng)對(duì)象與其他元素相加時(shí),對(duì)象會(huì)調(diào)用 toPrimitive 轉(zhuǎn)化為原始值:
執(zhí)行 toPrimitive,未傳入PreferredType,methodNames為[valueOf, toString]執(zhí)行 ({}).valueOf,返回對(duì)象本身{},不是原始值繼續(xù)執(zhí)行 ({}).toString(),返回"[object Object]",返回結(jié)果為原始值,轉(zhuǎn)換結(jié)束
此時(shí) 1 + {},右側(cè)為 string 類型,將 1 進(jìn)行 ToString() 轉(zhuǎn)化為 "1" ,最后字符串連接,結(jié)果為 "1[object Object]"
注意:{} + 1 輸出的結(jié)果會(huì)和 1 + {} 一樣嗎?
{} 在 JavaScript 中,不止可以作為對(duì)象定義,也可以作為代碼塊的定義。js 引擎會(huì)把 {} + 1 解析成1個(gè)代碼塊和1個(gè)+1,最終輸出結(jié)果為 1
答案
1[object?Object]
1
復(fù)制代碼
13.[]與{}的相加的結(jié)果是多少?
[] + {}
數(shù)組是特殊的對(duì)象,需要調(diào)用 toPrimitive,轉(zhuǎn)換為原始值
執(zhí)行 toPrimitive,未傳入PreferredType,methodNames為[valueOf, toString]執(zhí)行 [].valueOf,返回?cái)?shù)組本身執(zhí)行 [].toString,返回空字符串''
空對(duì)象不做贅述。
答案
"[object?Object]"
復(fù)制代碼
[] + []
類似 1 兩個(gè)空數(shù)組都執(zhí)行 toPrimitive,返回兩個(gè)空字符串。
答案
""
復(fù)制代碼
{} + []
類似于 {} + 1,{} + [] 相當(dāng)于 {}; + [],一元 + 強(qiáng)制將 "" 隱式轉(zhuǎn)換為0,最終結(jié)果為0
答案
0
復(fù)制代碼
{} + {}
對(duì)于這個(gè)題,我先公布一下答案,之后說一下我的疑問。
答案
[object?Object][object?Object]
復(fù)制代碼
疑問
為什么 JavaScript 引擎沒有將前面的 {} 解釋成代碼塊?
友情提示:由于
{}可以解釋為代碼塊的形式,有些需要注意的地方,舉個(gè)栗子:
空對(duì)象調(diào)用方法時(shí): {}.toString()會(huì)報(bào)錯(cuò)箭頭函數(shù)返回對(duì)象時(shí): let getTempItem = id => { id: id, name: "Temp" }會(huì)報(bào)錯(cuò)
14.你能靈活運(yùn)用 parseInt 與 parseFloat 嗎
`parseInt`:從數(shù)字類開始看,看到非數(shù)字類為止,返回原來的數(shù)。\(小數(shù)點(diǎn)也屬于非有效數(shù)字\)
parseInt('123x')?->?123
parseInt('-023x')?->?-23
parseInt('1.1')?->?1
parseInt('-abc')?->?NaN
parseInt('x123')?->?NaN
復(fù)制代碼
parseInt(string, radix)還有第二個(gè)參數(shù)radix表示要解析數(shù)字的基數(shù),取值為2~36(默認(rèn)值為10)parseFloat與parseInt類似,只不過它返回浮點(diǎn)數(shù)。從數(shù)字類開始看,看到除了第一個(gè)點(diǎn)以外的非數(shù)字類為截止,返回前面的數(shù)。
網(wǎng)紅題:['1','2','3'].map(parseInt)
這個(gè)網(wǎng)紅題考察的就是 parseInt 有兩個(gè)參數(shù)。map 傳入的函數(shù)可執(zhí)行三個(gè)參數(shù):
//?ele???遍歷的元素
//?index?遍歷的元素索引
//?arr???數(shù)組
arr.map(function(ele,?index,?arr){})
復(fù)制代碼
['1','2','3'].map(parseInt)相當(dāng)于執(zhí)行了以下三次過程:
parseInt('1',?0,?['1','2','3'])
parseInt('2',?1,?['1','2','3'])
parseInt('3',?2,?['1','2','3'])
復(fù)制代碼
parseInt('1', 0, ['1','2','3']): radix為0時(shí),默認(rèn)取10,最后返回1parseInt('2', 1, ['1','2','3']): radix取值為2~36,返回NaNparseInt('3', 2, ['1','2','3']): radix取值為2,二進(jìn)制只包括0,1,返回NaN
15.如何讓 if(a == 1 && a == 2) 條件成立?
valueOf 的應(yīng)用
var?a?=?{
????value:?0,
????valueOf:?function()?{
????????this.value++;
????????return?this.value;
????}
};
console.log(a?==?1?&&?a?==?2);?//true
復(fù)制代碼
關(guān)于本文
作者:戰(zhàn)場(chǎng)小包
https://juejin.cn/post/7021750693262262308
最后
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
???“分享、點(diǎn)贊、在看” 支持一波??
