这个问题基本回答:
原始类型有:string/number/boolean/null/undefined/symbol,总共六类
其中包含了ES6新增的symbol,以及ES10新增的BigInt
回答出上面的答案,这个问题其实你仅仅只是回答了最浅层的答案,下面我们来看下由这个问题引申出来的扩展。
扩展1 :null 是对象么?
null 不是对象。虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
扩展2:原始类型和对象类型的异同?
在 JS 中,除了原始类型那么其他的都是对象类型了。
对象类型和原始类型的区别:存储方式的不同。原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。
扩展3:函数参数是对象的时候,会发生什么?
这个地方大家可能一下想不起来这里要考察的是什么?
我们先来看个例子:
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // 输出什么?
console.log(p2) // 输出什么?
上面输出什么呢?
首先,函数传参是传递对象指针的副本,
到函数内部修改参数的属性这步,p1的值也被修改了,
但是我们重新为person分配了一个对象时就出现了分歧,
所以最后person拥有了一个新的指针,也就和p1没有任何关系了,导致最终两个的值是不一样的。
扩展4:如何正确的判断类型?
判断类型的方法有:typeof/instanceof/Object.prototype.toString.call() ,那这些方法的优缺点又是什么呢?
- typeof:typeof 不能准确的判断类型
typeof 对于原始类型来说,除了 null 都可以显示正确的类型,typeof null 结果是 object
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
- instanceof
如果想要判断一个对象的正确类型,可以使用instanceof,instanceof判断的原理是通过原型链来判断:即判断实例对象的__proto__ 和 构造函数的prototype是不是统一引用。
( instanceof用法: x instanceof Function 判断x是否是函数,也就是左边变量的隐式原型是否等于右边的显式原型)
如果想用instanceof来判断原始类型的话,我们可以利用hasInstance自己实现:
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
// 使用
console.log('test' instanceof PrimitiveString) // true
- Object.prototype.toString.call()
这个方法可以准确的判断原始类型和对象类型的正确类型,这里着重给大家介绍一下这个方法的原理,便于大家理解。
首先,我们先明确一下toString这个方法
let test = {name: 'test'}
console.log(test.toString())
上面代码,大家第一印象可能是输出 {name: 'test'} 的字符串形式,但其实输出的是
这是为什么呢?
这里就不得不说到我们的原型和原型链了。接下来我们来分析一下:
首先,test 这个变量本身是没有toString这个方法的,所以,当我们调用toString方法的时候,就会沿着原型链向上查找,所以找到的是test原型链上的toString方法,也就是:
这个原型链上的方法又是哪里来的呢?
实例对象的隐式原型等于构造函数的显式原型:即
我们知道,JS的大原型其实是Object构造函数的prototype指向的那个Object,JS的世界的来源就是它。
这个toString才是原汁原味的toString,它的返回一直是
一个字符串个格式的[object xxxx] xxxx代表着调用这个方法的“数据”的数据类型。
到这里大家都明白了我们的toString 返回的正确格式到底是什么,又是来自于哪里,但有些同学可能就会问,那为什么有些数据类型的toString方法返回的是变量的字符串形式呢?
举例:
上面定义了一个函数变量,为什么toString返回的是变量的字符串形式呢?
原因:JS其实重写了某些数据类型的toString方法
有些同学可能会产生疑虑,怎么确定调用的toString方法是重写的那个,还是原汁原味的那个呢?
可以跟着我继续看下去
上面我们说过了,JS的大千世界,最后的大原型其实是Object构造函数的prototype指向的那个Object。
所以,为了验证我们的Function的toString方法到底来自哪里,我们把Function上的toString方法删除,看看会发生什么?
(这里说明一下,变量func 本身是没有toString方法的,所以,就会沿着原型链上面去找,也就是Function)
看到这里想必大家都明白了
我们把Function上的toString方法删掉之后,就会沿着原型链上继续查找toString方法,继而也就找到了我们原汁原味的那个toString方法,因此最后输出的结果是'[object Function]'了
因此,Function类型上其实是重写了toString方法的。
那为什么Function会继续往上查找呢?
这里我们再来回顾一下原型链接:实例对象的隐式原型(__proto__)等于构造函数的显式原型(prototype),也就是
所以呢,当我们把Function.prototype上的toString方法删掉之后,就会去找Function.prototype.__proto__上的toString方法,那也就是我们的Obejct.prototype上的toString方法了。
(其实我们最终的原型链的顶端是null,也就是Object.prototype.__proto__,当查找方法的时候,会一直沿着原型链上查找,直到查找到为止,如果一直没有查找到,那就会在Object.prototype.__proto__结束,返回null)
到这里大家应该就知道了,我们为什么使用Obejct.proptoype.toString这个方法来判断类型了,因为,它是JS世界的来源,是我们原汁原味的方法。
这里大家可能又会问,我们在判断的时候总不能先把类型上的toString方法删掉,再判断吧?
那肯定是不行的,所以,我们的 call 方法就用上了
采用call方法去改变Object.prototype.toString方法this的指向。
最终我们就得到了 Object.prototype.toString.call()方法来判断我们的类型了,这也是最为准确的方法,建议大家使用这个方法
扩展5:原始类型和对象类型在内存中的存储方式?
- 对于原始类型,名字和值都存在栈内存中。
- 对于对象类型,名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。
总结:
- 原始类型包括:string/number/boolean/null/undefined/symbol/BigInt
- null不是对象
- 原始类型和对象类型最主要的区别在于存储方式的不同:原始类型存储的是值,对象类型存储的是地址
- 使用Object.prototype.toString.call()判断类型是最准确的
提问:
- 判断数据类型的方法都有哪些?
- instanceof 可以判断基本类型吗?
- typeof 函数 会返回什么?
大家可以思考下,再遇到上面的问题,你回答的思路是什么呢?