原生 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新增的symboltypeof能正确区分数据类型吗?不能。对于原始类型,除
null都可以正确判断;对于引用类型,除function外,都会返回"object"typeof注意事项typeof返回值为string格式,注意类似这种考题:typeof(typeof(undefined)) -> "string"typeof未定义的变量不会报错,返回"undefiend"typeof(null) -> "object": 遗留已久的bugtypeof无法区别数组与普通对象: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】。文章转载请联系作者。











评论