数据类型
# 介绍一下js
的数据类型和区别特别重要
七种基本数据类型:undefined
null
string
number
bigInt
boolean
symbol
一种引用数据类型:object
(包含普通对象-Object
,数组对象-Array
,正则对象-RegExp
,日期对象-Date
,数学函数-Math
,函数对象-Function
)
其中es6
新增的是symbol
和bigint
symbol
代表创建后独一无二不可变的数据类型 为了解决可能出现的全局变量冲突bigint
是一种数字类型的数据 可以表示任何精度格式的整数,可以安全存储操作大整数,可以超过number
能够表示的安全整数范围
他们分别存放在
栈:原始数据类型(
undefined
null
boolean
number
string
)- 占据空间少 大小固定 属于频繁被使用数据 所以放在栈中
- 栈区内存由编译器自动分配释放 存放函数的参考值 局部变量的值
堆:引用数据类型(
object
array
function
)- 占据空间大 大小不固定 引用数据在栈中存储了指针指向堆中实体的起始地址
- 由开发者分配释放 若开发者不释放程序结束时由垃圾回收机制回收
堆:
JavaScript
的闭包- 在编译过程中,如果
JavaScript
引擎判断到一个闭包,会在堆空间创建换一个“closure(fn)”
的对象(这是一个内部对象,JavaScript
是无法访问的),用来保存闭包中的变量
- 在编译过程中,如果
# 数据检测方法有哪些特别重要
typeof
其他解析都正确 除了数组 对象 null
Regexp
Date
会被判断成object
值得注意的是
typeof function() {} === 'function'
为什么null
会被解析成object
呢
原因
在最初的js
是32
位操作系统,所有值都存储在32
位的单元里,每个单元包含一个小的类型标签(0-3bit)
以及当前要存储值的真实数据,类型标签存储在每个单元的低位
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
2
3
4
5
对象的标签值为000
,而null
则是空指针全是0
,所以typeof
null
是object
对象被赋值了null
以后,对象对应的堆内存中的值就是游离状态了,GC
会择机回收该值并释放内存。 因此,需要释放某个对象,就将变量设置为 null
,即表示该对象已经被清空,目前无效状态
instanceof
可以判断对象的类型,内部机制是在其原型链上是否找到该类型的原型
只能正确判断引用类型 不能判断基本数据类型
引申:
可以判断一个对象在其原型链上是否存在一个构造函数的prototype
属性
值得注意的是
将数字转成包装类型
let a = new Number(12)
a instanceof Number // true
此外
undefined instanceof undefined
和 null instanceof null
会报错
Uncaught TypeError: Right-hand side of 'instanceof' is not an object
constructor
都能判断出来,其作用一是判断数据类型
二是对象实例通过constructor
对象访问他的构造函数,如果创建一个对象来改变他的原型 constructor
就不能判断他的数据类型了
Object.prototype.toString.call()
# 为什么Object.toSting
和Object.prototype.toString.call()
返回的结果不一样呢
因为toString
是object
的原型方法,而array
function
作为object
的实例,重写了toString
方法(function
返回内容为函数体的字符串 array
返回的是元素促成的字符串),若想得到对象的具体类型 应该调用object
原型上的toString
# 判断数组的方法特别重要
- 通过
Object.prototype.toString.call()
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
- 通过原型链判断
obj.__proto__ === Array.prototype
es6
的isArray
Array.isArray(obj)
- 通过
instanceof
obj.instanceof Array === true
- 通过
Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj) === true
# null
和undefined
的区别特别重要
都是基本数据类型
undefined
代表未定义null
代表空对象,一般变量声明还没赋值或一个函数没有返回值是undefined
,null
主要是赋值给一些可能返回对象的变量作为初始化undefined
在js
中不是一个保留字 可以作为变量名 哈哈哈但是会影响对undefined
值的判断 可以通过void 0
来获得安全的undefined
值undefined == undefined
undefined === undefined
null === null
null == null
在验证
null
时,一定要使用===
,因为==
无法分别null
和undefined
# 实现一个instanceof
原理是判断构造函数的prototype
是否出现在对象的原型链上
function myInstance(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 判断构造函数的prototype对象是否在对象的原型链上
while(true) {
if(!proto) return false
if(proto === right.prototype) return true
// 没找到就继续在其原型链向上找
proto = Object.getPrototypeOf(proto)
}
}
2
3
4
5
6
7
8
9
10
11
12
# 为什么0.1 + 0.2 !== 0.3
如何让他相等特别重要
先解决如何让其相等
(0.1 + 0.2).tofixed(2)
(0.1 * 10 + 0.2 * 10) / 10
因为计算机是通过二进制的形式存储数据 0.1
的二进制是0.000 1100 1100...(1100循环)
0.2
的二进制是0.00 1100 1100...(1100循环)
,js
遵循IEEE 754
标准,使用64
位固定长度表示(标准的双精度浮点数),最多保留52
位,加上前面的个位 就是保留53
位有效数字,剩余的舍去 那么0.1 + 0.2
相加再转换成十进制就是 0.30000000000000004
- 第一部分(蓝色):用来存储符号位
(sign)
,用来区分正负数,0
表示正数,占用1
位 - 第二部分(绿色):用来存储指数
(exponent)
,占用11
位 - 第三部分(红色):用来存储小数
(fraction)
,占用52
位
# ["1", "2", "3"].map(parseInt)
?重要
[1, NaN, NaN]
因为 parseInt
需要两个参数 (val, radix)
,其中radix
表示解析时用的基数
下面我们来分析一下['1', '2', '3'].map(parseInt)
;
1. parseInt('1', 0); // radix为0时,使用默认的10进制。
2. parseInt('2', 1); // radix值在2-36,无法解析,返回NaN
3. parseInt('3', 2); // 基数为2,2进制数表示的数中,最大值小于3,无法解析,返回NaN
parseInt('111', 2) // 7
2
3
4
# typeof NaN
是什么
NaN
不是一个数字,NaN
是一个警戒值(sentinel value
有特殊用途的常规值)用于指出数字类型的错误情况
typeof NaN
是number
NaN
是唯一一个非自反的值 NaN !== NaN
为true
# isNaN
和Number.isNaN
函数的区别
- 函数
isNaN
会尝试将参数转换为数值 任何不能转化为数值的都返回true
Number.isNaN
先会判断传入参数是否为数字,如果是数字会判断是不是NaN
不会进行数据类型转换
# ==
的强制类型转换规则
如果类型不一样 会进行类型转换
首先判断类型是否相同 相同则直接比较
不相同就进行类型转换
会对比是不是
null
和undefined
是的话返回true
判断两者是不是
string
和number
是的话字符串转换为number
判断两者是不是
boolean
和number
是的话将boolean
转换为number
- 判断两者一方是不是
Object
另一方为string
number
symbol
的一种 是的话将object
转为原始类型在判断
- 判断两者一方是不是
'1' == {name: 'js'}
↓
'1' == '[object Object]'
2
3
# 其他值是怎么转字符串的
null
和undefined
boolean
直接转null -> 'null'
undefined -> 'undefined'
false -> 'false'
true -> 'true'
number
类型的直接转换 不过那些极小极大的会使用指数symbol
直接转换,但只允许显式强制类型转换 使用隐式会出错对普通对象来说 除非自行定义的
tostring
否则会调用Object.prototype.toString()
# 其他值转number
的规则
undefined
转为NaN
null
转为0
boolean
中true
转为1
false
转为0
string
的话先隐式转换(相当于调Number()
) 如果包含非数字值的转换为NaN
空字符串为0
symbol
不能转换为数字对象(包括数组)会首先被转换为相应基本类型的值 如果返回非数字的基本类型值,再遵循上面的规则强制转换为数字
转换为相应基本类型的值的意思是 通过内部操作
DefaultValue
检查是否有valueOf()
方法,如果有就返回基本类型值进行转换为number
没有就使用toString
的返回值来...如果都没有 就报
TypeError
# 其他值转Boolean
的规则
undefined
null
false
+0
-0
NaN
都是false
其他都为true
# Object.is()
和 ==
和 ===
的区别
使用双等会进行数据类型的转换
使用三等不会进行数据类型的转换 更为严格
使用
Object.is()
大致和三等差不多 主要还是解决一些特殊情况例如Object.is(+0, -0)
是false
;Object.is(NaN, NaN)
是true
的问题
# 什么是js
的包装类型
基本类型
是没有方法和属性 为了便于操作基本类型的值 在调用基本类型的方法和属性时js
会隐式转化为对象
const a = "abc";
a.length; // 3 js将abc转换为String('abc')
a.toUpperCase(); // "ABC"
2
3
可以使用valueof
将包装类型倒转成基本类型
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
2
3
看看下面一题
var a = new Boolean( false );
if (!a) {
console.log("Oops"); // never runs
}
// 什么都不会打印 因为虽然传入的是一个false 但是false被包裹成包装类型就成为了对象 他的值并非false
2
3
4
5
6
# 介绍一下JS
是如何进行隐式转换的
首先讲讲
ToPrimitive
方法 这是每个值隐含的方法 用来将值转换为基本类型的值
// 如果值为对象
/**
* @obj 需要转换的对象
* @type 期望的结果类型
*/
ToPrimitive(obj,type)
2
3
4
5
6
7
当
type
为number
时调用
object
的valueof
方法 如果为原始值则返回 否则进行下一步调用
object
的toString
方法 后续同上抛出
TypeError
当
type
为string
时调用
toString
方法 后续同上调用
valueof
方法抛出
TypeError
两者最大的不同就是tostring
方法和valueof
的方法的先后不同
如果对象为
Date
对象 则type
默认为string
其他情况默认为
number
属性
// 概括为
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN
2
3
4
# +
操作符
如果两边至少有一个为字符型的 那么两边都会被隐式转成字符串,其他情况两边都被转成数字
1 + '23' // '123'
1 + false // 1
1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
'1' + false // '1false'
false + true // 1
2
3
4
5
# -
*
\
操作符
优先将值转换为数字NaN
也是数字
1 * '23' // 23
1 * false // 0
1 / 'aa' // NaN
2
3
# ==
操作符
两边的值都尽量转成number
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0
2
3
看一道题
var a = {}
a > 2 // false
// 对比结果如下
// a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
// a.toString() // "[object Object]",现在是一个字符串了
// Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
// NaN > 2 //false,得出比较结果
// 又比如
var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"
2
3
4
5
6
7
8
9
10
11
12
总结一下
除了加法操作符以外的,两边都会优先转换成number
类型
# 为什么有BigInt
提案
JavaScript
中Number.MAX_SAFE_INTEGER
表示最⼤安全数字,即在这个范围内不会出现精度丢失的问题(小数除外),官方提出BigInt
来解决此问题
# Object.assign
和扩展运算符是深拷贝还是浅拷贝 两者区别
扩展运算符
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
2
3
4
5
6
Object.assign
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
2
3
4
5
6
可以看出两者都是浅拷贝
Object.assign
接收的第一个参数作为目标对象,后面的参数作为源对象 然后将所有源对象合并到目标对象中 他修改了一个对象 触发了ES6
的setter
- 扩展操作符 数组或对象中每一个值都会被拷贝到新的数组或对象中 不复制继承的属性或类的属性 但是会复制
ES6
的symbols
属性