前面提到了类型判断,JavaScript 的变量类型也是面试中经常考察的知识点。本节收集了一些类型相关的面试真题。
1. JavaScript 中有几种基本类型数据?请再列举几个引用对象。
答案:
7 种,分别为:Boolean,Null,Undefined,Number,BigInt,String,Symbol
。
列举几种引用对象:
普通对象 Object
数组对象 Array
正则对象 RegExp
函数 Function
解读:
这道题目是几乎每次面试必考的题目,一般作为开胃菜被面试官提出来。将它们分为三组,答案并不难记:
- 表示空:
Undefined
,Null
- 基础三大件:
Number
,String
,Boolean
- 两个新类型:
Symbol
,BigInt
顺便一提,BigInt 的作用是提供了一种方法来表示大于 253 - 1 的整数。
2. 请说一下JavaScript中的typeof
操作符的用法?
答案:
typeof
用来返回一个值的变量类型,对于不同类型的变量其返回值如下:
typeof undefined === 'undefined'
typeof true === 'boolean'
typeof 78 === 'number'
typeof 'hey' === 'string'
typeof Symbol() === 'symbol'
typeof BigInt(1) === 'bigint'
typeof new String('abc') === 'object'
typeof null === 'object'
typeof function(){} === 'function'
typeof {name: 'Jack'} === 'object'
解读:
注意typeof
是用来返回值的类型,而不是返回**变量的类型,**因为 JavaScript 中的变量是没有类型的,比如:
let a = 1;
console.log(typeof a) // 'number'
a = '1'
console.log(typeof a) // 'string'
a
变量是没有类型的,但是赋给a
变量的值确是有类型的。
typeof
对于大多数对象都会返回object
,一个例外是函数,对于函数会返回function
。另一个要注意的是typeof
对于null
会返回object
,这是一个历史悠久的 bug,但是由于无数的网站已经默认了这个 bug,所以现在也无法对其进行修正了。所以使用typeof
判断null
的方法是:
var a = null;
(!a && typeof a === "object"); // true
3. 什么是 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
,所以循环体中的内容不会运行。
4. 如何使用Object.prototype.toString
来判断值的类型,为什么使用它可以判断值的类型?
答案:
在任何值上调用Object.prototype.toString
方法,都会返回一个 [object NativeConstructorName]
格式的字符串。每个类在内部都有一个 [[Class]]
属性,这个属性中就指定了上述字符串中的构造函数名。
Object.prototype.toString.call({name:'Jack'}) // [object Object]
Object.prototype.toString.call(function(){}) // [object Function]
Object.prototype.toString.call(/name/) // [object RegExp]
而对于基本类型:
Object.prototype.toString.call('abc') // [object String]
Object.prototype.toString.call(12) // [object Number]
Object.prototype.toString.call(true) // [object Boolean]
基本类型值是没有构造函数的,为什么也能返回构造函数名呢?这是因为在toString
被调用时 JavaScript 将基本类型值转换成了包装类型。
而对于null
和undefined
:
Object.prototype.toString.call( null ); // "[object Null]"
Object.prototype.toString.call( undefined ); // "[object Undefined]"
虽然 JavaScript 中没有Null()
和Undefined
构造器,但是 JavaScript 也为我们处理这这两种情况。
5. JavaScript 中如何进行显式类型转换?
JavaScript 的类型转换的结果总是得到string
、number
和boolean
类型的一种。
string
和number
string
和 number
类型的值进行互相转换,分别使用String
和Number
函数:
const a = 18
const b = String(18) // '18'
const c = Number(b) // 18
number
向 string
类型转换还可以使用toString
方法:
const a = 18
const b = a.toString() // '18'
string
向 number
类型转换还可以使用一元操作符+
:
const a = '18'
const b = +a //18
string
向number
类型转换还可以使用parseInt
:
const a = '18px'
const b = parseInt(a) // 18
const c = Number(a) // NaN
可以看出,parseInt
与Number
不同的地方在于,parseInt
允许传入非数字字符 (例如px
),其从左往右解析,遇到非数字字符就会停下。而Number
不允许传入非数字字符。
- 任意值转换成
boolean
使用Boolean
函数来显式地将任意值转成boolean
,如:
const a = '123'
const b = undefined
const c = 0
Boolean(a) // true
Boolean(b) // false
Boolean(c) // false
实践中最常用的方法是使用!!
符号:
const a = '123'
const b = undefined
const c = 0
!!a // true
!!b // false
!!c // false
解读:
注意题目中的类型转换是这种形式:String(123)
,而不是这样:new String(123)
,因为第一个才是进行包装类型转换,而第二个是生成实例。
关于一元操作符+
还有一个用法,那就是将日期对象转换成number
var date = new Date( "Mon, 1 Mar 2020 08:53:06" )
+date // 1583013186000
6. JavaScript 中如何进行隐式类型转换?
解答:
首先要介绍ToPrimitive
方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:
/**
* @obj 需要转换的对象
* @type 期望的结果类型
*/
ToPrimitive(obj,type)
type
的值为number
或者string
。
当type
为number
时规则如下:
- 调用
obj
的valueOf
方法,如果为原始值,则返回,否则下一步; - 调用
obj
的toString
方法,后续同上; - 抛出
TypeError
异常。
当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]"
总结:
由于 JavaScript 的弱类型语言特性,它过于灵活的类型转换规则确实给开发者带来不少烦恼。特别是隐式转换,其实在实际开发中并不会遇到这么多题目中的情况,但是类似题目确实是面试中的常考题,建议大家多看多记。