最近面试,JS 基本数据类型这块被完虐了……
前言
最近去面试了几个公司,发现自己被面试官在js基本数据类型这块知识完虐,发现自己js基础不扎实 包括自己作为面试官去面试别人,发现一个很大的问题,能力是很不错的,但是js基础扎实不够扎实 很多人可能认为能力比基础更重要答案是否定的,引用网上比较流行的话
基础很重要,只有基础好才会很少出
bug,大多数的都是基础不扎实造成的
这里给出几个问题,要是如果都能回答对并且知道为什么(本文章可以选择忽略)
- 基本数据类型和引用数据类型有什么区别
- 字符串长度为什么确定了就不能再修改
- 0b1, 0x1 为什么表示的是数字,不是字符串
- 为什么基本数据类型是存储在栈中,引用数据类型是存储在堆中的
- 什么时候使用
null,什么时候使用undefined - 给基本数据类型定义了方法和属性不生效?
- 假值对象是什么
typeof是根据什么原理区分数据类型的- 0.1 + 0.2 为什么不等于0.3
- 对象数据类型什么时候
valueOf优先于toString - 对象类型什么时候
toString优先于valueOf - obj + 1 如何才能等于2
在开始之前,在这里强调一下处于某个块中的知识点以及红色字体标记是非常重要的知识点在面试过程中高频出现以及容易出错的地方,在阅读过程一定要自己亲自尝试加深记忆而不是只看不敲代码
数据类型
在js中根据赋值操作是赋值的是值还是内存地址,可以分为基本数据类型和对象数据类型(引用数据类型)
数据类型在数据结构中的定义是一组性质相同的值的集合以及定义在这个值集合上的一组操作的总称
基础数据类型和对象数据类型的区别如图所示
基本类型
js中基本数据类型主要包括Number,String,Boolean,Undefinde,Null,Symbol, BigInt
Number
该类型包括整数和小数。数字类型用于存储数值,是不可变类型,如果改变数字类型的值,将重新分配内存空间。
number用二进制(0b),八进制(0o),十进制,十六进制(0x)来表示, 由于受内存限制,最小值为5e-324,最大值为1.7976931348623157e+308,如果超过了最大值会显示为Infinity, 超过了最小值会显示为—Infinity,表示的最大整数为2^53 - 1
- number类型EE754格式来表示其整数和浮点数值, 浮点数的精度最大为17位,大于17位会发生精度丢失,这也是导致 0.1 + 0.2不严格等于0.3的原因
- 如果该数大于进制,有些浏览器会将表示进制的前导符忽略默认十进制,但是有些浏览器会报错,chrome浏览器会报错
- 可以通过
Number.MAX\_VALUE访问最大值和Number.MIN\_VALUE访问最小值
var num1 = 0123 // 十进制
var num2 = 0b1011 // 二进制
var num3 = 0b112011 // 二进制最高为1,会报错: Invalid or unexpected token
var num4 = 0o12376 // 八进制
var num5 = 0o7899222 // 八进制最高为7,会报错: Invalid or unexpected token
var num6 = 0x2177 // 十六进制
var num7 = 0xEFR // 十六进制最高为F,会报错: Invalid or unexpected token
var num8 = .5 // 浮点数,可以省略前面的0
var num9 = 0.5 //浮点数
NaN即非数值,是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况
NaN有2个特殊的特点
- 涉及到NaN的操作都会返回NaN
- NaN和任何值都不相等,通过`isNaN函数来判断是不是NaN
String
该类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由双引号(“)或单引号(‘)表示,因此这两种字符串的写法都是有效的
string 类型比较特殊是存储在常量池中的, 常量池也算栈的一块内存空间,//todo 想要深入了解常量池到可以参考JS 的字符串如何分配内存这篇文章
var str = 'test' // 单引号字符串
var str1 = "test" // 双引号字符串
var str2 = 'test" // 非合法字符串
Boolean
该类型的字面值只有true和false是区分大小写的。也就是说.True和False(以及其他的混合大小写形式)都不是Boolean值,只是标识符
var isSame = true // 布尔值为true
var isValidate = false // 布尔值为 false
var isNotBoolean = True // 不是布尔值,这是一个变量
Null
该类型的字面量只有一个值,即null, 从逻辑上来讲null值表示一个空对象指针,这也正是使用typeof 操作符检测null时会返回object的原因
- 通常在声明某个变量为对象类型时。默认初始化该值为null
- null == undefined为 true, null === undefined 为 false
var obj = null // Null的字面量为null
Undefined
该类型只有一个值,即特殊的undefined, 在使用let,var关键词声明变量并为初始化时,这个变量的值就是undefined
由于
undefined作为window的全局属性,老版本浏览器可以进行修改,因此未获取正确的undefined时通常长长使用void 0, 不知道为什么void 0 可以获取安全的undefined,可以参考js冷知识void 0是什么?为什么比undefined好用?这篇文章
var a // undefined
var b = undefined // Undefined的字面量为undefined
Bigint
它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数, 通过数字后导n来进行创建,在实际开发中应用场景也比较少,就不详细介绍了,感兴趣的可以参考MDN
目前没有完美的兼容库来支持那些不能原生支持的浏览器, 兼容性比较差,实际应用场景也比较少
const bigInter = 1n // 创建了一个BigInt类型的字面量
引用数据类型 (object 类型)
对象类型其实就是一组数据和功能的集合,对象可以通过执行new操作符来创建Object类型的实例并为其添加属性或者方法,实际开发中我们比较常用通过字面量的形式来创建对象,对象的属性名只能是字符串类型或symobl类型,如果不是字符串类型或者symbol类型,那么会隐性转为字符串类型
对象中比较重要的考点,任何对象类型都会有这3个方法
toString: 用来返回对象的字符串表示
valueOf: 用来返回对象的数值类型表示,通常情况下和toString()的返回值相同
constructor: 保存着用于创建当前对象的函数
这3个方法一定要记住,在类型转换中以及在面试中都是比较高频的考点
var obj = new Object() // 通过new操作来创建对象
// 给对象添加方法
obj.getAge = function () {
return 1
}
// 给对象添加属性
obj.age = 1
obj[Symbol('a')] = 2
Symbol
由于对象的属性或者方法存在可覆盖性,这就容易造成因为命名冲突而导致的覆盖,symbol能够保证每个属性或者方法的名字是独一无二的
在Es6之前,无论对象的属性还是方法都是字符串类型进行保存,因此很容易引起命名冲突 symbol 本质上是一个特殊的对象类型,也是不能使用new操作符来进行创建的唯一对象
var = Symbol('a') // 创建一个symbol的数据类型
//
var b = new Symbol('b') // Symbol is not a constructor at new Symbol
var obj = {}
obj[Symbol('a')] = 1
obj[Symbol('a')] = 2 // 对象的属性不会被覆盖
判断数据类型
上面我们对js常见的数据类型也做了简单理解,本节我们将学习如何使用typeof关键词来进行类型判断以及typeof能够进行类型判断的原理
js是一门弱语言(变量可以随时持有任何类型的值),它在声明变量时无需确定变量的类型,js在运行时会自动判断, 想要了解js运行时以及运行的环境可以参考JS runtime environment)
typeof 作用于各种数据类型的返回值,如图所示
typeof 底层原理
不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。具体存储信息如图所示
观察上面的表格我们可以获取很多有用的信息比如为什么typeof null 返回的是object,因为object类型是通过000进行存储
Symbol和BigInt的底层存储
- Symbol是使用c++实现的特殊对象,正常来讲的话应该也是000,具体的前三位存储信息的看源代码,有兴趣的话可以看一下源码chrome V8源码
- BigInt其实是比较特殊的整数,因此bigInt的前三位也是100,但是typeof如何去区分的话,有兴趣的可以看一下源码chrome V8 typeof具体实现逻辑
判断对象数据类型
在实际使用过程中可能存在对对象数据类型的判断,我们可以使用constructor以及instanceof以及Object.prototype.toString.call,由于篇幅有限就不具体展开有兴趣的可以去参考[对象类型判读]这篇文章
数据类型之间转换
数据类型之间的转换不管在面试过程中还是实际开发过程使用的频率还是比较高的,因此这一块知识还是相当比较重要的
Number类型与其他类型之间的转换
在js中有三个函数可以把非数值类型转换为数值分为别为parseInt()、parseFloat()、Number(),前面2个转换规则比较简单,这里主要讲一下Number的转换规则
- Boolean类型,true转换为1,false转换为0
- Undefined类型,转换为NaN
- Null类型转换为0
- 如果是字符串的话有以下规则
- 如果字符串中只包含数字那么将其转换为对应的字面量
- 字符串如果是空字符串那么转换为0
- 不符合以上规则的转换为NaN
- Object类型
- 首先调用
valueOf方法,如果返回的是基本数据类型,那么再使用以上基本数据类型的转换规则 - 如果valueOf方法返回的不是基本数据类型,那么会调用toString方法,如果返回的是基本数据类型,那么再使用以上基本数据类型的转换规则
- 如果
toString方法返回的不是基本数据类型,那么会报错
- 首先调用
- Symbol无法转换为number类型
- BinInt会忽略后面的n
字符串中有前导0,那么会忽略前导0 Object转换为Number类型,本质上调的是
Symbol.toPrimitive方法,我们可以重写该方法从而实现自己转换规则 实际开发过程中我们比较喜欢使用+隐性转换将其他类型转换为Number类型
var num1 = Number('1223') // 123
var num2 = Number('123aqq') // NaN
var num3 = Number('') // 0
var num4 = Number(true) // 1
var num5 = Number(false) // 0
var num6 = Number(null) // 0
var num7 = Number(undefined) // NaN
var num8 = Number(Symbol('a')) // Cannot convert a Symbol value to a number at Number
var num9 = Number(2n) // 2
var obj = {}
var num10 = Number(obj) // NaN
// 重写valueOf方法
obj.valueOf = function () {
return 3
}
var num11 = Number(obj) // 3
obj.toString = function () {
return 2
}
var num12 = Number(obj) // 2
// 重新改写toString
obj.toString = function () {
return {}
}
var num12 = Number(obj) // Cannot convert object to primitive value at Number
String类型与其他类型之间的转换
在js中主要使用toString()、String()来进行将其他类型转为String类型, 两者转换规则相同
- Number类型直接转为对应的数字前面和后面分别加上引号
- Boolean类型转换为对应的布尔值前面和后面加上引号
- Undefined类型转换为对应的undefined前面和后面加上引号
- Null类型转换为对应的undefined前面和后面加上引号
- Object类型
- 首先调用
toString方法,如果返回的是基本数据类型,那么再使用以上基本数据类型的转换规则 - 如果toString方法返回的不是基本数据类型,那么会调用valueOf方法,如果返回的是基本数据类型,那么再使用以上基本数据类型的转换规则
- 如果
valueOf方法返回的不是基本数据类型,那么会报错
- 首先调用
- Symbol类型转换为对应symbol('xx')前面和后面加上引号
- BigInt转换时会忽略末尾的n,前面和后面加上引号
在转换为数字类型时优先调用好的是valueOf,而在这里优先调用的是toString,千万别记混了 一般来说更多的是String()方法因为可以兼容null和undefined 数字的toString方法能够支持传入一个参数,表示将数字转换为什么进制
var str1 = String(222) // '222'
var str2 = String(true) // 'true'
var str3 = String(undefined) // 'undefined'
var str4 = String()
var str5 = String(Symbol('a')) // 'Symbol('a')'
var str6 = Number(2n) // '2'
var obj = {}
var str7 = String(obj) // NaN
// 重新改写toString
obj.toString = function () {
return '2222'
}
var str8 = Number(obj) // '2222'
obj.toString = function () {
return {}
}
// 重写valueOf方法
obj.valueOf = function () {
return 'wwwr'
}
var str9 = String(obj) // wwwr
// 重新改写toString
obj.toString = function () {
return {}
}
var num12 = String(obj) // Cannot convert object to primitive value at String
Boolean类型与其他类型之间的转换
在js中主要使用Boolean()来进行将其他类型转为String类型, 两者转换规则相同
- Number类型
- 值为0转换为false
- 值为NaN转换为false
- 其他任何值转换为true
- String类型
- 字符串长度为0转换false
- 字符串只要长度不为0转换为true
- Null类型转换为false
- Undefined转换为false
- Object类型转换为true
- Symbol转换为true
- BigInt类型
- 0n转换为false
- 其他任何值为true
其他类型转换为Boolean类型本质上是执行的内部的
toBoolean方法,该方法不可见
var boolean1 = Boolean(0) // false
var boolean2 = Boolean(1) // true
var boolean3 = Boolean(NaN) // false
var boolean4 = Boolean('') // false
var boolean5 = Boolean('boolean') // true
var boolean6 = Boolean(undefined) // false
var boolean7 = Boolean(null) // false
var obj = {}
var boolean8 = Boolean(obj) // true
var boolean9 = Boolean(Symbol('a')) // true
var boolean10 = Boolean(0n) // false
var boolean11 = Boolean(1n) // true
对象类型和其他类型之间的转换
其他数据类型可以通过new操作符加上首字母大写的构造函数来创建对象,除null和undefined以外或者使用 new + Object()来创建对象
-
Number类型: new + Number(xxx)
-
String类型 new + String(xxx)
-
Boolean类型 new + Boolean(false | true)
-
Undefined没有对应的构造函数,可以采用 new Object(undefined)
-
Null类型没有对应的构造函数, 可以采用 new Object(undefined)
-
Symbol类型 没有对应的构造函数,可以采用 new Object(Symbol(xx))
-
BigInt类型 new + BigInt(xxx)
以上首字母大写的构造函数也叫做某某类型的包装类型,重写了Object对应的ValueOf和toString方法,返回值为传入构造函数对应的值 在js中也存在一些假值对象,当然本身这是不合理的比如
document.all对象,为什么这么是因为ie存在使用一些注释来判断ie环境
var obj1 = new Number(222)
var obj2 = new String('str')
var obj3 = new Boolean(true)
var obj4 = new Object(undefined)
var obj5 = new Object(null)
var obj6 = new Object(Symbol('a'))
var obj7 = new BigInt(2n)
回答开篇
在看本节时经过自己前面的学习看看自己现在是否能够进行回答开篇问题
- 基本数据类型和引用数据类型有什么区别
根据赋值操作是否赋值为地址值还是真正的值 - 字符串长度为什么确定了就不能再修改
字符串是基础数据类型,数据类型在声明的时候就确定了内存大小 0b1,0x1为什么表示的是数字,不是字符串
js数字类型二进制,八进制的字面量表示方法- 为什么基本数据类型是存储在栈中,引用数据类型是存储在堆中的
基本数据类型大小确定,引用数据类型大小不确定 - 什么时候使用null,什么时候使用undefined 某个对象将被赋值为对象类型时,将该变量进行初始化为
null,undefined不需要赋初始值,只声明,默认为undefined - 给基本数据类型定义了方法和属性不生效?
基本数据类型在使用属性或者方法时会先转换为对应的包装类型,但很快该对象就别销毁了,下一次访问某个属性本质上是又重新重新创建了一个新的对象 - 假值对象是什么IE浏览器自己作妖,为了做一些特殊操作,遗留下来的问题
- typeof是根据什么原理区分数据类型的
typeof根据底层存储二进制前三位来区分类型 - 0.1 + 0.2 为什么不等于0.3
数字采用EE754来存储,导致在存储的过程中精度丢失 - 对象数据类型什么时候
valueOf优先于toString
在转换为数字类型或者在进行数学运算时 - 对象类型什么时候
toString优先于valueOf
在转换为字符串类型或者在进行字符串拼接时 - obj + 1 如何才能等于2
改写该对象的valueOf方法使其返回值为1或者改写Symbol.toPrimitive