經(jīng)常被面試官問到的JavaScript數(shù)據(jù)類型知識,你真的懂嗎?
前言
之前面試了幾個開發(fā)者,他們確實做過不少項目,能力也是不錯的,但是發(fā)現(xiàn)js基礎(chǔ)不扎實,于是決定寫一下這篇javascrip數(shù)據(jù)類型相關(guān)的基礎(chǔ)文章,其實也不僅僅是因為面試了他們,之前自己在面試的時候,也曾經(jīng)被虐過,面試官說過的最深刻的一句話我到現(xiàn)在都記得。
基礎(chǔ)很重要,只有基礎(chǔ)好才會很少出bug,大多數(shù)的bug都是基礎(chǔ)不扎實造成的。
這里給出兩道我們公司數(shù)據(jù)類型基礎(chǔ)相關(guān)的面試題和答案,如果都能做對并且知道為什么(可以選擇忽略本文章):
- //類型轉(zhuǎn)換相關(guān)問題
- var bar=true;
- console.log(bar+0);
- console.log(bar+"xyz");
- console.log(bar+true);
- console.log(bar+false);
- console.log('1'>bar);
- console.log(1+'2'+false);
- console.log('2' + ['koala',1]);
- var obj1 = {
- a:1,
- b:2
- }
- console.log('2'+obj1);
- var obj2 = {
- toString:function(){
- return 'a'
- }
- }
- console.log('2'+obj2)
- //輸出結(jié)果 1 truexyz 2 1 false 12false 2koala,1 2[object Object] 2a
- //作用域和NaN 這里不具體講作用域,意在說明NaN
- var b=1;
- function outer(){
- var b=2;
- function inner(){
- b++;
- console.log(b);
- var b=3;
- }
- inner();
- }
- outer();
- //輸出結(jié)果 NaN
本篇文章會以一個面試官問問題的角度來進行分析講解。
js中的數(shù)據(jù)類型
面試官:說一說javascript中有哪些數(shù)據(jù)類型?
JavaScript 中共有七種內(nèi)置數(shù)據(jù)類型,包括基本類型和對象類型。
基本類型
基本類型分為以下六種:
- string(字符串)
- boolean(布爾值)
- number(數(shù)字)
- symbol(符號)
- null(空值)
- undefined(未定義)
注意:
- string 、number 、boolean 和 null undefined 這五種類型統(tǒng)稱為原始類型(Primitive),表示不能再細分下去的基本類型;
- symbol是ES6中新增的數(shù)據(jù)類型,symbol 表示***的值,通過 Symbol 函數(shù)調(diào)用生成,由于生成的 symbol 值為原始類型,所以 Symbol 函數(shù)不能使用 new 調(diào)用;
- null 和 undefined 通常被認為是特殊值,這兩種類型的值唯一,就是其本身。
對象類型
對象類型也叫引用類型,array和function是對象的子類型。對象在邏輯上是屬性的無序集合,是存放各種值的容器。對象值存儲的是引用地址,所以和基本類型值不可變的特性不同,對象值是可變的。
js弱類型語言
面試官:說說你對javascript是弱類型語言的理解?
JavaScript 是弱類型語言,而且JavaScript 聲明變量的時候并沒有預(yù)先確定的類型,變量的類型就是其值的類型,也就是說變量當(dāng)前的類型由其值所決定,夸張點說上一秒種的String,下一秒可能就是個Number類型了,這個過程可能就進行了某些操作發(fā)生了強制類型轉(zhuǎn)換。雖然弱類型的這種不需要預(yù)先確定類型的特性給我們帶來了便利,同時也會給我們帶來困擾,為了能充分利用該特性就必須掌握類型轉(zhuǎn)換的原理。
js中的強制轉(zhuǎn)換規(guī)則
面試官:javascript中強制類型轉(zhuǎn)換是一個非常易出現(xiàn)bug的點,知道強制轉(zhuǎn)換時候的規(guī)則嗎?
注:規(guī)則***配合下面什么時候發(fā)生轉(zhuǎn)換使用這些規(guī)則看效果更佳。
ToPrimitive(轉(zhuǎn)換為原始值)
ToPrimitive對原始類型不發(fā)生轉(zhuǎn)換處理,只針對引用類型(object)的,其目的是將引用類型(object)轉(zhuǎn)換為非對象類型,也就是原始類型。
ToPrimitive 運算符接受一個值,和一個可選的期望類型作參數(shù)。ToPrimitive 運算符將值轉(zhuǎn)換為非對象類型,如果對象有能力被轉(zhuǎn)換為不止一種原語類型,可以使用可選的 期望類型 來暗示那個類型。
轉(zhuǎn)換后的結(jié)果原始類型是由期望類型決定的,期望類型其實就是我們傳遞的type。直接看下面比較清楚。
ToPrimitive方法大概長這么個樣子具體如下。
- /**
- * @obj 需要轉(zhuǎn)換的對象
- * @type 期望轉(zhuǎn)換為的原始數(shù)據(jù)類型,可選
- */
- ToPrimitive(obj,type)
type不同值的說明
- type為string:
- 先調(diào)用obj的toString方法,如果為原始值,則return,否則進行第2步
- 調(diào)用obj的valueOf方法,如果為原始值,則return,否則進行第3步
- 拋出TypeError 異常
- type為number:
- 先調(diào)用obj的valueOf方法,如果為原始值,則return,否則進行第2步
- 調(diào)用obj的toString方法,如果為原始值,則return,否則第3步
- 拋出TypeError 異常
- type參數(shù)為空
- 該對象為Date,則type被設(shè)置為String
- 否則,type被設(shè)置為Number
Date數(shù)據(jù)類型特殊說明:
對于Date數(shù)據(jù)類型,我們更多期望獲得的是其轉(zhuǎn)為時間后的字符串,而非毫秒值(時間戳),如果為number,則會取到對應(yīng)的毫秒值,顯然字符串使用更多。
其他類型對象按照取值的類型操作即可。
ToPrimitive總結(jié)
ToPrimitive轉(zhuǎn)成何種原始類型,取決于type,type參數(shù)可選,若指定,則按照指定類型轉(zhuǎn)換,若不指定,默認根據(jù)實用情況分兩種情況,Date為string,其余對象為number。那么什么時候會指定type類型呢,那就要看下面兩種轉(zhuǎn)換方式了。
toString
Object.prototype.toString()
toString() 方法返回一個表示該對象的字符串。
每個對象都有一個 toString() 方法,當(dāng)對象被表示為文本值時或者當(dāng)以期望字符串的方式引用對象時,該方法被自動調(diào)用。
這里先記住,valueOf() 和 toString() 在特定的場合下會自行調(diào)用。
valueOf
Object.prototype.valueOf()方法返回指定對象的原始值。
JavaScript 調(diào)用 valueOf() 方法用來把對象轉(zhuǎn)換成原始類型的值(數(shù)值、字符串和布爾值)。但是我們很少需要自己調(diào)用此函數(shù),valueOf 方法一般都會被 JavaScript 自動調(diào)用。
不同內(nèi)置對象的valueOf實現(xiàn):
- String => 返回字符串值
- Number => 返回數(shù)字值
- Date => 返回一個數(shù)字,即時間值,字符串中內(nèi)容是依賴于具體實現(xiàn)的
- Boolean => 返回Boolean的this值
- Object => 返回this
對照代碼會更清晰一些:
- var str = new String('123');
- console.log(str.valueOf());//123
- var num = new Number(123);
- console.log(num.valueOf());//123
- var date = new Date();
- console.log(date.valueOf()); //1526990889729
- var bool = new Boolean('123');
- console.log(bool.valueOf());//true
- var obj = new Object({valueOf:()=>{
- return 1
- }})
- console.log(obj.valueOf());//1
Number
Number運算符轉(zhuǎn)換規(guī)則:
- null 轉(zhuǎn)換為 0
- undefined 轉(zhuǎn)換為 NaN
- true 轉(zhuǎn)換為 1,false 轉(zhuǎn)換為 0
- 字符串轉(zhuǎn)換時遵循數(shù)字常量規(guī)則,轉(zhuǎn)換失敗返回 NaN
注意:對象這里要先轉(zhuǎn)換為原始值,調(diào)用ToPrimitive轉(zhuǎn)換,type指定為number了,繼續(xù)回到ToPrimitive進行轉(zhuǎn)換。
String
String 運算符轉(zhuǎn)換規(guī)則
- null 轉(zhuǎn)換為 'null'
- undefined 轉(zhuǎn)換為 undefined
- true 轉(zhuǎn)換為 'true',false 轉(zhuǎn)換為 'false'
- 數(shù)字轉(zhuǎn)換遵循通用規(guī)則,極大極小的數(shù)字使用指數(shù)形式
注意:對象這里要先轉(zhuǎn)換為原始值,調(diào)用ToPrimitive轉(zhuǎn)換,type就指定為string了,繼續(xù)回到ToPrimitive進行轉(zhuǎn)換(上面有將到ToPrimitive的轉(zhuǎn)換規(guī)則)。
- String(null) // 'null'
- String(undefined) // 'undefined'
- String(true) // 'true'
- String(1) // '1'
- String(-1) // '-1'
- String(0) // '0'
- String(-0) // '0'
- String(Math.pow(1000,10)) // '1e+30'
- String(Infinity) // 'Infinity'
- String(-Infinity) // '-Infinity'
- String({}) // '[object Object]'
- String([1,[2,3]]) // '1,2,3'
- String(['koala',1]) //koala,1
Boolean
ToBoolean 運算符轉(zhuǎn)換規(guī)則
除了下述 6 個值轉(zhuǎn)換結(jié)果為 false,其他全部為 true:
- undefined
- null
- -0
- 0或+0
- NaN
- ''(空字符串)
假值以外的值都是真值。其中包括所有對象(包括空對象)的轉(zhuǎn)換結(jié)果都是true,甚至連false對應(yīng)的布爾對象new Boolean(false)也是true。
- Boolean(undefined) // false
- Boolean(null) // false
- Boolean(0) // false
- Boolean(NaN) // false
- Boolean('') // false
- Boolean({}) // true
- Boolean([]) // true
- Boolean(new Boolean(false)) // true
js轉(zhuǎn)換規(guī)則不同場景應(yīng)用
面試官問:知道了具體轉(zhuǎn)換成什么的規(guī)則,但是都在什么情況下發(fā)生什么樣的轉(zhuǎn)換呢?
什么時候自動轉(zhuǎn)換為string類型
- 在沒有對象的前提下
字符串的自動轉(zhuǎn)換,主要發(fā)生在字符串的加法運算時。當(dāng)一個值為字符串,另一個值為非字符串,則后者轉(zhuǎn)為字符串。
- '2' + 1 // '21'
- '2' + true // "2true"
- '2' + false // "2false"
- '2' + undefined // "2undefined"
- '2' + null // "2null"
- 當(dāng)有對象且與對象+時候
- //toString的對象
- var obj2 = {
- toString:function(){
- return 'a'
- }
- }
- console.log('2'+obj2)
- //輸出結(jié)果2a
- //常規(guī)對象
- var obj1 = {
- a:1,
- b:2
- }
- console.log('2'+obj1);
- //輸出結(jié)果 2[object Object]
- //幾種特殊對象
- '2' + {} // "2[object Object]"
- '2' + [] // "2"
- '2' + function (){} // "2function (){}"
- '2' + ['koala',1] // 2koala,1
對下面'2'+obj2詳細舉例說明如下:
- 左邊為string,ToPrimitive原始值轉(zhuǎn)換后不發(fā)生變化
- 右邊轉(zhuǎn)化時同樣按照ToPrimitive進行原始值轉(zhuǎn)換,由于指定的type是number,進行ToPrimitive轉(zhuǎn)化調(diào)用obj2.valueof(),得到的不是原始值,進行第三步
- 調(diào)用toString() return 'a'
- 符號兩邊存在string,而且是+號運算符則都采用String規(guī)則轉(zhuǎn)換為string類型進行拼接
- 輸出結(jié)果2a
對下面'2'+obj1詳細舉例說明如下:
- 左邊為string,ToPrimitive轉(zhuǎn)換為原始值后不發(fā)生變化
- 右邊轉(zhuǎn)化時同樣按照ToPrimitive進行原始值轉(zhuǎn)換,由于指定的type是number,進行ToPrimitive轉(zhuǎn)化調(diào)用obj2.valueof(),得到{ a: 1, b: 2 }
- 調(diào)用toString() return [object Object]
- 符號兩邊存在string,而且是+號運算符則都采用String規(guī)則轉(zhuǎn)換為string類型進行拼接
- 輸出結(jié)果2[object Object]
代碼中幾種特殊對象的轉(zhuǎn)換規(guī)則基本相同,就不一一說明,大家可以想一下流程。
注意:不管是對象還不是對象,都有一個轉(zhuǎn)換為原始值的過程,也就是ToPrimitive轉(zhuǎn)換,只不過原始類型轉(zhuǎn)換后不發(fā)生變化,對象類型才會發(fā)生具體轉(zhuǎn)換。
string類型轉(zhuǎn)換開發(fā)過程中可能出錯的點:
- var obj = {
- width: '100'
- };
- obj.width + 20 // "10020"
預(yù)期輸出結(jié)果120 實際輸出結(jié)果10020
什么時候自動轉(zhuǎn)換為Number類型
- 有加法運算符,但是無String類型的時候,都會優(yōu)先轉(zhuǎn)換為Number類型
例子:
- true + 0 // 1
- true + true // 2
- true + false //1
- 除了加法運算符,其他運算符都會把運算自動轉(zhuǎn)成數(shù)值。
例子:
- '5' - '2' // 3
- '5' * '2' // 10
- true - 1 // 0
- false - 1 // -1
- '1' - 1 // 0
- '5' * [] // 0
- false / '5' // 0
- 'abc' - 1 // NaN
- null + 1 // 1
- undefined + 1 // NaN
- //一元運算符(注意點)
- +'abc' // NaN
- -'abc' // NaN
- +true // 1
- -false // 0
注意:null轉(zhuǎn)為數(shù)值時為0,而undefined轉(zhuǎn)為數(shù)值時為NaN。
判斷等號也放在Number里面特殊說明
== 抽象相等比較與+運算符不同,不再是String優(yōu)先,而是Number優(yōu)先。
下面列舉x == y的例子
如果x,y均為number,直接比較
沒什么可解釋的了
- 1 == 2 //false
如果存在對象,ToPrimitive()type為number進行轉(zhuǎn)換,再進行后面比較
- var obj1 = {
- valueOf:function(){
- return '1'
- }
- }
- 1 == obj1 //true
- //obj1轉(zhuǎn)為原始值,調(diào)用obj1.valueOf()
- //返回原始值'1'
- //'1'toNumber得到 1 然后比較 1 == 1
- [] == ![] //true
- //[]作為對象ToPrimitive得到 ''
- //![]作為boolean轉(zhuǎn)換得到0
- //'' == 0
- //轉(zhuǎn)換為 0==0 //true
存在boolean,按照ToNumber將boolean轉(zhuǎn)換為1或者0,再進行后面比較
- //boolean 先轉(zhuǎn)成number,按照上面的規(guī)則得到1
- //3 == 1 false
- //0 == 0 true
- 3 == true // false
- '0' == false //true
4.如果x為string,y為number,x轉(zhuǎn)成number進行比較
- //'0' toNumber()得到 0
- //0 == 0 true
- '0' == 0 //true
什么時候進行布爾轉(zhuǎn)換
- 布爾比較時
- if(obj) , while(obj) 等判斷時或者 三元運算符只能夠包含布爾值
條件部分的每個值都相當(dāng)于false,使用否定運算符后,就變成了true
- if ( !undefined
- && !null
- && !0
- && !NaN
- && !''
- ) {
- console.log('true');
- } // true
- //下面兩種情況也會轉(zhuǎn)成布爾類型
- expression ? true : false
- !! expression
js中的數(shù)據(jù)類型判斷
面試官問:如何判斷數(shù)據(jù)類型?怎么判斷一個值到底是數(shù)組類型還是對象?
三種方式,分別為 typeof、instanceof 和 Object.prototype.toString()
typeof
通過 typeof操作符來判斷一個值屬于哪種基本類型。
- typeof 'seymoe' // 'string'
- typeof true // 'boolean'
- typeof 10 // 'number'
- typeof Symbol() // 'symbol'
- typeof null // 'object' 無法判定是否為 null
- typeof undefined // 'undefined'
- typeof {} // 'object'
- typeof [] // 'object'
- typeof(() => {}) // 'function'
上面代碼的輸出結(jié)果可以看出,
null 的判定有誤差,得到的結(jié)果
如果使用 typeof,null得到的結(jié)果是object
操作符對對象類型及其子類型,例如函數(shù)(可調(diào)用對象)、數(shù)組(有序索引對象)等進行判定,則除了函數(shù)都會得到 object 的結(jié)果。
綜上可以看出typeOf對于判斷類型還有一些不足,在對象的子類型和null情況下。
instanceof
通過 instanceof 操作符也可以對對象類型進行判定,其原理就是測試構(gòu)造函數(shù)的 prototype 是否出現(xiàn)在被檢測對象的原型鏈上。
- [] instanceof Array // true
- ({}) instanceof Object // true
- (()=>{}) instanceof Function // true
復(fù)制代碼注意:instanceof 也不是***的。
舉個例子:
- let arr = []
- let obj = {}
- arr instanceof Array // true
- arr instanceof Object // true
- obj instanceof Object // true
在這個例子中,arr 數(shù)組相當(dāng)于 new Array() 出的一個實例,所以 arr.__proto__ === Array.prototype,又因為 Array 屬于 Object 子類型,即 Array.prototype.__proto__ === Object.prototype,因此 Object 構(gòu)造函數(shù)在 arr 的原型鏈上。所以 instanceof 仍然無法優(yōu)雅的判斷一個值到底屬于數(shù)組還是普通對象。
還有一點需要說明下,有些開發(fā)者會說 Object.prototype.__proto__ === null,豈不是說 arr instanceof null 也應(yīng)該為 true,這個語句其實會報錯提示右側(cè)參數(shù)應(yīng)該為對象,這也印證 typeof null 的結(jié)果為 object 真的只是javascript中的一個 bug 。
Object.prototype.toString() 可以說是判定 JavaScript 中數(shù)據(jù)類型的***解決方法了,具體用法請看以下代碼:
- Object.prototype.toString.call({}) // '[object Object]'
- Object.prototype.toString.call([]) // '[object Array]'
- Object.prototype.toString.call(() => {}) // '[object Function]'
- Object.prototype.toString.call('seymoe') // '[object String]'
- Object.prototype.toString.call(1) // '[object Number]'
- Object.prototype.toString.call(true) // '[object Boolean]'
- Object.prototype.toString.call(Symbol()) // '[object Symbol]'
- Object.prototype.toString.call(null) // '[object Null]'
- Object.prototype.toString.call(undefined) // '[object Undefined]'
- Object.prototype.toString.call(new Date()) // '[object Date]'
- Object.prototype.toString.call(Math) // '[object Math]'
- Object.prototype.toString.call(new Set()) // '[object Set]'
- Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
- Object.prototype.toString.call(new Map()) // '[object Map]'
- Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
我們可以發(fā)現(xiàn)該方法在傳入任何類型的值都能返回對應(yīng)準(zhǔn)確的對象類型。用法雖簡單明了,但其中有幾個點需要理解清楚:
- 該方法本質(zhì)就是依托Object.prototype.toString() 方法得到對象內(nèi)部屬性 [[Class]]
- 傳入原始類型卻能夠判定出結(jié)果是因為對值進行了包裝
- null 和 undefined 能夠輸出結(jié)果是內(nèi)部實現(xiàn)有做處理
NaN相關(guān)總結(jié)
NaN的概念
NaN 是一個全局對象的屬性,NaN 是一個全局對象的屬性,NaN是一種特殊的Number類型。
什么時候返回NaN (開篇第二道題也得到解決)
- 無窮大除以無窮大
- 給任意負數(shù)做開方運算
- 算數(shù)運算符與不是數(shù)字或無法轉(zhuǎn)換為數(shù)字的操作數(shù)一起使用
- 字符串解析成數(shù)字
一些例子:
- Infinity / Infinity; // 無窮大除以無窮大
- Math.sqrt(-1); // 給任意負數(shù)做開方運算
- 'a' - 1; // 算數(shù)運算符與不是數(shù)字或無法轉(zhuǎn)換為數(shù)字的操作數(shù)一起使用
- 'a' * 1;
- 'a' / 1;
- parseInt('a'); // 字符串解析成數(shù)字
- parseFloat('a');
- Number('a'); //NaN
- 'abc' - 1 // NaN
- undefined + 1 // NaN
- //一元運算符(注意點)
- +'abc' // NaN
- -'abc' // NaN
誤區(qū)
- toString和String的區(qū)別
toString
toString()可以將數(shù)據(jù)都轉(zhuǎn)為字符串,但是null和undefined不可以轉(zhuǎn)換。
- console.log(null.toString())
- //報錯 TypeError: Cannot read property 'toString' of null
- console.log(undefined.toString())
- //報錯 TypeError: Cannot read property 'toString' of undefined
toString()括號中可以寫數(shù)字,代表進制
二進制:.toString(2);
八進制:.toString(8);
十進制:.toString(10);
十六進制:.toString(16);
- String
String()可以將null和undefined轉(zhuǎn)換為字符串,但是沒法轉(zhuǎn)進制字符串
- console.log(String(null));
- // null
- console.log(String(undefined));
- // undefined
【編輯推薦】