JavaScript数据类型
i东东 人气:0数据类型的分类
JS中的数据类型有七中原始数据类型:
- 原始数据类型[值类型/基本数据类型]:
- number 数字
- string 字符串
- boolean 布尔
- null 空对象指针
- undefined 未定义
- symbol 唯一值
- bigint 大数
- 对象类型[引用数据类型]
- object
- 标准普通对象 object
- 标准特殊对象 Array、RegExp、Date、Math、Error...
- 非标准特殊对象 Number、String、Boolean...
- 可调用/执行对象 函数Function
- object
非标准特殊对象
除了null和undefined每个原始值类型都有一个自己对应的对象类型值
Symbol
Symbol是唯一值,他是不能通过New操作符进行创建的,每次执行Symbol都是创建一个唯一值,下方代码A和B虽然看起来是一样的但却不相等的两个值。假如如果把C=A这样把A的值赋给C,那么A和C肯定是相等的
let A = Symbol('i东东') let B = Symbol('i东东') let C = A console.log(A === B); // false console.log(A === C); // true
应用场景:
- 给对象设置唯一值的属性名,在ES6之前对象的属性名只能是字符串类型,但是新增了ES6之后对象的属性名增加了可以用Symbol类型的属性名。
- Map新的数据结构:可以允许属性名是对象
- Symbol.asyncIterator/iterator/hasInstance/toStringTag...是某些JS知识底层实现机制
- 在派发行为标识进行统一管理的时候,可以基于Symbol类型的值,保证标识的唯一性
用字符串和数字类型的都可以取到值,name就可以认为他们的属性名是字符串类型的
下方代码console.log([Symbol('AA')])
是无法取到值的,因为这个相当于创建了一个新的唯一值,用新的唯一值去访问是不对应的,正确的方法应该是创建一个唯一值,再把这个唯一值作为属性去访问。
let key = Symbol('88') let obj = { num: 100, 10:'数字', [Symbol('AA')]:'Symbol类型的属性名', // 在大括号中加Symbol语法要求必须加大括号 [key]:400 } console.log(obj[Symbol('AA')]); // undefined console.log(obj[key]); // 400说
一个对象的属性名是不能重复的,用唯一值做属性的好处就是,他是不会重复的
let obj = { [Symbol('AA')]: 100, [Symbol('AA')]: 100, // 这两个是不同的两个值 }
BigInt 大数类型
JS中的最大安全数 Number.MAX_SAFE_INTEGER = 9007199254740991
JS中的最小安全数 Number.MIN_SAFE_INTEGER = -9007199254740991
超过安全数后,进行运算或者访问,结果会不准确!! 数据库当中存取数据会分成整型、长整型、短整型,长整型支持的数字长度是比JS的安全数大的,当服务器返回给客户端一个长整型的数字,如果超出最大安全数值就会出现问题。
解决方案:
- 服务器端给客户端返回大数,按照字符串格式返回!
- 客户端把其变换成 Bigint
- 最后把运算后的BigInt转换为字符串,再传递给服务器
console.log(BigInt('9007199254740991') + BigInt(12345)); // 9007199254753336n console.log(9007199254753336n.toString()); // 9007199254753336 // 123n 在数字后面直接加`n`也表示大数BigInt
数据类型检测
- typeof
- instanceof
- constryctor
- Object.prototype.toString.call
- Array.isArray
- isNaN
在日常开发中用到最多的就是typeof检测数据类型,所有的数据类型值,在计算机底层都是按照'64位'的二进制进行存储的!type是按照二进制值进行检测类型的
例如:
- 二进制的前三位是0,认为是对象,然后再去看有没有实现call方法,如果实现了返回'functhon',没有时间,则返回'object'
- 第一位是1为整数
- 010浮点数
- 100字符串
- 110布尔
- 000000... null(null为64个0) ==> object [因为typeof的局限性]
- ......
检测未被申明的变量,值为undefined
console.log(a); // ReferenceError: a is not defined console.log(typeof a); // undefined
场景1:检测当前值是否是一个对象
const fn = options =>{ let type = typeof options if(options !== null && (type === 'object' || type === 'function')){ // 这样进来的才能确保是一个对象 } } fn({ num:10 })
场景2:支持更多的模块导入方案
(function(){ if(typeof window !== 'undefined') window.utils = utils if(typeof module === 'object' && typeof module.exports == 'object') module.exports = utils })()
数据类型间的相互转换
数据类型间的相互转换主要有隐式数据类型转换和显示数据类型转换两种。 一般用于浏览器中的隐式转换:
- 数学运算
- isNaN检测
- == 比较
比如说在数据运算时会进行隐式数据类型转换,10-2的时候实际上默认就调用了Number('2')进行了类型转换之后才进行的减法运算。
比如用isNaN来判断时前值是不是一个有效数字,就相当于调用了下面的方法,先调用Number再执行isNaN。
规则
Number([val])
- 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符都是NaN
- 把布尔值转换成数字:true=>1 false=>0
- null=>0 undefined=>NaN
- Symbol无法转换为数字,会报错:Cannot convert a Symbol value to a number
- BigInt去除'n'(超出安全数字的会按照科学计数法处理)
- 那对象转换为数字:先调用对象的
Symbol.toPrimitive
方法,如果不存在这个方法,再调用valueOf
获取原始值,如果获取的值不是原始值,在调用toString
把其变为字符串,最后自傲把字符串用过Number
方法进行转换
let time = new Date() console.log(Number(time)); // 1662715039452 console.log(time[Symbol.toPrimitive]('number')); // 1662715039452 // 首先检测 Symbol.toPrimitive有没有, 结果:有 而且是一个函数 time[Symbol.toPrimitive]('number')
let arr = [10] console.log(Number(arr)); /** * 首先arr[Symbol.toPrimitive] => undefined * 然后arr.valueOf() 数组是没有原始值的 => [10] 任何一个对象都有valueOf * 再然后 arr.toString => '10' * 最后在把字符串'10'转换为数字 => 10 */
let num = new Number(10) console.log(Number(num)); /** * 首先num[Symbol.toPrimitive] => undefined * 然后num.valueOf() 数组是没有原始值的 => 10 */
parseInt([val],[radix]) parseFloat([val])
parseInt([val],[radix])是可以传两个值进去的:
- value必须是字符串,不是字符串会会先隐式转换成字符串,通过String([val]),如果是对象会用上面的三步进行转换
- radix进制,如果不写或者写0,则默认为10进制,如果字符串是以0x开始的,默认为16进制,有效进制范围2~36之间(如果不在这个区间结果直接是NaN)。从[val]字符串左侧开始第一个字符开始查找,查找出符合[radix]进制的值(遇到不符合的则结束查找,无论后面是否还有有符合的),把找到的内容按照[radix]进制,转化为10进制!!
console.log(parseInt('10103px13',2)); // 10 /** * 首先找到符合二进制的数 '1010' * 把这个二进制值转换为十进制 '按权展开求和' * 1*2^3+0*2^2+1*2^1+0*2^0 => 8+0+2+2 => 10 */
这里放一道字节面试题
arr会输出什么,为什么? let arr = [27.2,0,'0013','14px',123] arr = arr.map(parseInt)
解题步骤:
- arr.map方法:迭代数组中的每一项,并且把每一项进行修改,原始数组不变,以新数组的形式返回。迭代数组中的每一项item为当前值,index为索引。
- map中应该接受一个函数,现在我们是把
parseInt
作为这个函数传递进去了,所以每次迭代都会执行parseInt
并且将item和index作为参数传递进去
/** * parseInt(27.2,0) => 找符合二进制的'27',当做十进制转成十进制=> 27 * parseInt(0,1) => 超出有效进制范围 => NaN * parseInt('0013',2) => 001 当做二进制转化为十进制 => 0+0+1 => 1 * parseInt('14px',3) => 1 当做三进制转成十进制 => 1 * parseInt(123,4) => parseInt('123',4) => 123当做四进制转成十进制 1*4^2+2*4^1+3*4^0 => 16+8+4 => 27 */
所以最终得:arr = [27, NaN, 1, 1, 27]
注:
parseInt('0013',2) => 001 当做二进制转化为十进制 => 0+0+1 => 1 parseInt(0013,2) => 0013 当js中遇到以0开头的数字会默认当做8进制转为10进制然后在进行其他运算 => 当做八进制转为十进制 0*8^3+0*8^2+1*8^1+3*8^0 => 11 => 二进制转十进制 11 2+1=3
把其他类型转换为String
转换规则:
- '+'出现左右两边,其中一边是字符串,或者是某些对象,会以字符串拼接的规则处理
- '+'出现在一个值的左边,转换为数字
console.log(10 + '10'); // 1010 console.log(10 + new Number(10)); // new Number(10)[Symbol.toPrimitive] -> undefined // new Number(10).valueOf() => 10 // 10 + 10 = 20 console.log(10 + new Date()); // new Date()[Symbol.toPrimitive]('default') -> 不知道传递什么会传defaulu => 'Tue Sep 13 2022 17:00:40 GMT+0800 (中国标准时间)' // 最终等于:10'Tue Sep 13 2022 17:00:40 GMT+0800 (中国标准时间)' console.log(10 + [10]); // [10][Symbol.toPrimitive] -> undefined // [10].valueOf => '[10]' // [10].toString() => '10' // 最终等于: '1010' let num = '10' console.log(+num) // 10 转换为数字
把其他类型的值转换成Boolean
Boolean(0) = false 除了0 NaN undefined 空字符串 null 以外都是true
==两个等号比较规则
- ==相等,两边数据类型不同,首先要转换为相同类型,然后在进行比较
- 对象==字符串 对象转字符串[Symbol.toPrimitive]->valueOf()->toString()
- null == nudefined -> true null/undefined和其他任何值都不相等
- null === undefined -> false
- 对象==对象 比较的是堆内存地址,地址相同则相等
- NaN !== NaN
- 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后在进行比较的,'==='绝对相等,如果两边类型不同则直接是false,不会转换数据类型。
NaN == NaN // false NaN永远不可能等于NaN console.log(Object.is(NaN,NaN)); // true
贴一道面试题 下面输出为什么?
console.log([]==false); // 首先都会转换为数字, 0 == 0 => true console.log(![]==false); // 先处理![]=>false == false =>true
Js中的装箱和拆箱
装箱和拆箱都是浏览器进行数据类型隐式转换的过程
let num = 10 console.log(num.toFixed(2)); // 10.00 // num是原始值,不是对象,按常理来讲是不能做'成员访问'的 // 默认会做装箱操作: new Number(num) 变为非标准铁树对象,这样就可以调用toFixed了 let num = new Number(10) console.log(num + 10); // 20 // 在操作的过程中浏览器会将num这个非标准特殊对象变为原始值 [Symbol.toPrimitive] -> valueOf -> toString 这个操作叫做拆箱 // 装箱和拆箱都是浏览器进行数据类型隐式转换的过程
加载全部内容