数据类型篇
1、JavaScript有哪些数据类型,它们的区别是什么?
基本数据类型:number、string、boolean、undefined、NaN、BigInt、Symbol
引入数据类型:Object
NaN是JS中的特殊值,表示非数字,NaN不是数字,但是它的数据类型是数字,它不等于任何值,包括自身,在布尔运算时被当做false,NaN与任何数运算得到的结果都是NaN,计算失败或者运算无法返回正确的数值的情况下就会返回NaN。一些数学函数的运算结果也会出现NaN。
其中Sysmbol和BigInt是ES6中新增的数据类型:
(1)Symbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
(2)BigInt是一种数字类型的数据,它可以表示任意精度格式的整数,使用BigInt可以安全地存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围。
以上这些数据类型可以分为原始数据类型和引用数据类型
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)
2、基本数据类型和引用数据类型的区别
1、基本数据类型是按值访问的、也就是说我们可以操作保存在变量中的实际的值。
2、基本数据类型的值是不可变的,任何方法都无法改变一个基本类型的值,当这个变量重新赋值后看起来变量的值是改变了,但是这里的变量名只是指向变量的一个指针,所以改变的是指针的指向改变,该变量是不变的,但是引用类型可以改变。
3、基本数据类型不可以添加属性和方法,但是引用类型可以。
4、基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制搭配为新变量分配的位置上,引用数据类型的赋值是对象引用。
5、基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同。
6、基本数据类型是存放在栈区的,引用数据类型同时保存在栈区和堆区。
3、数据类型检测的方法有哪些?
(1)typeof
console.log(typeof 2); //numberconsole.log(typeof true); //booleanconsole.log(typeof 'str'); //stringconsole.log(typeof []); //objectconsole.log(typeof function(){}); //functionconsole.log(typeof {}); //objectconsole.log(typeof undefined); //undefinedconsole.log(typeof null); //object
其中数组、对象、null都会被判断为object,其他判断都正确。
(2)instanceof
instanceof可以正确判断对象的类型,其内部运行机制是判断再起原型链中能否找到该类型的原型
console.log(2 instanceof Number); //falseconsole.log(true instanceof Boolean); //falseconsole.log('str' instanceof String); //falseconsole.log([] instanceof Array); //trueconsole.log(function(){} instanceof Function); //trueconsole.log({} instanceof Object); //true
由以上代码可以看出,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。
(3)constructor
console.log((2).constructor === Number); //trueconsole.log((true).constructor === Boolean); //trueconsole.log(('str').constructor === String); //trueconsole.log(([]).constructor === Array); //trueconsole.log((function(){}).constructor === Function); //trueconsole.log(({}).constructor === Object); //true
constructor有两个作用,一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意的是:如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了。
function Fn(){}Fn.prototype = new Array();var f = new Fn();console.log(f.constructor === Fn); //falseconsole.log(f.constructor === Array); //true
(4)Object.prototype.toString.call()
Object.prototype.toString.call()使用Object对象的原型方法toString来判断数据类型。
var a = Object.prototype.toString;console.log(a.call(2)); //[object Number]console.log(a.call(true)); //[object Boolean]console.log(a.call('str')); //[object String]console.log(a.call([])); //[object Array]console.log(a.call(function(){})); //[object Function]console.log(a.call({})); //[object Object]console.log(a.call(undefined)); //[object Undefined]console.log(a.call(null)); //[object Null]
同样是检测对象obj调用toString方法,obj.String()的结果和Object.prototype.toString.call(obj)的结果却不一样,这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串...),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
4、null和undefined的区别
undefined:未定义的值
得到undefined的方式:
1、声明一个变量,但没有赋值
2、访问对象上不存在的属性
3、函数定义了形参,但没有传递实参
4、使用函数的返回值,当没有return操作时,就默认返回一个原始的状态值,这个值就是undefined,表名函数的单回执未被定义。
5、使用void对表达式求值 ECMAScript明确规定void操作符,对任何表达式求值都返回
null:代表空值
区别:undefined表示一个变量自然的、最原始的状态值,而null则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的的语义,不要对一个变量显式的赋值undefined,当需要释放一个对象时,直接赋值为null即可。
当对这两种类型使用typeof进行判断时,Null类型化会返回‘object’,这是一个历史遗留问题,当使用‘==’对这两种类型的值进行比较时会返回true,使用‘===’时会返回false。
5、为什么typeof null得到object而不是null?
typeof null的结果是Object。因为JavaScript中不同对象在底层都表示为二进制,二JavaScript中会把二进制前三位都为0的判断为object类型,而null的二进制表示全都是0,自然前三位也是0,所以执行typeof时会返回‘object’。
在JavaScript第一个版本中,所有值都存储在32位的单元中,每个单元包含一个小的类型标签(1-3 bits)以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有5种数据类型:
000:object -当前存储的数据指向一个对象
1:int -当前存储的数据是一个31位的有符号整数
010:double -当前存储的数据指向一个双精度的浮点数
100:string -当前存储的数据指向一个字符串
110:boolean -当前存储的数据是布尔值
如果最低位是1,则类型标签标志位的长度只有一位;如果最低位是0,则类型标签标志位的长度占3位,为存储其他四种数据类型提供了额外两个bit的长度。
但是有两个特殊的数据类型:
undefined的值是(-2)30(一个超出整数范围的数字)
null的值是机器码NULL指针(null的指针的值全是0)
也就是说null的类型标签也是000,和object的类型标签一样,所以会被判定为Object。
6、为什么null==undefined 得到true 但是null===undefined得到 false ?
要比较相等性之前,不能将null和undefined转换成其他任何值,但null == undefined会返回true。ECMAScript规范中是这样定义的。
原因:null:Null类型,代表‘空值’,代表一个空对象指针,使用typeof运算得到‘object’,所以你可以认为它是一个特殊的对象值。
undefined:Undefined类型,当一个声明了一个变量未初始化时,得到的就是undefined。
实际上,undefined值是派生自null值的,ECMAScript标准规定对二者进行相等性测试要返回true。
7、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);}
}
8、为什么0.1+0.2 !== 0.3,如果让其相等?
在开发过程中遇到类似这样的问题:
let n1 = 0.1,n2=0.2
console.log(n1+n2) //0.30000000000000004
这里得到的不是想要的结果,如果要想等于0.3,就要进行转换:(n1+n2).toFixed(2)
toFixed(num)方法可以把Number四舍五入为指定小数位的数字。但是出现这样的结果其实是因为计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.00010011001100...(1100循环),0.2的二进制是0.00110011001100...(1100循环),这两个数的二进制都是无限循环的数。而JavaScript处理无限循环的二进制小数时,一般我们认为数字包括小数和整数,但是在JavaScript中只有一种数字类型Number,它的实现遵循IEEE 754标砖,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从‘0舍1入’的原则。
根据这个原则,0.1和0.2的二进制数相加,再转换为十进制数就是0.30000000000000004
而对于这种情况,最直接的解决方法就是设置一个误差范围,通常称为‘机器精度’。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 === 0.3
function NumberEpsilon(arg1,arg2) { return Math.abs(arg1-arg2) < Number.EPSILON;
}
console.log(NumberEpsilon(0.1+0.2,0.3)); //true
9、如何获取安全的undefined值?
因为undefined是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响undefined的正常判断。表达式void___没有返回值,因此返回结果是undefined。void并不改变表达式的结果,只是让表达式不返回值。因此可以用void()来获得undefined。
10、typeof NaN的结果是什么
NaN的意思是不是一个数字(Not a number),NaN是一个警戒值,用于指出数字类型中的错误情况,即执行数学运算但是没有成功,这是失败后返回的结果。
console.log(typeof NaN); //number
NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即x===x不成立)的值。而NaN !== NaN为true
11、isNaN和Number.isNaN函数的区别
- 函数isNaN接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的值都会返回true,因此非数字的值传入也会返回true,会影响NaN的判断
- 函数Number.isNaN会首先判断传入的参数是否为数字,如果是数字再继续判断是否为NaN,不会进行数据类型的转换,这种方法对于NaN的判断更为准确。
12、==操作符的强制类型转换规则
对于 == 来说,如果对比双方的类型不一样,就会进行类型转换。假如对比x和y是否相同,就会进行如下判断流程:
1、首先会判断两者类型是否相同,相同的话就比较两者的大小
2、类型不相同的话,就会进行类型转换
3、会先判断是否在对比null和undefined,是的话就返回true
4、判断两者类型是否为string和number,是的话就会将字符串转换为number
1 == ‘1’
1 == 1
5、判断其中一方是否为boolean,是的话就会把boolean转为number再进行判断
‘1’ == true
‘1’ == 1
1 == 1
6、判断其中一方是都为object且另一方为string、number或者symbol,是的话就会把object转为原始类型再进行判断
‘1’ == {name:'js'}
'1' == '[object.Object]'