本专栏的文章是通过总结常见的面试题目,来学习前端相关的知识点,本篇文章是 javascript 第一篇,从标准内置对象开始再详细介绍类型转换机制,争取把所有关于 js 类型的面试题目都一网打尽。这篇文章 1-4节 很多内容都来自 《你不知道的javascript 中》这本书,可以在下面的链接中获取电子书。如果觉得我的逻辑不够清晰,看电子书就可以了,可以直接看第五部分的面试题目。
链接: https://pan.baidu.com/s/1e4OiJjPxPMNiMqyYjnKvlA 提取码: b7fw
内容在不断的补充和调整,有不全面或者疏漏的内容,欢迎评论区指正。
Javascript 的标准内置对象有很多,我们先只需要几个和 javascript 基本数据类型对应的常见的对象即可,首先我们要知道 js 的基本类型有哪些。
Javascript 有 8 种基本数据类型,分别是
基本数据类型就这些,除了 null 我们基本可以找到他们对应的内置对象。
数据类型 | 对应的标准内置对象 |
undefined | 值属性(这些全局属性返回一个简单值,这些值没有自己的属性和方法) |
Boolean | 基本对象(基本对象是定义或使用其他对象的基础) |
Number | 数字和日期对象(用来表示数字、日期和执行数学计算的对象) |
String | 字符串(这些对象表示字符串并支持操作字符串) |
Object | 基本对象(基本对象是定义或使用其他对象的基础) |
Symbol | 基本对象(基本对象是定义或使用其他对象的基础) |
BigInt | 数字和日期对象(用来表示数字、日期和执行数学计算的对象) |
我们再来看一下《你不知道的 javascript 中》这本书的第三章提到的原生函数
所以这里面说的原生函数(内建对象) 和官网的标准内置对象其实是一个东西,只要把这几个常见的记住就可以了。其实很好记住,首先记住 8 个基本数据类型,再补充数组、函数、正则表达式、日期、错误就可以了。
由于基本类型值没有 .length 和 .toString 这样的属性和方法,所以才需要把基本类型封装成对象。一个普通的字符串能够使用 .length 这个长度属性本质就是因为 js 自动把这个字符串包装成了一个对象,数字类型和字符串类型都会被自动包装,但 boolean 类型不会自动封装,不过 数字、字符串、布尔我们都可以手动进行封装?。
变量 str 调用 str.valueOf() 可以获取包装之前的基本类型值。
首先明确一点 typeof 是一个运算符,表示操作数的类型,这个想必不管多菜的小白都知道。
这里强调一点,typeof 返回的结果都是小写单词,不要不以为然,后面还有大小写混合的你就乱套了。
对于自动封装的字符串和数字,执行typeof 返回的依旧是小写的 string 、number,但是对于手动封装的包装类型字符串、数字等,typeof 将返回 object
(1)typeof null === "object"
这个是 js 最初遗留的bug
在 Javascript 最初的实现中,Javascript 中的值是由一个表示类型的标签和实际数值表示的。对象( object )的类型标签是 0。由于 null 代表的是空指针(大多数平台下值位0x00),因此,null 的类型标签是 0。这就导致 typeof null === "object"
(2)typeof NaN === "number"
NaN 的意思是not a number ,但是使用typeof 操作符的得到的还是小写的 number 。
好了,关于基本数据类型就学到这里,下面我们看一下js 的类型转换机制,这块其实非常非常简单,长篇大论下来就一句话!
所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]](我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..) 来查看
—— 你不知道的JavaScript(中卷).pdf
被自动封装的字符串和数字,他们的内部属性 [[class]] 分别是 String 、Number【注意是首字母大写的单词】
Javascript 的类型转换分成显示转换和隐式类型转换,统一称为强制类型转换。很好区分:(1)在使用一元操作符(比如:+)、比较运算的时候会执行隐式类型转换;(2)在使用原生函数进行转换的时候就是显示类型转换【如: Number('123')】
不知道原生函数是啥?你肯定没认真看第一章的内容!
在开始之前我们还要知道 js 类型转换中涉及到4个抽象操作!分别是
什么是抽象操作?说白了就是该如何做的抽象描述,他不是一个具体的函数,就是一系列操作的文字描述。
记住,js 的类型转换返回总是基本类型,string 、number (NaN)、boolean 这三个返回值之一。分别对应的就是 toString 、toNumber、toBoolean 三个抽象操作。下面我们详细介绍 4 个抽象操作的规则。
toPrimitive 是一个获取对象原始值的抽象操作,他的返回值一定是字符串这个原始值!
什么是原始值?就是 string、number、boolean,类型转换中的原始值就他们三个!
这个单词请记住
需要获取原始值的变量一定不是原始值,一定是一个对象,流程如下
还记得 valueOf 是干啥用的吗?在 1.2 中提到过使用 valueOf 可以获取包装类型的基本类型
注意,绝大多数普通对象,除了日期对象,调用 valueOf 返回的都是值的本身。
看到了没有,整个获取原始值的抽象操作就 2 个步骤,就是调用了2个函数,valueOf 和 toString,现在也就很好理解,为啥获取原始值的返回类型一定是字符串了吧, 因为它一定会调用 toString整个方法!!
下面我们再来看看调用 toString 的原理。
顾名思义需要把变量转换成 string 类型,输入任意类型的变量,返回 string 类型的结果。
(1)常见的基本数据类型转换规则如下:
(2)对象转化字符串规则【本质是执行?toPrimitive 抽象操作】
这我们就不得不提到面试中,进行类型检测的另一个常见的方法?Object.prototype.toString.call(),透过现象看本质,这个方法就是调用 Object 原型中的 toString 方法。
这部分内容在 1.4 中有提到的
对于使用 typeof 返回 object 的对象(比如数组),都包含一个内部属性[[Class]],这个内部属性就是某个原生函数,比如Number,String等。无法直接访问,但是可以使用Object.prototype.toString来查看。
所以Object.prototype.toString.call() 一个对象,返回值有两个单词,一个是小写的 object【typeof 的值】 + 空格 + 后面跟首字母大写的原生方法? ?=> 组合【指定一个是对象类型,一个是具体的哪个对象如 Array 】,然后首尾再加上中括号。
普通对象【无自定义 toString 】的 toString 【去原型链上找 toString 方法即 Object.prototype.toString】返回内部属性 [[Class]] 的值 如 [object Object]
非普通对象,比如数组和函数,数组重写了toString 方法,所以会调用自己的 toString 方法进行类型转换。
总结一下,对于对象转换成字符串的规则如下
为什么非要用 .call ?
Function?实例的?
call()
?方法会以给定的?this
?值和逐个提供的参数调用该函数。及时为了指定this 值
把非数值转换成数字的方法就是调用 toNumber 抽象操作,其实就是相当于显示调用了原生函数Number 进行类型转换
对于常量的转换规则如下:
对于字符串:
对象(包括数组):
记住,在js中有且仅有下面这7个假值 falsy,除了这7个都是真值
所以 toBoolean 的这个抽象操作非常简单,假值返回一定是false,真值返回一定是true
经典面试题,+ 操作符什么时候执行数字加操作,什么时候执行字符串拼接操作?
当 + 操作符有两个操数,且两边都是字符串,或者有一个是字符串(或者能够通过 toPrimitive 抽象操作得到字符串)的时候,就执行字符串拼接操作,此时要将不是字符串的转成字符串。
我在重复一遍,我就不信你记不住!
对象(包括数组) 获取原始值是通过抽象操作 ToPrimitive,先调用 obj.valueOf() 再调用 obj.toString(),(Date对象例外,先调用 date.toString() 再调用 data.valueOf(),因为 Date 对象重写了 valueOf 方法,调用 valueOf 会返回时间戳。数组和 function 也重写 toString 方法,数组返回使用逗号拼接的字符串,function 返回代码函数的字符串形式。
当两个操作数都是数字的时候,毫无疑问执行数字加操作。
当 + 只有右边一个操作数的时候,无论操作数是什么类型,都执行数字加操作,将操作数使用toPrimitive 返回原始值,再执行toNumber,如果是数字就返回数字,如果不是数字就返回 NaN。
[] + {} = "[object Object]" 和 {} + [] = 0,因为 {} 在前面被解释为了一个代码块而不是一个空对象。所以会背忽略,不产生实际的值,所以执行数字加操作,把 [] 转成数字,在 {} + [] 加上一个括号就相等了。所以这个特殊的点在于花括号的特殊性!
除了 + 操作意外以外,其他的运算符比如 减、乘、除、取余,都会把操作数使用 toNumber 转成数值。
比较运算符包括 大于、小于、等于、不等于,就这四个,我们经常进行的比较都是在数学范畴的,比如数字的比较,所以在 js 中我们比较的本质也是对数字的比较,一会你就明白是啥意思了。
js 中等于包括宽松相等(两个等号 == )和严格相等(三个等号 === ),其中宽松相等会会进行隐式类型转换。
转换的规则是:
看到没有?就是把字符串类型的变量转成数字,然后对于数字进行比较
到没有?也是把布尔类型的变量转成数字,然后对于数字进行比较
null 、和undefined 宽松相等,自身也相等
NaN 是 js 中唯一一个不自反的值,也就是说不与自身相等,无论是宽松相等,还是严格相等都返回 false。这是不科学的,这是所以 js 用Object.is 修正了这一点。
+0 和 -0 宽松相等,也严格相等,宽松相等可以理解,但是严格相等这是不科学的,所以 js 用Object.is 修正了这一点。
如果比较双方都是字符串 ,按照字母表顺序比较
比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较。
发现了没有,除了一些特殊的列子,所有的比较都是在操作数转换成数字的基础上进行的,也就是最终非 Number 类型的都调用了 toNumber,然后再进行比较。
我们这样想,在最开始学习的比较运算符就是针对数字的,然后在 js 中的比较的本质就是是变量对应的 number 原始值的比较,这样就很好理解了,也不用再纠结什么类型转换的高深原理了,一切都是纸老虎。
在条件语句中比如 if 、while 会隐式转换调用 toBoolean 转换成 boolean 类型。
javascrip 中的类型转换可以分为显示类型转换和隐式类型转换。
(1)显示类型转换是指我们手动调用原生函数进行类型转换,常见的有 Number()、String()、Boolean()。
还有一个parseInt,相比于Number转换就没有那么严格了,parseInt
函数逐个解析字符,遇到不能转换的字符就停下来
(2)隐式类型转换发生在算数运算符和比较运算符中。
【自动转成数值】在算数运算符加减乘除和取余的运算过程中,除了 加,都是将操作数使用 toNumber转成数值类型再进行运算。
【自动转成字符串】加操作有点特殊,在有两个操作数,并且其中一个操作数是字符串或者能够通过 toPrimitive 操作转成字符串的时候,就将两个操作数都转成字符串,然后执行字符串拼接操作。其他情况是转成数值然后进行加操作
【自动转成数值】对于比较运算符,如果两个操作数都是字符串,就更具字母表顺序进行比较,其他情况都是把来个操作数使用 toPrimitive 再执行 toNumber 转成数值再进行比较。
(2)在条件语句中,会有 toBoolean 的隐式类型转换,比如if while,除了7个为false的值其他的值都会转成true。
隐式类型转换是 js 自动的机制。其本质就是对操作数的原始值的比较。
会执行字符串拼接操作。因为 js 的隐式转换规则是,在 + 操作符的时候,如果其中一个操作数是字符串或者可以通过获取原始值的方式得到字符串,就执行字符串的拼接操作。在这里,需要将数字使用toString转成字符串,再拼接。
js中有且仅有7个值被认为 falsy,除了这7个值之外都是true
对象转换成数字的第一步是先执行 toPrimitive 获取原始类型?,第二部再执行 toNumber 得到数字类型。
内部的机制是
(1)对象{a: 1}调用 valueOf 得到对象的原始值,还是{a: 1}
(2)调用 toString ,也就是执行Object.prototype.toString.call({a:1}) 得到字符串类型的结果 [object Object]
(3)再对?[object Object] 进行 toNumber 的显示转换,得到NaN
js 的隐式转换就是在执行运算操作符、比较操作符、条件语句的时候,对于不同类型的操作数进行的自动的类型转换。一般来说就3中,转成数字、转成字符串、转成布尔类型。
对于宽松相等会进行类型转换,严格相等不会进行类型转换。
宽松相等的一般会把两个操作数都转成数值类型然后再进行比较。一些特殊情况的例子有
(1)NaN == NaN =>?false 因为NaN 是js中唯一不自反的值,NaN 转成Number 还是NaN
(2)null == undefined? => true 这个很好理解,因为null 和undefined 转成Number 都是0
(3)+0 == -0? => true
对于严格相等,不进行隐式类型转换,但是有几个特殊
(1)NaN === NaN => false,没错在严格相等下NaN 依旧不和自身相等
(2)+0 ===?-0?? => true 这个是有违常理的,但是确实存在这个一个问题,所以使用Object.is()修正
Object.is 修正了两个诡异的问题
数组作为特殊的对象,在进行类型转换的时候也是执行 toPrimitive 操作,先调用 valueOf 再调用 toString 方法,但是由于数组的构造函数重写了 toString 方法,所以他就不会调用Object 原型上的toString 方法了, 而是调用Array 的toString 方法。
数组调用valueOf 之后会返回原数组,然后 toString 方法会把数组的各个值使用 逗号 拼接起来,对于多维数组也是依次用逗号拼接。特殊的比如 [null] 和 [undefined] 会返回空字符串。
NaN 在进行 toNumber 的时候依旧返回NaN,toString 返回字符串 NaN,toBoolean 返回 false
+-Infinity? 在进行 toNumber 的时候返回他本身 +-Infinity,toString 返回对应的字符串+-Infinity,在toBoolean 的时候返回true。这个有点不通,都是返回true,因为Infinity 不属于js中的falsy值,js中的falsy的值有且仅有下面7个:
null 、undefined、NaN、false、空字符串、+0、-0
因为 js 对字符串进行了自动的默认的包装,使用了new String 构造函数包装,使得字符串有String类的方法。但是因为是自动的包装属于是隐式的包装,所以在调用typeof 的时候依旧返回 string,只有在调用 Object.prototype.toString.call 的时候才会返回[string String]。
如果手动显示的封装,手动调用 new String(),这个时候使用typeof 会返回 Object
typeof null 返回知识小写的object,这是一个遗留的bug,在最初开始的时候js使用了一个类型标签代表数据的类型,object对象的类型标签值是000,而null代表空值也是0,所以二者冲突了。
总共8个基本数据类型,Null, Undefined, String,Number,Boolean,Object,Symbol,BigInt
使用首字母大写的单词表示数据类型,包括Null 和Undefined,这是因为在对 null 调用Object.prototype.toString.call 的时候返回值的第二单词也是首字母大写。
null 和undefined 都是基本数据类型之一。
null 代表空值,可以用它给一个变量赋值。
undefined 代表为定义,如果一个变量声明里,但是没有给他赋值,就会返回 undefined 。但是我们一般不会手动给一个变量赋值为undefined,undefined 不是 js 中的保留字,也就是允许我们定义一个名字叫 undefined 的变量,这是不安全的做法。
我们使用 void 0 来获取安全的undefined 值。
void 0 本质是模拟一个没有返回值的函数,没有返回值那就是未定义返回值,那就是undefined ,所以其实可以 void 1 void 任何值,只不过大家都用 void 0,就不要标新立异了。
(1)对于基本数据类型使用 typeof
(2)对于所有的数据类型使用 Object.prototype.toString.call 是最准确的
(3)对于数组可以使用 Array.isArray 判断
(4)对于NaN,可以使用isNaN 或者 Number.isNaN 判断,区别在于 isNaN 会进行类型 toNumber 类型转换,如果不能转换成数值的都会返回 true;Number.isNaN 更准确,会先判断是不是数字,如果不是数字类型那就直接返回 false,如果是数字再进行判断是否为NaN,不会进行类型转换,这个方法更准确。
搞这么多长篇大论我们其实就需要记住两个新的知识点,第一个是:对象获取原始值的抽象操作的步骤【valueOf + toString 】、第二个是:比较操作的本质是对数字的比较。然后再记住一下特殊的地方就可以了,比如NaN 的非自反性!这很难吗?这一点都不难。
整篇文章的内容基本上都在《你不知道的javascript 中》有详细的讲解,可以在下面的链接获取电子版书籍,强烈建议大家都看一下,看第一遍不懂,就看第二遍,有一天会豁然开朗。
链接: https://pan.baidu.com/s/1e4OiJjPxPMNiMqyYjnKvlA 提取码: b7fw
内容比较多,有不对的地方欢迎再评论区指正。