《JavaScript高级程序设计》第三章:语言基础的3.4小节,读书笔记

116 阅读16分钟

前言

本文介绍《JavaScript高级程序设计》的3.4小节-数据类型,看完你将能回答如下问题:

  1. JS一共有几种数据类型?【3.4.1】
  2. 你知道的检测数据类型的方式?typeof关键字有什么缺陷?【3.4.1】
  3. 有哪些情形,数据返回的是undefined?【3.4.2】
  4. 哪些情形,数据返回的是null?【3.4.3】
  5. null和undefined有什么区别?【3.4.3】
  6. 哪些数据转化为布尔值,返回的是false?【3.4.4】
  7. +0 === -0是true还是false?Object.is(+0, -0)呢?【3.4.5】
  8. JS的十进制和十六进制如何去表示?【3.4.5】
  9. 如何获取JS的最小值和最大值【3.4.5】
  10. NaN等于自身吗?isNaN函数是干嘛用的【3.4.5】
  11. Number函数,parseInt函数,parseFloat函数有啥区别?【3.4.5】
  12. toString方法转化不同的数据类型,注意测试【3.4.6】
  13. 谁身上有toString方法?和String方法有区别吗?【3.4.6】
  14. Symbol数据类型的用途【3.4.7】
  15. Symbol能进行计算吗?可以转化为数字、字符串吗?【3.4.7】
  16. 知道魔术字符串吗?【3.4.7】
  17. 如何遍历到对象的Symbol键名?【3.4.7】
  18. Symbol.for和SymbolKeyfor是干嘛用的?【3.4.7】
  19. 小数能创建BigInt吗?【3.4.8】
  20. 怎么样把BigInt转化为普通整数?【3.4.8】
  21. 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'

注意:

  1. typeof null,值是'object'
  2. 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的值
Booleantruefalse
String非空字符串''空字符串
Number非零数值0、NaN
Object任意对象null
UndefinedN / 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 

如上写法在编辑器中会报错,但是控制台还是可以正常显示: image.png

image.png

编辑器不会报错的写法:

// 0o开头,其余数字在0-7之间,是正确的八进制
let intNum = 55; // 整数 十进制
let octalNum1 = 0o70; // 打印出来是56
let octalNum2 = 0o77; // 打印出来是56

其余数字如果超过了7,编辑器和控制台都会报错:

image.png

image.png

十六进制数

必须以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输出的值就是InfinityNumber.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,表示'本来要返回一个数值的,但是出错了'

如下:

  1. 0除以任何数值,在其他语言会出错,但在ECMAScript中,返回的是NaN
  2. 分子是非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的特点:

  1. NaN的任何操作,多步操作都会返回他自身
  2. NaN不等于任何值,包括他自身
  3. 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. 数值转换

  1. 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
  1. 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

  1. 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类型

  1. 字符串字面量
\n => 换行符
\t => 制表符
\b => 退格符
\r => 回车符
\f => 换页
\\ => 反斜杠
\' => 单引号
\" => 双引号
\` => 反斜杠

注意,转移序列始终只表示一个字符,\n只表示一个字符。比如 'a\n'的长度就是2,

  1. 字符串的特点

一旦创建,他的内存空间不能修改,必须得创建新的内存空间,并销毁原先的内存空间

let lang = 'Java'
lang = 'Java' + 'Script'

如上,会创建一个10个字符的空间给JavaScript字符,并且销毁原先的Java

  1. 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');
  1. 模版字面量与插值表达式
  • 注意,他支持换行的字符串
let str = `
    first line
    second line
`
console.log(str, 'str');

打印出来是这样的 image.png

  • 不仅支持变量,我还在书上看到这个用法
let fn = () => '我是函数返回的值'
let str = `你是谁?${fn()}` // 你是谁?我是函数返回的值 str2
  1. 字符串原始值

通过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

符号的用途就是创建唯一记号,进而用作非字符串形式的对象属性,避免发生冲突

概述

  1. 创建一个符号值
let sym = Symbol()
console.log(typeof sym, 'typeof sym'); // 'symbol'

注意,如下生成的s是一个原始值,不是对象,所以不能用new关键字来初始化,也就是说Symbol函数不能用作构造函数,如下会报错

let s = new Symbol()
console.log(s, 's'); 

image.png

  1. Symbol函数接受一个字符串作为参数
// 接受一个参数作为构造函数
let sym3 = Symbol('foo')        
let sym4 = Symbol('boo')   
console.log(sym3, 'sym') // Symbol(foo) 'sym3'
console.log(sym4, 'sym') // Symbol(boo) 'sym4'
  1. 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函数的描述

  1. Symbol值不能和其他数据类型进行运算
console.log(sym6 + 'a', "Symbol('a') + a");

结果如下: image.png

可以显式转化为字符串: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. 作为属性的几种方式
// 法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
})

image.png

  1. 用点的方式访问打印出来是undefined,因为点运算符后面通常都是字符串
console.log(obj3.sym3, 'obj3'); // undefined
  1. 声明为对象内部的属性时,必须为方括号[],否则就是普通的属性
let obj4 = {
    sym3: '不是foo的值'
}
console.log(obj4, 'obj4');
  1. 声明为对象内部的值,用作常量
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