原生 JavaScript 灵魂拷问,你能答上多少 (一)
前言
当下的前端开发,三大框架三分天下,框架的简单、强大让我们欲罢不能,使用原生 JavaScript
越来越少。
但我认为 JavaScript
作为每一个前端工程师的立身之本,不止要学会,还要学好、学精,学再多遍都不为过。
另一方面,前端面试中,越来越重视原生 JavaScript
的考察,其所占比例也越来越高。
因此我决定整理JavaScript
中容易忽视或者混淆的知识点,写一系列篇文章,以灵魂拷问的方式,系统且完整的带大家遨游原生 JavaScript
的世界,希望能给大家带来一些收获。
JS 类型之问——概念与检测篇
1.JS 中的数据类型有哪些?
基本数据类型:共有 7 种
Symbol
: ES6
引入的一种新的原始值,表示独一无二的值,主要为了解决属性名冲突问题。
Bigint
:ES2020
新增加,是比 Number
类型的整数范围更大。
引用数据类型:1 种
2.你真的懂 typeof 吗?
typeof
的作用?区分数据类型,可以返回 7 种数据类型:
number、string、boolean、undefined、object、function
,以及ES6
新增的symbol
typeof
能正确区分数据类型吗?不能。对于原始类型,除
null
都可以正确判断;对于引用类型,除function
外,都会返回"object"
typeof
注意事项typeof
返回值为string
格式,注意类似这种考题:typeof(typeof(undefined)) -> "string"
typeof
未定义的变量不会报错,返回"undefiend"
typeof(null) -> "object"
: 遗留已久的bug
typeof
无法区别数组与普通对象:typeof([]) -> "object"
typeof(NaN) -> "number"
习题
答案
3.什么是 instanceof?你能模拟实现一个 instanceof 吗?
instanceof
判断对象的原型链上是否存在构造函数的原型。只能判断引用类型。instanceof
常用来判断A
是否为B
的实例
模拟实现
instanceof
思想:沿原型链往上查找
测试:
4.如何区分数组与对象?使用 instanceof 判断数组可靠吗?
ES6
提供的新方法Array.isArray()
如果不存在
Array.isArray()
呢?可以借助Object.prototype.toString.call()
进行判断,此方式兼容性最好
instanceof
判断
判断方式
instanceof
判断数组类型如此之简单,为何不推荐使用那?
instanceof
操作符的问题在于,如果网页中存在多个 iframe
,那便会存在多个 Array
构造函数,此时判断是否是数组会存在问题。
更详细的内容可以参考博文:JavaScript为啥不用instanceof检测数组
5.如何判断一个数是否为 NaN?
NaN
有个非常特殊的特性, NaN
与任何值都不相等,包括它自身
鉴于这个独特的特性,可以手撕一个比较简单的判断函数
全局函数
isNaN
方法:不推荐使用。MDN
对它的介绍是:isNaN
函数内包含一些非常有趣的规则。
但为了避免一些面试官出一些冷门题目,咱们来稍微了解一下 isNaN
的有趣机制:会先判断参数是不是 Number
类型,如果不是 Number
类型会尝试将这个参数转换为 Number
类型,之后再去判断是不是 NaN
举个例子:
isNaN
的结果很大程度上取决于 Number()
类型转换的结果,关于 Number
的转换结果,后面会专门有一部分来介绍。
Number.isNaN
(推荐使用)
与 isNaN()
相比,Number.isNaN()
不会自行将参数转换成数字,只有在参数是值为 NaN
的数字时,才会返回 true
。
6.如何实现一个功能完善的类型判断函数?
Object.prototype.toString.call([value])
,可以精准判断数据类型,因此可以根据这个原理封装一个自己的 type
方法。
JS 类型之问——类型转换篇
7.toString 和 valueOf 方法有什么区别?
基础:这两个方法属于
Object
对象,是为了解决JavaScript
值运算与显示的问题。为了更适合自身功能,很多JavaScript
内置对象都重写了这两个方法。toString()
: 返回当前对象的字符串形式;valueOf()
: 返回该对象的原始值各个类型下两个方法返回值情况对比
调用优先级
隐式转换时会自动调用
toString
和valueOf
方法,两者优先级如下:强制转化为字符串类型时,优先调用
toString
方法强制转换为数值类型时,优先调用
valueOf
方法使用运算符操作符情况下,
valueOf
优先级高于toStirng
对象的类型转换见下一问。
8.你知道对象转换成原始值是什么流程吗 (ToPrimitive)?
对象转换成原始类型,会调用内置的 [ToPrimitive]
函数
(参考博客: 从ECMA规范彻底理解 JavaScript 类型转换)
ToPrimitive
方法接受两个参数,一个是输入的值input
,一个是期望转换的类型PreferredType
如果未传入
PreferredType
参数,让hint
等于'default'
,后面会将hint
修改为'number'
如果
PreferredType
是hint String
,让hint
等于'string'
如果
PreferredType
是hint Number
,让hint
等于'number'
返回
OrdinaryToPrimitive(input, hint)
OrdinaryToPrimitive(input, hint)
如果
hint
是'string'
,那么就将methodNames
设置为toString、valueOf
如果
hint
是'number'
,那么就将methodNames
设置为valueOf、toString
methodName
存储的就是当前preferredType
下的调用优先级,如果全部调用完毕仍然未转化为原始值,会发生报错。
9.你能做出下面这个题吗?
有了第七问和第八问的知识,这个题目就不难了。 JavaScript
对象的键必须是字符串,因此分别需要将对象 a
和 b
转换为 string
类型。具体转换流程:
对象 a
和 b
转换后的结果都是 [object Object]
,obj
对象上只添加了一个属性 [object Object]
。
答案
10.你能理清类型转换吗?
首先需要知道:在JavaScript
中,只有三种类型的转换
转换为
Number
类型:Number() / parseFloat() / parseInt()
转化为
String
类型:String() / toString()
转化为
Boolean
类型:Boolean()
因此遇到类型转换问题,只需要弄清楚在什么场景之下转换成那种类型即可。
转换为 boolean
显式:
Boolean
方法可以显式将值转换为布尔类型隐式:通常在逻辑判断或者有逻辑运算符时触发(
|| && !
)
boolean
类型只有 true
和 false
两种值。
除值 0,-0,null,NaN,undefined,或空字符串("") 为 false
外,其余全为 true
转化为 string
显式:
String
方法可以显式将值转换为字符串隐式:
+
运算符有一侧操作数为string
类型时
转化为 string
类型的本质:需要转换为 string 的部分调用自身的 toString 方法(null/undefined 返回字符串格式的 null 和 undefined)
当被转换值为对象时,相当于执行
ToPrimitive(input, 'hint String')
转化为 number
显式:
Number
方法可以显式将值转化为数字类型
Number
的具体规则,ES5
规范中给了一个对应的结果表
String
: 空字符串返回0
,出现任何一个非有效数字字符,返回NaN
隐式:
number
的隐式类型转换比较复杂,对需要隐式转换的部分执行Number
:比较操作(
<, >, <=, >=
)按位操作(
| & ^ ~
)算数操作(
+ - * / %
) 注意:+的操作数存在字符串时,为 string 转换一元
+-
操作
11.== 的隐式转换规则
==
: 只需要值相等,无需类型相等;null, undefined
在==
下互相等且自身等==
的转换规则:
在上面的表格中,ToNumber(A)
尝试在比较前将参数 A
转换为数字。ToPrimitive(A)
将参数 A
转换为原始值( Primitive
)。
12.1 + {}
与 {} + 1
的输出结果分别是什么?
通过上面的学习,当对象与其他元素相加时,对象会调用 toPrimitive
转化为原始值:
执行
toPrimitive
,未传入PreferredType
,methodNames
为[valueOf, toString]
执行
({}).valueOf
,返回对象本身{}
,不是原始值继续执行
({}).toString()
,返回"[object Object]"
,返回结果为原始值,转换结束
此时 1 + {}
,右侧为 string
类型,将 1
进行 ToString()
转化为 "1"
,最后字符串连接,结果为 "1[object Object]"
注意: {} + 1
输出的结果会和 1 + {}
一样吗?
{}
在 JavaScript
中,不止可以作为对象定义,也可以作为代码块的定义。js
引擎会把 {} + 1
解析成 1 个代码块和 1 个+1,最终输出结果为 1
答案
13.[]与{}的相加的结果是多少?
[] + {}
数组是特殊的对象,需要调用 toPrimitive
,转换为原始值
执行
toPrimitive
,未传入PreferredType
,methodNames
为[valueOf, toString]
执行
[].valueOf
,返回数组本身执行
[].toString
,返回空字符串''
空对象不做赘述。
答案
[] + []
类似 1
两个空数组都执行 toPrimitive
,返回两个空字符串。
答案
{} + []
类似于 {} + 1
,{} + []
相当于 {}; + []
,一元 +
强制将 ""
隐式转换为0
,最终结果为0
答案
{} + {}
对于这个题,我先公布一下答案,之后说一下我的疑问。
答案
疑问
为什么 JavaScript
引擎没有将前面的 {}
解释成代码块?
友情提示:由于
{}
可以解释为代码块的形式,有些需要注意的地方,举个栗子:
空对象调用方法时:
{}.toString()
会报错箭头函数返回对象时:
let getTempItem = id => { id: id, name: "Temp" }
会报错
14.你能灵活运用 parseInt 与 parseFloat 吗
parseInt
:从数字类开始看,看到非数字类为止,返回原来的数。(小数点也属于非有效数字)
parseInt(string, radix)
还有第二个参数radix
表示要解析数字的基数,取值为2~36
(默认值为10
)parseFloat
与parseInt
类似,只不过它返回浮点数。从数字类开始看,看到除了第一个点以外的非数字类为截止,返回前面的数。
网红题:['1','2','3'].map(parseInt)
这个网红题考察的就是 parseInt
有两个参数。 map
传入的函数可执行三个参数:
['1','2','3'].map(parseInt)相当于执行了以下三次过程:
parseInt('1', 0, ['1','2','3'])
: radix 为 0 时,默认取 10,最后返回 1parseInt('2', 1, ['1','2','3'])
: radix 取值为 2~36,返回 NaNparseInt('3', 2, ['1','2','3'])
: radix 取值为 2,二进制只包括 0,1,返回 NaN
15.如何让 if(a == 1 && a == 2) 条件成立?
valueOf
的应用
后语
我是 战场小包 ,一个快速成长中的小前端,希望可以和大家一起进步。
如果喜欢小包,可以在 infoQ 关注我,可以关注战场小包,同样可以关注我的小小公众号——小包学前端。
一路加油,冲向未来!!!
版权声明: 本文为 InfoQ 作者【战场小包】的原创文章。
原文链接:【http://xie.infoq.cn/article/3763493dd37c4c714858478a5】。文章转载请联系作者。
评论