js 数据类型
# js 数据类型
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol、bigint
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
Number 基本类型可以精确表示的最大整数是 2^53
对于基本类型,赋值(=)是值的拷贝,比较(===)的是实际的值,
对于引用类型(Array 也是一种 Object),赋值(=)是引用地址的拷贝,比较(===)的是引用地址:
# 基本数据类型和引用数据类型的区别
- 存储位置不同
基本数据类型:存储在栈(stack)中
引用数据类型:引用地址存储在栈(stack)中,对象本身的值存储在内存堆
- 访问机制不同
基本数据类型:按值访问
引用数据类型:按引用访问
- 复制变量时的不同
基本数据类型:复制值
引用数据类型:复制的是内存地址,两个中任何一个作出的改变,另一个就会同时改变
- 参数传递的不同
基本数据类型:按值传递
引用数据类型:按内存地址传递,函数内部对这个参数的修改会体现在外部,它们都指向同一个对象
# String 和 new String 的区别
三个基本包装类的创建方式:
字面量:
var str1 = 'hello world'
==> 基本数据类型字符串声明:
var str2 = String('hello world')
==> 基本数据类型字符串表达式(new):
var str3 = new String('hello world')
==> 引用数据类型字符串对象(注意:是对象!!!)
结论:str1 === str2 // true
,str1 === str3 // false 对象和基本类型值不相等
# Symbol
作用:
可以用来表示一个独一无二的变量防止命名冲突
还可以利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。
概念:表示独一无二的值
语法:let sym = Symbol();
Symbol 值通过 Symbol 函数生成
注意:
Symbol 函数前不能使用 new 命令,这是因为生成的 Symbol 是一个原始类型的值,不是对象
Symbol 值不能与其他类型的值进行运算,会报错 如:
"your symbol is " + sym
Symbol 值可以显式转为字符串。如:
String(sym) // 'Symbol(My symbol)'
或者sym.toString() // 'Symbol(My symbol)'
Symbol 值作为键名,不会被常规方法遍历得到
Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
- 使用同一个 Symbol 值,Symbol.for()
let s1 = Symbol.for("foo");
let s2 = Symbol.for("foo");
s1 === s2; // true
2
3
4
# bigint
可以表示任意大的整数,BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
BigInt() 不是构造函数,因此不能使用 new 操作符。
语法:BigInt(value); value: 创建对象的数值。可以是字符串或者整数。
除了通过 BigInt(),我们还可以通过在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n
let valA = 10n;
let valB = BigInt(10);
console.log(valA === valB); // true
10n == 10; // true
2
3
4
5
6
# BigInt 和 Number
因为隐式类型转换可能丢失信息,所以不允许在 bigint 和 Number 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由 BigInt 或 Number 精确表示。
10n + 1; // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
Math.max(2n, 4n, 6n); // TypeError...
2
# BigInt 和 String
难免会遇到数字和字符串的转换,BigInt 也不例外,不过可惜的是 BigInt 转为 String 时,其标志性的 n 会被省略
String(10n); // '10'
"" + 11n; // '11'
2
# 大整数
JS 超过 53 个二进制位的数值,无法保持精度
JS 大于或等于 2 的 1024 次方的数值,JavaScript 无法表示,会返回 Infinity。
// 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1; // true
// 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024); // Infinity
2
3
4
5
在(-2^53, 2^53)不包含边界范围内,双精度数表示和整数是一对一的,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。
Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)(2^53 - 1)9007199254740991
Number.MIN_SAFE_INTEGER 常量 代表在 JavaScript 中最小的安全的 integer 型数字(-(2^53 - 1))-9007199254740991
大数相加:
function add(a, b) {
// 取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
// 用 0 去补齐长度
a = a.padStart(maxLength, 0); // "00000000000003782647863278468012934670"
b = b.padStart(maxLength, 0); // "23784678091370408971329048718239749083"
// 定义加法过程中需要用到的变量
let t = 0;
let f = 0; // "进位"
let sum = "";
for (let i = maxLength - 1; i >= 0; i--) {
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t / 10);
sum = (t % 10) + sum;
}
if (f == 1) {
sum = "1" + sum;
}
return sum;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 浮点数
JS 采用 IEEE 754 双精度版本(64 位),JavaScript 所有数字都保存成 64 位浮点数
计算机基础:
计算机将所有数据以二进制的形式存储
计算机用有限的大小来存储数据(因为现实生活中不存在无限大的内存或硬盘)
单精度浮点数 float 总共包含 32 位,其中 1 位表示符号、8 位表示指数,最后 23 位表示小数;
双精度浮点数 double 总共包含 64 位,其中 s(sign) 1 位表示符号,e(exponent) 11 位表示指数,最后 m(mantissa) 52 位表示小数
JavaScript 中数字的存储机制 (s) _ (m) _ (2^e)
问题:为什么 JS 中
0.1+0.2 != 0.3 ? // 0.1 + 0.2 = 0.30000000000000004
对于十进制转二进制,整数部分除二取余,倒序排列,小数部分乘二取整,顺序排列
0.1 转化为二进制 0.0 0011 0011 0011 0011 0011 0011 … (0011 循环)
0.2 转化为二进制 0.0011 0011 0011 0011 0011 0011 0011 … (0011 循环)
原因:计算机将所有数据以二进制的形式存储,并且存储空间有限,十进制小数转为二进制小数的过程中,会损失精度(计算机会舍弃后面的数值)
解决方案:
第三方库 BigNumber.js
将浮点数转化成整数计算
计算结果:
// 0.1
e = -4;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)
// 0.2
e = -3;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)
//这里的m指的是小数点后的52位,e为m的指数,小数点前的整数部分就是隐藏位s来表示符号
//如果发现指数e不一致时,一般采用右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出
//转化之后进行求和
e = -3; m = 0.1100110011001100110011001100110011001100110011001101 (52位)
+
e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)
// 得到
e = -3; m = 10.0110011001100110011001100110011001100110011001100111 (52位)
// 保留一位整数
e = -2; m = 1.00110011001100110011001100110011001100110011001100111 (53位)
// 发现超过了52位,于是要做四舍五入,因为无法区分哪个更接近,于是规则是保留偶数的一个,得到最终的二进制数
m=1.0011001100110011001100110011001100110011001100110100 (52位)
// 然后得到最终的二进制数
1.0011001100110011001100110011001100110011001100110100 * 2^-2 = 0.010011001100110011001100110011001100110011001100110100
// 现在转化为十进制,二进制小数转化为十进制的方法是小数点后 第一位 *2 ^ -1,第二位 *2 ^ -2,以此类推
// 可以利用等比数列求和公式,最终求得十进制数为0.30000000000000004
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# js 检测数据类型的几种方法
# 1. typeof
typeof 会返回一个变量的基本类型
主要能判断'number','boolean','string','undefined','symbol', 'bigint','function','object',八种数据类型
typeof 对于原始类型来说,除了 null 都可以显示正确的类型
typeof 1; // 'number'
typeof "1"; // 'string'
typeof undefined; // 'undefined'
typeof true; // 'boolean'
typeof Symbol(); // 'symbol'
2
3
4
5
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
typeof []; // 'object'
typeof {}; // 'object'
typeof console.log; // 'function'
2
3
缺点:
(1)不能判断变量具体的数据类型比如数组、正则、日期、对象,因为都会返回 object,
(2)判断 null 的时候返回的是一个 object,这是 js 的一个缺陷,判断 NaN 的时候返回是 number
typeof 原理
typeof 原理: 不同的对象在底层都表示为二进制,在 Javascript 中二进制前(低)三位存储其类型信息。
- 000: 对象
- 010: 浮点数
- 100:字符串
- 110: 布尔
- 1: 整数
typeof null 为"object"的 原因是因为 不同的对象在底层都表示为二进制,在 Javascript 中二进制前(低)三位都为 0 的话会被判断为 Object 类型,null 的二进制表示全为 0,自然前三位也是 0,所以执行 typeof 时会返回"object"。
# 2. instanceof
instanceof 返回的是一个布尔值
缺点:
检测不了 number,boolean,string
instanceof 不能检测来自 iframe 的数组
instanceof 是判断类型的 prototype 是否出现在对象的原型链中,但是对象的原型可以随意修改,所以这种判断并不准确。
const obj = {};
obj.__proto__ = Array.prototype;
// Object.setPrototypeOf(obj, Array.prototype)
obj instanceof Array; // true
2
3
4
# 3. Object.prototype.toString.call(value)(最好的)
在 Number、String、Boolean、Array、Function、RegExp...这些类的原型上都有一个 toString 方法:这个方法就是把本身的值转化为字符串
(12).toString()//'12'
(true).toString()//'true'
[12,23].toString()//'12.23'
在 Object 这个类的原型上也有一个方法 toString,但是这个方法并不是把值转换成字符串,而是
返回当前值得所属类详细信息,固定结构:'[object 所属的类]'
var obj = { name: "珠穆朗玛峰" };
obj.toString(); //[object Object]
2
想知道谁的所属类信息,我们就把这个 toString 方法执行,并且让 this 变为我们检测的这个数据值,那么方法返回的结果就是当前检测这个值得所属类信息
Object.prototype.toString.call(12)//[object Number]
缺点:Object.prototype.toString.call() 不能校验自定义类型
function Animal() {}
let a = new Animal();
Object.prototype.toString.call(a);
("[object Object]");
2
3
4
# 4. Array.isArray
判断是否为数组 Array.isArray([]) // true
Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。
当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length - 1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
2
3
4
5
6
7
8
9
10
# 5. 判断是不是 NaN
(1) Object.is var c = NaN; Object.is(c, NaN) // true
(2) NaN 是唯一一个不等于任何自身的特点
function isNaN(n) {
if (n !== n) {
return true;
} else {
return false;
}
}
2
3
4
5
6
7
(3) typeof(n) === "number" && isNaN(n)
hasOwnproperty 检测当前属性是否为对象的私有属性
# js 类型转换机制
常见的类型转换有:
- 强制转换(显示转换)
显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:
Number() , parseInt(),String(),Boolean()
- 自动转换(隐式转换)
可以归纳为两种情况发生隐式转换的场景:
(1)比较运算(==、!=、>、<)、if、while 需要布尔值地方
(2)算术运算(+、-、*、/、%)
在 JS 中类型转换只有三种情况:
(1) 转换为布尔值
在条件判断时,除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true,包括所有对象。
(2) 转换为数字
(3)转换为字符串
# parseInt
语法:parseInt(string, radix)
如果 radix 在 2-36 之外会返回 NaN。
在没有指定基数,或者基数为 0 的情况下,JavaScript 作如下处理:
(1) 如果字符串 string 以"0x"或者"0X"开头, 则基数是 16 (16 进制).
(2) 如果字符串 string 以"0"开头, 基数是 8(八进制)或者 10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用 10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出 radix 参数的值。
(3) 如果字符串 string 以其它任何值开头,则基数是 10 (十进制)。
- 实例:
['1','2','3'].map(parseInt)
[1, NaN, NaN]
(1) parseInt('1', 0); // 1 (parseInt 的处理方式,这个地方 item 没有以"0x"或者"0X"开始,8 和 10 这个基数由实现环境来定,ES5 规定使用 10 来作为基数,因此这个 0 相当于传递了 10)
(2) parseInt('2', 1); // NaN (因为 parseInt 的定义,超出了 radix 的界限)
(3) parseInt('3', 2); // NaN (虽然没有超出界限,但是二进制里面没有 3,因此返回 NaN)
- 实例:
['10', '10', '10', '10', '10'].map(parseInt)
[10, NaN, 2, 3, 4]
# 对象转原始类型
对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:
如果已经是原始类型了,那就不需要转换了
调用 x.valueOf(),如果转换为基础类型,就返回转换的值
调用 x.toString(),如果转换为基础类型,就返回转换的值
如果都没有返回原始类型,就会报错
# 四则运算符
加法运算符不同于其他几个运算符,它有以下几个特点:
运算中其中一方为字符串,那么就会把另一方也转换为字符串
如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + "1"; // '11'
true + true; // 2
4 + [1, 2, 3]; // "41,2,3"
2
3
另外对于加法还需要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。
数字(Number)/字符串(String)以外的原始类型相加:
/**
* 数字(Number)/字符串(String)以外的原始类型相加
*/
console.log(true + true); // 2
console.log(true + null); // 1
console.log(true + undefined); //NaN
console.log(undefined + null); //NaN
console.log(undefined + undefined); //NaN
console.log(null + null); //0
2
3
4
5
6
7
8
9
当数字与字符串以外的,其他原始数据类型直接使用加号运算时,就是转为数字再运算,这与字符串完全无关。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
4 * "3"; // 12
4 * []; // 0
4 * [1, 2]; // NaN
2
3
# 比较运算符
如果是对象,就通过 toPrimitive 转换对象
如果是字符串,就通过 unicode 字符索引来比较
let a = {
valueOf() {
return 0;
},
toString() {
return "1";
},
};
a > -1; // true
2
3
4
5
6
7
8
9
在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。
# 常见 js 类型判断
# !!双感叹号
能判断返回 false 的七个值:undefined,null,"",-0,0,NaN,false
用两个!!就可以将变量转化为对应布尔值。
# ===
=== 不能判断以下情况:+0 === -0(true),NaN === NaN (false)
,需要用Object.is
# undefined == null
console.log( undefined == null ) //true
console.log( undefined === null ) //false
Javascript 规范: 要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,并且规定 null 和 undefined 是相等的。
null 和 undefined 都代表着无效的值。
强制转换为布尔类型,两者都是 false
强制转换为数值,undefined 返回的是 NaN,null 返回的是 0。用 isNaN() 验证,undefined 不是数字,null 是数字。
比较操作符 == 不会自动转换 undefined 和 null
// 操作符 `==` 不会自动转换 undefined 和 null
undefined == false; // false
undefined == true; // false
null == false; // false
null == true; // false
undefined == 0; // false
null == 0; // false
// 操作符会自动转换其他值为数值
true == 1; // true
true == 2; // false
2
3
4
5
6
7
8
9
10
11
12
# null 和 undefined 区别
null 表示"没有对象",即该处不应该有值。典型用法是:
(1) 如果定义的变量在将来用于保存对象,那么最好将该变量初始化为 null,而不是其他值。
(2) 当一个数据不再需要使用时,我们最好通过将其值设置为 null 来释放其引用
(3) 作为对象原型链的终点。
undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于 undefined。 var data; console.log(data === undefined); //true
(2) 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
(3)对象没有赋值的属性,该属性的值为 undefined。
(4)函数没有返回值时,默认返回 undefined。
# 为什么 null 的 typeof 是 object
这是一个存在很久了的 Bug
因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
# Number.isNaN 与 isNaN 的区别
Number.isNaN 不存在类型转换的行为
isNaN:isNaN 会通过 Number 方法,试图将"参数"转换成 Number 类型,如果转换失败,返回 true
Number.isNaN:Number.isNaN 只是严格的判断传入的参数是否全等于 NaN
//isNaN
console.log(isNaN(null)); //false
console.log(isNaN(true)); //false
console.log(isNaN(false)); //false
console.log(isNaN(0)); //false
console.log(isNaN(undefined)); //true
console.log(isNaN("AB")); //true
console.log(isNaN({ a: 1 })); //true
console.log(isNaN(NaN)); //true
//Number.isNaN
console.log(Number.isNaN(null)); //false
console.log(Number.isNaN(true)); //false
console.log(Number.isNaN(false)); //false
console.log(Number.isNaN(0)); //false
console.log(Number.isNaN(undefined)); //false
console.log(Number.isNaN("AB")); //false
console.log(Number.isNaN({ a: 1 })); //false
console.log(Number.isNaN(NaN)); //true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 字符串数字比较
//1.数字比较
console.log("数字比较:" + (12 < 3)); //false
//2.字符串数字和数字比较 统一转换成数字进行比较
console.log("字符串数字和数字比较:" + (11 < "5")); //false
//3.字符串和数字进行比较 字符串为非纯数字时,则将非数字字符串转成数字的时候会转换为NaN,当NaN和数字比较时不论大小都返回false.
console.log("字符串和数字进行比较:" + (11 > "FlyElephant")); //false
console.log("字符串和数字进行比较:" + (11 < "FlyElephant")); //false
//4.字符串数字比较 转换成ASCII码比较
console.log("字符串数字比较:" + ("11" < "5")); //true
console.log("11".charCodeAt()); //49
console.log("5".charCodeAt()); //53
//5.字符串比较 转换成ASCII码进行比较
console.log("字符串比较:" + ("博客园" < "FlyElephant"));
console.log("博客园".charCodeAt()); //21338
console.log("FlyElephant".charCodeAt()); //70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 加法运算
// 数字(Number)/字符串(String)以外的原始类型相加转为数字再运算
console.log(true + true); // 2
console.log(true + null); // 1
console.log(true + undefined); //NaN
console.log(undefined + null); //NaN
console.log(undefined + undefined); //NaN
console.log(null + null); //0
console.log([] + []); //""
console.log({} + {}); //"[object Object][object Object]"
console.log({} + []); //"[object Object]"
// 一元加号运算时,唯一的运算元相当于强制求出数字值的Number([])运算
console.log(+[]); // 0
console.log(+{}); // NaN
console.log(+null); //0
console.log(+true); //1
console.log(+undefined); //NaN
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18