数据类型
# 介绍一下js的数据类型和区别特别重要

七种基本数据类型:undefined null string number bigInt boolean symbol
一种引用数据类型:object (包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function)
其中es6新增的是symbol和bigint
symbol代表创建后独一无二不可变的数据类型 为了解决可能出现的全局变量冲突bigint是一种数字类型的数据 可以表示任何精度格式的整数,可以安全存储操作大整数,可以超过number能够表示的安全整数范围
他们分别存放在
栈:原始数据类型(
undefinednullbooleannumberstring)- 占据空间少 大小固定 属于频繁被使用数据 所以放在栈中
- 栈区内存由编译器自动分配释放 存放函数的参考值 局部变量的值
堆:引用数据类型(
objectarrayfunction)- 占据空间大 大小不固定 引用数据在栈中存储了指针指向堆中实体的起始地址
- 由开发者分配释放 若开发者不释放程序结束时由垃圾回收机制回收
堆:
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 == undefinedundefined === undefinednull === nullnull == 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
# 如何定义 a,使得 a == 1 && a == 2 && a == 3 为真
let a = {
i: 1,
toString() {
return this.i++;
}
};
let i = 1;
Object.defineProperty(globalThis, 'a', {
get() {
return i++;
}
});
let a = new Proxy({ i: 1 }, {
get(target, key) {
return target.i++;
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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另一方为stringnumbersymbol的一种 是的话将object转为原始类型在判断
- 判断两者一方是不是
'1' == {name: 'js'}
↓
'1' == '[object Object]'
2
3
# 其他值是怎么转字符串的
null和undefinedboolean直接转null -> 'null'undefined -> 'undefined'false -> 'false'true -> 'true'
number类型的直接转换 不过那些极小极大的会使用指数symbol直接转换,但只允许显式强制类型转换 使用隐式会出错对普通对象来说 除非自行定义的
tostring否则会调用Object.prototype.toString()
# 其他值转number的规则
undefined转为NaNnull转为0boolean中true转为1false转为0string的话先隐式转换(相当于调Number()) 如果包含非数字值的转换为NaN空字符串为0symbol不能转换为数字对象(包括数组)会首先被转换为相应基本类型的值 如果返回非数字的基本类型值,再遵循上面的规则强制转换为数字
转换为相应基本类型的值的意思是 通过内部操作
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属性