前言
本文介绍《JavaScript高级程序设计》的3.4小节-数据类型,看完你将能回答如下问题:
- JS一共有几种数据类型?【3.4.1】
- 你知道的检测数据类型的方式?typeof关键字有什么缺陷?【3.4.1】
- 有哪些情形,数据返回的是undefined?【3.4.2】
- 哪些情形,数据返回的是null?【3.4.3】
- null和undefined有什么区别?【3.4.3】
- 哪些数据转化为布尔值,返回的是false?【3.4.4】
- +0 === -0是true还是false?Object.is(+0, -0)呢?【3.4.5】
- JS的十进制和十六进制如何去表示?【3.4.5】
- 如何获取JS的最小值和最大值【3.4.5】
- NaN等于自身吗?isNaN函数是干嘛用的【3.4.5】
- Number函数,parseInt函数,parseFloat函数有啥区别?【3.4.5】
- toString方法转化不同的数据类型,注意测试【3.4.6】
- 谁身上有toString方法?和String方法有区别吗?【3.4.6】
- Symbol数据类型的用途【3.4.7】
- Symbol能进行计算吗?可以转化为数字、字符串吗?【3.4.7】
- 知道魔术字符串吗?【3.4.7】
- 如何遍历到对象的Symbol键名?【3.4.7】
- Symbol.for和SymbolKeyfor是干嘛用的?【3.4.7】
- 小数能创建BigInt吗?【3.4.8】
- 怎么样把BigInt转化为普通整数?【3.4.8】
- BigInt能进行数学运算吗?【3.4.8】
3.4 数据类型
3.4.1 typeof操作符
JS中一共有多少种数据类型?
基本数据类型:数字 字符串 布尔值 null undefined symbol bigint
复杂数据类型:object function
typeof关键字
let number = 1;
let str = '1';
let bool = true;
let Null = null;
let Un = undefined;
let obj = {
name: '1'
}
let symbol = Symbol(111)
let big = BigInt(11)
let fun = () => {
console.log(2, '2');
}
console.log(typeof number); // 'number'
console.log(typeof str); // 'string'
console.log(typeof bool); // 'boolean'
console.log(typeof Null); // 'object'
console.log(typeof Un); // 'undefined'
console.log(typeof obj); // 'object'
console.log(typeof symbol); // 'symbol'
console.log(typeof big); // 'bigint'
console.log(typeof fun); // 'function'
typeof关键字也可以当做函数使用
console.log(typeof(number)); // 'number'
注意:
- typeof null,值是'object'
- typeof 函数,值是'function'。理论上函数在ES中是对象,但是因为函数有自己特殊的属性,所以单独区分开来是'function'(函数特殊的属性:)
3.4.2 undefined
声明变量未赋值
let message; // 未声明,值是undefined
console.log(message, 'message');
console.log(message === undefined, 'message === undefined'); // true
如下代码和上面是一样的,但是不推荐像下面这样做,“永远不要显示的给一个变量声明值为undefined”
let message1 = undefined
console.log(message1 === undefined, 'message1 === undefined'); // true
但未声明变量和值为undefined是有区别的
console.log(age, 'age') // age会报错,这里没有let age
注意,未声明的变量,可以用typeof操作符。注意,无论声明还是未声明变量,typeof返回都是undefined。严格意义上来讲,这两个是有本质区别的。推荐声明的时候就初始化,这样typeof为undefined的时候就能明确这个变量是没有声明。
console.log(tyepof age, 'typeof age) // 值为undefined
补充知识:
除了声明变量值为undefined,还有以下情况返回的是undefined:
函数调用没有return
// 2 函数调用 里面没有return值
function fn() {
}
console.log(fn());
访问对象但是没有该属性
// 3 访问一个对象 没有的属性值
let obj = { name: 'xiaohang' }
console.log(obj.age);
函数有三个形参,但是只传递了2个
// 4 函数有3个形参 但是只传递了2个 在里面打印了第三个
function fn2(p1, p2, p3) {
console.log(p3);
}
fn2(1, 2)
3.3.3 null
null表示的是空对象指针,所以typeof null值也为'object'
console.log(typeof null, 'object');
null和undefined的用途不一样,任何时候只要没有固定的对象要保存,就可以把变量声明为null
let message = null
此外,null值还有别的情况:
找不到固定的dom元素
let box = document.querySelector('#box')
console.log(box, 'box'); // null box
正则表达式匹配不到固定的值
let reg = /g/
console.log('namea'.match(/g/)) // 值为null
null和undefined的数字计算
console.log(null + 0, 'null + 0'); // 0
console.log(undefined + 0, 'undefined + 0'); // NaN
3.4.4 Boolean类型
Boolean类型是ES中使用最频繁的类型之一,有true和false,注意区分大小写,True和False是有效的标识符;调用Boolean函数可以将其他数据类型转化为布尔值
let message = 'Hello world'
let messageAsBoolean = Boolean(message)
console.log(messageAsBoolean, 'messageAsBoolean'); // true
如下列表记录了其他数据类型转化为布尔值的结果
| 数据类型 | 转换为true的值 | 转换为false的值 |
|---|---|---|
| Boolean | true | false |
| String | 非空字符串 | ''空字符串 |
| Number | 非零数值 | 0、NaN |
| Object | 任意对象 | null |
| Undefined | N / A(不存在) | undefined |
let message = 'Hello world'
let messageAsBoolean = Boolean(message)
console.log(messageAsBoolean, 'messageAsBoolean'); // true
// 数字 0和NaN都会转化为false
console.log(Boolean(0));
console.log(Boolean(NaN));
// 字符只有空字符 - false
console.log(Boolean(''));
// 对象 只有null - false
console.log(Boolean(null)); // false
console.log(Boolean({})); // true
// undefined
console.log(Boolean(undefined));
// Function
console.log(Boolean(function fn() { })); // true
3.4.5 Number
JS中的Number类型,使用IEEE 754格式表示整数和浮点数(双精度值)。
0. 进制数
十进制:
let intNum = 55 // 整数 十进制
八进制:
// Number类型
let intNum = 55; // 整数 十进制
let octalNum1 = 070; // 打印出来是56
let octalNum2 = 079; // 无效八进制数 打印出来是79
let octalNum3 = 08; // 无效八进制数 打印出来是56
console.log(octalNum1, 'octalNum1'); // 56
console.log(octalNum2, 'octalNum2'); // 79
console.log(octalNum3, 'octalNum3'); // 8
如上写法在编辑器中会报错,但是控制台还是可以正常显示:
编辑器不会报错的写法:
// 0o开头,其余数字在0-7之间,是正确的八进制
let intNum = 55; // 整数 十进制
let octalNum1 = 0o70; // 打印出来是56
let octalNum2 = 0o77; // 打印出来是56
其余数字如果超过了7,编辑器和控制台都会报错:
十六进制数
必须以0x开头(区分大小写),其余的数字为09,AF组成(不区分大小写),A是10,F是16
let hexNum1 = 0xA;
let hexNum2 = 0x1f;
console.log(hexNum1, 'hexNum1'); // 10
console.log(hexNum2, 'hexNum2'); // 31
+0和-0是否相等?
console.log(+0 === -0); // true
1. 浮点数
JS的浮点数的写法
let floatNum1 = 1.1
注意,小数点前面可以没有数字,但是推荐加上
let floatNum3 = .1
ES总是想方设法把浮点数转化为整数,因为浮点数的内存空间是整数的2倍
let floatNum4 = 1.0 // 转化为1
let floatNum5 = 2. // 转化为2
console.log(floatNum4, 'floatNum4');
console.log(floatNum5, 'floatNum5');
科学计数法
// 表示 3乘10的负17次方
let floatNum6 = 3e-17
console.log(floatNum6, 'floatNum6');
ECMAScript会默认把小数点至少有6个0的浮点数转化为科学计数法
let floatNum7 = 0.0000003
console.log(floatNum7, 'floatNum7'); // 默认转化为3e-7
浮点数的精确度高达17位,但是存在误差,比如:
console.log(0.1 + 0.2, '0.1 + 0.2'); // 0.30000000000000004 '0.1 + 0.2'
出现上述的原因是:在计算机中,浮点数是以二进制进行存储的,0.1和0.2的二进制对应的是无限循环小数,所以相加出现很多小数点。【这个在面试题中可能会遇见】
2. 值的范围
ES中接受的最小值:5e-324
console.log(Number.MIN_VALUE, 'Number.MIN_VALUE最小值'); // 5e-324
ES中接受的最大值:1.7976931348623157e+308
console.log(Number.MAX_VALUE, 'Number.MAX_VALUE最小值'); // 1.7976931348623157e+308
如果可以计算的值超出了ES可以表示的范围,就是Infinity,负数就是-Infinity。
注意Number.POSITIVE_INFINITY输出的值就是Infinity,Number.NEGATIVE_INFINITY输出的值就是-Infinity,
console.log(Number.POSITIVE_INFINITY, 'Number.POSITIVE_INFINITY'); // Infinity
console.log(Number.NEGATIVE_INFINITY, 'Number.NEGATIVE_INFINITY'); // -Infinity
可以使用Infinity()方法判断某个数是否在Infinity和-Infinity之间
console.log(isFinite(Infinity), 'isFinite(Number.Infinity)'); // false
console.log(isFinite(Number.MAX_SAFE_INTEGER), 'isFinite(Number.MAX_SAFE_INTEGER)'); // true
3. NaN
NaN的是Not a Number,表示'本来要返回一个数值的,但是出错了'
如下:
- 0除以任何数值,在其他语言会出错,但在ECMAScript中,返回的是NaN
- 分子是非0,分母是0,则会返回Infinity或者-Infinity
console.log(0 / 0); // NaN
console.log(- 0 / + 0); // NaN
console.log(5 / 0); // Infinity
console.log(-1 / 0); // -Infinity
NaN的特点:
- NaN的任何操作,多步操作都会返回他自身
- NaN不等于任何值,包括他自身
- ES提供了isNaN()函数用来判断一个数,是否
不是数值,他会尝试把这个数转化为数值,不能转化就会返回true,否则返回false
console.log(isNaN(NaN), 'isNaN(NaN)'); // true
console.log(isNaN(10), 'isNaN(10)'); // false
console.log(isNaN('10'), 'isNaN(10)'); // false
console.log(isNaN('blue'), 'isNaN(blue)'); // true
console.log(isNaN(true), 'isNaN(true)'); // false
isNaN还可以用于测试对象
4. 数值转换
- Number转换规则:
数值:
console.log(Number(10));// 10,十进制
console.log(Number(0o10));// 8,八进制
console.log(Number(0x10));// 16,十六进制
浮点数:
console.log(Number('0.01'), 'Number(0.01)'); // 0.01
console.log(Number('00.11'), 'Number(00.11)'); // 0.11 多余的0会被去除掉
布尔:
console.log(Number(true));// 1
console.log(Number(false));// 0
null:
console.log(Number(null)); // 0
undefined:
console.log(Number(undefined)); // NaN
字符串
// 5.1 字符串 只包含数字 前面有0会忽略0
console.log(Number('21')); // 21
console.log(Number('012')); // 12 字符串是八进制 -》不按照八进制进行转化 前置的0倍忽略掉 就跟十进制一样了
console.log(Number('0o12'), 'Number(0o12)'); // 10 0o按照八进制进行转换
console.log(Number('0x10')); // 16 十六进制 -》对应的十进制数
空字符
console.log(Number(''), 'Number()'); // 0
console.log(Number(' '), 'Number( )'); // 0
其他情况
console.log(Number('123ab'), 'Number(123ab)'); // NaN
console.log(Number('123++'), 'Number(123++)'); // NaN
对象: 先调用valueOf,再调用toString
const obj = {
name: 123
}
console.log(Number(obj)); // NaN
const obj2 = {
name: 123,
valueOf: function () {
return this.name
}
}
const obj3 = {
name: 123,
toString: function () {
return this.name
}
}
console.log(Number(obj2)); // 123
console.log(Number(obj3)); // 123
- parseInt
涉及到字符串转换为数字,优先用parseInt:
- 从第一个非空字符串开始匹配,如果第一个不是数字、+-,立刻返回NaN
- 如果第一字符符合条件,继续开始匹配
- 如果是空字符,返回NaN,对比Number返回的是0
- 字符串最前面的空格会被忽略,如'+0111',0会被忽略
- 碰到非字符,比如'1234blue',其blue会被忽略
- 碰到'22.5',返回22,因为parseInt只能转化为整数
// 忽略前导0
console.log(parseInt('0111'), "parseInt('0111')"); // 111
// 开头可以是+-号
console.log(parseInt('+0111'), "parseInt('+0111')"); // 111 console.log(parseInt('+00111'), "parseInt('+00111')"); // 111
// 开头是+—号和数字以外的,直接返回NaN
console.log(parseInt('a+0111'), "parseInt('a+0111')"); // NaN
// 依次检测字符,碰到非数字,立即停止检测
console.log(parseInt('+011a1'), "parseInt('+011a1')"); // 11
console.log(parseInt(''), "parseInt('')"); // 0
支持进制数,或者指定特殊的进制
// 十六进制
console.log(parseInt('0xA')); // 10
// 无法检测八进制
console.log(parseInt('010')); // 10
// 这样的八进制可以,第二位指定进制数,并推荐写第二位
console.log(parseInt('10', 8)); // 8
// 第二位指定十六进制数
console.log(parseInt('A', 16)); // 10
建议始终传递第二个参数,大多数情况是10
- parseFloat
- parseFloat中,第一个小数点有效,后续的小数点及后面的数都会被忽略
- 开头0会被忽略,只能解析十六进制数
- 没有小数点,或者小数点后面是0,则返回整数
console.log(parseFloat('1.11.1'), 'parseFloat("1.11.1")'); // 1.11
console.log(parseFloat('0xA'), 'parseFloat("0xA")'); // 0
console.log(parseFloat('10', 2), 'parseFloat("10", 2)'); // 10
3.4.6 String类型
- 字符串字面量
\n => 换行符
\t => 制表符
\b => 退格符
\r => 回车符
\f => 换页
\\ => 反斜杠
\' => 单引号
\" => 双引号
\` => 反斜杠
注意,转移序列始终只表示一个字符,\n只表示一个字符。比如 'a\n'的长度就是2,
- 字符串的特点
一旦创建,他的内存空间不能修改,必须得创建新的内存空间,并销毁原先的内存空间
let lang = 'Java'
lang = 'Java' + 'Script'
如上,会创建一个10个字符的空间给JavaScript字符,并且销毁原先的Java
- toString方法
let a = '123'
let b = [1, 2, 3, 4]
let c = {}
let d = function () { }
let reg = /123/
console.log(a.toString(), "'123'.toString"); // '123'
console.log(b.toString(), "[1, 2, 3, 4].toString"); // '1,2,3,4'
console.log(c.toString(), "{}.toString()"); // '[object Object]'
console.log(d.toString(), "function () { }.toString()"); // 'function () { }'
console.log(reg.toString(),"/123/.toString()"); // '/123/'
toString方法转化进制
// toString按照进制转化
let num = 10;
console.log(num.toString(2), 'num.toString(2)'); // '1010'
console.log(num.toString(8), 'num.toString(8)'); // '12'
console.log(num.toString(10), 'num.toString(10)'); // '10'
console.log(num.toString(16), 'num.toString(16)'); // 'a'
几乎所有值都有toString方法,null和undefined没有
如下的方法会报错,不存在这个方法
// console.log(null.toString(), 'null.toString()');
// console.log(undefined.toString(), 'undefined.toString()');
如果值有可能是undefined或者null,可以用String函数进行区分
console.log(String(null), 'String(null)'); // 'null'
console.log(String(undefined), 'String(undefined)'); // 'undefined'
加上空字符串,可以把一个数转化为字符串
// 加上空字符串
console.log(null + '', '+null');
- 模版字面量与插值表达式
- 注意,他支持换行的字符串
let str = `
first line
second line
`
console.log(str, 'str');
打印出来是这样的
- 不仅支持变量,我还在书上看到这个用法
let fn = () => '我是函数返回的值'
let str = `你是谁?${fn()}` // 你是谁?我是函数返回的值 str2
- 字符串原始值
通过String.raw``的方式
let str3 = String.raw`\u00a9`
console.log(str3, 'str3'); // \u00a9 str3
注意,\u00a9直接打印出来是这个
let str4 = `\u00a9`
console.log(str4, 'str4'); // © str4
3.4.7 Symbol
符号的用途就是创建唯一记号,进而用作非字符串形式的对象属性,避免发生冲突
概述
- 创建一个符号值
let sym = Symbol()
console.log(typeof sym, 'typeof sym'); // 'symbol'
注意,如下生成的s是一个原始值,不是对象,所以不能用new关键字来初始化,也就是说Symbol函数不能用作构造函数,如下会报错
let s = new Symbol()
console.log(s, 's');
- Symbol函数接受一个字符串作为参数
// 接受一个参数作为构造函数
let sym3 = Symbol('foo')
let sym4 = Symbol('boo')
console.log(sym3, 'sym') // Symbol(foo) 'sym3'
console.log(sym4, 'sym') // Symbol(boo) 'sym4'
- Symbol是独一无二的值,即便是两个空的Symbol值也是不同的
let sym1 = Symbol()
let sym2 = Symbol()
console.log(sym1 === sym2, 'sym1 === sym2'); // false
有值的symbol也不相等
let sym5 = Symbol('a')
let sym6 = Symbol('a')
console.log(sym5 === sym6, 'sym5 === sym6'); // false
这是因为Symbol函数的参数只是对当前Symbol函数的描述
- Symbol值不能和其他数据类型进行运算
console.log(sym6 + 'a', "Symbol('a') + a");
结果如下:
可以显式转化为字符串:toString()、String()函数
// 可以显式转化为字符串
console.log(sym6.toString(), 'sym6.toString()'); // Symbol(a) sym6.toString()
console.log(String(sym6), 'String(sym6)'); // Symbol(a) String(sym6)
可以转化为布尔值:
// 可以转化为布尔值
console.log(!sym6, '!sym6'); // false
console.log(Boolean(sym6), 'Boolean(sym6)'); // true
不能转化为数值:
console.log(sym6 + 1, 'sym6 + 1'); // Cannot convert a Symbol value to a number
console.log(Number(sym6), 'Number(sym6)'); // Cannot convert a Symbol value to a number
description
获取symbol的描述值,ES2019提出这个属性,如果没有这个属性,只能通过String函数转化为字符串才能看到其描述值
// 获取symbol描述的概述值
console.log(sym6.description, 'sym6.description'); // a sym6.description
作为属性名的Symbol
- 作为属性的几种方式
// 法1:
let obj1 = {
[sym3]: 'foo的值'
}
console.log(obj1, 'obj1');
// 法2:
let obj2 = {}
obj2[sym3] = 'foo的值'
console.log(obj2, 'obj2');
// 法3:
let obj3 = {}
Object.defineProperty(obj3, sym3, {
value: 'foo的值',
configurable: true,
enumerable: true, // 该属性若为false,则控制台打印出来的是灰色的
writable: true
})
- 用点的方式访问打印出来是undefined,因为点运算符后面通常都是字符串
console.log(obj3.sym3, 'obj3'); // undefined
- 声明为对象内部的属性时,必须为方括号[],否则就是普通的属性
let obj4 = {
sym3: '不是foo的值'
}
console.log(obj4, 'obj4');
- 声明为对象内部的值,用作常量
const log = {}
log.level = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
}
console.log(log.level.DEBUG, 'log.level.DEBUG'); // Symbol(debug) 'log.level.DEBUG'
console.log(log.level.INFO, 'log.level.INFO'); // Symbol(info) 'log.level.INFO'
常量用Symbol,其他任何值不重复
消除魔术字符串
如下代码中,字符串'xhg'和函数getValue形成强耦合,这就是魔术字符串
function getValue (value) {
if (value === 'xhg') {
return 'result'
}
}
getValue('xhg')
如果把该字符串放到一个对象中,该强耦合就结束了
let obj = {
nameType: 'xhg'
}
function getValue (value) {
if (value === obj.nameType) {
return 'result'
}
}
getValue('xhg')
如果改成Symbol, 也能够执行,是不是xhg不重要,只要不和其他的字符串冲突就行,Symbol就能够做到这点
let obj = {
nameType: Symbol()
}
function getValue (value) {
if (value === obj.nameType) {
return 'result'
}
}
getValue('xhg')
3.4.8 BigInt 数据类型
超过2的53次,数字精度无法表示
// true
console.log(Math.pow(2, 53) === Math.pow(2, 53) + 1, 'Math.pow(2, 53) === Math.pow(2, 53) + 1');
超过2的1024次方,无法表示
console.log(Math.pow(2, 1024)); // Infinity
BigInt能够保证精度,只能保证整数
let a = 2172141653n;
let b = 15346349309n;
console.log(a * b, 'a * b'); // 33334444555566667777n
普通整数无法保持精度。注意,Number 能够把 BigInt 转化为普通整数
// 33334444555566670000 'Number(a) * Number(b)'
console.log(Number(a) * Number(b), 'Number(a) * Number(b)');
BigInt 整数必须加上 n,和普通整数区分开来
console.log(1n + 2n,'1n + 2n'); // 3n
直接报错,BigInt 值和普通数值相加,所以他们是两种不同类型的值
console.log(1n + 2,'1n + 2'); // 3n
报错
console.log(1n === 1, '1n === 1'); // false
表示进制数
console.log(0b1101n, '0b1101n'); // 二进制 13n
console.log(0o777n, '0o777n'); // 八进制511n
console.log(0xFFn, '0xFFn'); // 十六进制255n 15 * 16 + 15 * 1
判断类型
console.log(typeof 123n, 'typeof 123n'); // 'bigint'
可以使用-号,使用+号就会报错,会和 asm.js 冲突
// 可以使用负号
console.log(-12n, '-12n');
// console.log(+12n, '+12n');
使用 BigInt,才支持准确计算 70 的阶乘
let p2 = 1n;
for (let i = 1n; i <= 70n; i++) {
p2 *= i
}
// 11978571669969891796072783721689098736458938142546…857555362864628009582789845319680000000000000000n 'p2'
console.log(p2, 'p2');
BigInt 函数
转化方式和 Number 函数类似
console.log(BigInt(123), 'BigInt(123)'); // 123n
console.log(BigInt('123'), 'BigInt("123")'); // 123n
console.log(BigInt(false), 'BigInt(false)'); // 0n
console.log(BigInt(true), 'BigInt(true)'); // 1n
下面情况都会报错。注意字符串 123n 无法转化为 BigInt
// console.log(BigInt(undefined), 'BigInt(undefined)');
// console.log(BigInt(null), 'BigInt(undefined)');
// console.log(BigInt('123n'), 'BigInt(123n)');
// console.log(BigInt('abc'), 'BigInt(undefined)');
小数不能转
// console.log(BigInt(1.5), 'BigInt(1.5)');
有 toString 和 valueOf 方法
// 有toString和valueOf方法
console.log(BigInt(123).toString(), 'BigInt(123).toString()'); // '123'
console.log(456n.toString(), 'BigInt(123).toString()'); // '456'
console.log(456n.valueOf(), 'BigInt(123).valueOf()'); // 456n
还继承了toLocaleString,asUintN,asIntN 方法
BigInt 运算
几乎所有的数值运算都可以用到 BigInt 上面,注意如下/触发会省略小数位,bigInt 没有小数
console.log(9n / 5n, '9n / 5n');
+1n 这样的写法会直接报错
console.log(+1n) //
无符号右移直接报错,因为 bigInt 总是有符号的
console.log(5n >>> 1n, '>>>1n');
和标准库一起计算会报错
console.log(Math.sqrt(4n), 'Math.sqrt(4n)');
应该像下面这样
console.log(Math.sqrt(Number(4n)), 'Math.sqrt(Number(4n))'); // 2
这样也会报错
console.log(4n | 0); // 这样也会报错
和 if else 计算,0n 是 false,其他是 true
if (0n) {
console.log(0n, '0n');
} else {
console.log('else');
}
混合的比较
console.log(0n < 1, '0n < 1'); // true
console.log(0n < true, '0n < true'); // true
console.log(0n == 0, '0n == 0'); // true
console.log(0n == false, '0n == false'); // true
console.log(0n === false, '0n == false'); // false
big 和字符串进行计算,会先转化为字符串,再进行计算
console.log('' + 123n, '123n'); // 123