这几周在进行系统的复习,这一篇来说一下自己复习的JS数据结构的常见面试题中比较重要的一部分
文章目录
- 一、JavaScript有哪些数据类型
- 二、数据类型检测的方法
- 1. typeof
- 2. instanceof
- 3. constructor
- 4. Object.prototype.toString.call()
- 5. type null会被判断为Object,为什么
- 6. typeof NaN的结果是什么
- 三、判断数组的方式有哪些
- 1. Object.prototype.toString.call()
- 2. 通过原型链判断
- 3. 通过ES6的Array.isArray()做判断
- 4. 通过instanceof判断
- 5. 通过Array.prototype.isPrototypeOf()
- 四、null和undefined的区别
- 五、instanceof操作符的实现原理及实现
- 六、为什么0.1+0.2 !== 0.3,如何让其相等
- 1. 原因
- 2. 如何解决
- 七、==操作符的强制类型转换规则
- 八、Object.is()与比较操作符“=== ”、“ ==”的区别
- 九、其他值到字符串的转换规则
- 十、其他值到数字值的转换规则
- 十一、其他值到布尔类型的值的转换规则
- 十二、isNaN和Number.isNaN函数的区别
- 十三、JavaScript中如何进行隐式类型转换
- 1. ToPrimitive
- 2. 隐式转换规则
- 3. +操作符什么时候用于字符串拼接
一、JavaScript有哪些数据类型
八种:Number String Boolean Null undefined Object Symbol BigInt
Symbol & BigInt 是ES6新增的数据类型
- Symbol 代表创建后独一无二且不可变的数据类型;主要是为了解决可能出现全局变量冲突的问题
- BigInt是一种数字类型,可以表示任意精度格式的整数,使用BigInt可以安全地存储和操作大整数,即使这个数超出Number能够表示的安全整数范围
分类:原始数据类型 & 引用数据类型
栈:Number String Boolean Null Undefined(原始数据类型)
堆:Object(引用数据类型)(对象、数组、函数)
两种类型的区别在于存储位置的不同:
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
数据结构和操作系统中
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
● 在数据结构中,栈中数据的存取方式为先进后出。
● 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
● 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
● 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
二、数据类型检测的方法
1. typeof
注:数组、对象、null都会被判断为Object
2. instanceof
可以正确判断对象的类型,不能判断基本数据类型;可以用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性
内部运行机制是判断在其原型链中能否找到该类型的原型
3. constructor
判断数据的类型;对象实例通过constructor对象访问它的构造函数;如果创建一个对象来改变他的原型,constructor就不能用来判断数据类型了
4. Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
5. type null会被判断为Object,为什么
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
有两种特殊数据类型:
● undefined的值是 (-2)30(一个超出整数范围的数字);
● null 的值是机器码 NULL 指针(null 指针的值全是 0)
在 JavaScript 中二进制前三位都为 0 的话会被判断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回 object。
6. typeof NaN的结果是什么
返回的结果:number
NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。
三、判断数组的方式有哪些
1. Object.prototype.toString.call()
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
2. 通过原型链判断
obj.__proto__ === Array.prototype;
3. 通过ES6的Array.isArray()做判断
4. 通过instanceof判断
5. 通过Array.prototype.isPrototypeOf()
四、null和undefined的区别
undefined代表的含义是未定义,当一个变量没有任何数据时也是undefined,undefined是原始数据类型之一,可以直接把undefined赋值给变量或函数返回;被迫的替代方案
null是空对象,需要主动使用,没有声明不会出现;空
判断null和undefined时需要使用严格判断(===)
五、instanceof操作符的实现原理及实现
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
六、为什么0.1+0.2 !== 0.3,如何让其相等
1. 原因
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100…(1100循环),0.2的二进制是:0.00110011001100…(1100循环),这两个数的二进制都是无限循环的数。
但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。
根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004。
- 第一部分(蓝色):用来存储符号位(sign),用来区分正负数,0表示正数,占用1位
- 第二部分(绿色):用来存储指数(exponent),占用11位
- 第三部分(红色):用来存储小数(fraction),占用52位
2. 如何解决
一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3
七、==操作符的强制类型转换规则
- 一个布尔值,将其转换为数字比较
- 一个字符串,一个数字、布尔值或null,将字符串转换为数字比较。
- 一个对象,一个字符串、数字或符号,将对象转换为原始值比较。
对象转换为原始值的规则如下:
- 如果对象有valueOf方法并返回基本类型的值,则使用该值进行比较。
- 如果valueOf方法返回的仍然是对象,则调用对象的toString方法,并使用返回的字符串进行比较。
- 如果对象没有valueOf和toString方法,或者返回的不是基本类型的值,则抛出TypeError异常。
- 一个null,一个undefined,则相等。
- 一个NaN,一个不是NaN,则相等
- 一个数字0,一个字符串且内容为非空,则将数字0转换为字符串再进行比较。
- 其他情况下,将操作数都转换为数字再进行比较。
需要注意的是,如果两个都是对象,则比较的是它们的引用是否相等,不会进行强制类型转换。
八、Object.is()与比较操作符“=== ”、“ ==”的区别
- 使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
- 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
- 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
九、其他值到字符串的转换规则
- Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”,
- Boolean 类型,true 转换为 “true”,false 转换为 “false”。
- Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
- Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
- 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
十、其他值到数字值的转换规则
- Undefined 类型的值转换为 NaN。
- Null 类型的值转换为 0。
- Boolean 类型的值,true 转换为 1,false 转换为 0。
- String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
- Symbol 类型的值不能转换为数字,会报错。
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()吗方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
十一、其他值到布尔类型的值的转换规则
假值: undefined null false +0、-0 和 NaN “”
假值的布尔强制类型转换结果为false;从逻辑上说,假值表以外的应该都是真值
十二、isNaN和Number.isNaN函数的区别
- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true,会影响 NaN 的判断。
- 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
十三、JavaScript中如何进行隐式类型转换
1. ToPrimitive
JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,
// @obj 需要转换的对象
// @type 期望的结果类型
ToPrimitive(obj,type)
type的值为number或者string。
(1)当type为number时规则如下:
● 调用obj的valueOf方法,如果为原始值,则返回,否则下一步;
● 调用obj的toString方法,后续同上;
● 抛出TypeError 异常。
(2)当type为string时规则如下:
- 调用obj的toString方法,如果为原始值,则返回,否则下一步;
- 调用obj的valueOf方法,后续同上;
- 抛出TypeError异常。
默认情况下:
- 如果对象为 Date 对象,则type默认为string;
- 其他情况下,type默认为number。
Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN
2. 隐式转换规则
隐式类型转换主要发生在+、-、*、/以及==、>、<这些运算符之间
- +操作符的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
- -、*、\操作符NaN也是一个数字
- ==操作符两边的值都尽量转成number
- <>如果两边都是字符串,则比较字母表顺序;其他情况下,转为数字再比较;对象会被ToPrimitive转换为基本类型再进行转换
3. +操作符什么时候用于字符串拼接
如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法
除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
|| 和 && 操作符的返回值