8 大数据类型
- String
- Number
- Boolean
- Null
- Undefined
- Symbol(ES6)
- BigInt(ES11)
- Object
基本类型和引用类型
基本类型
除了 Object,剩下 7 个都是基本数据类型。
在 JavaScript 中,原始类型被设计为不可变(immutable)的数据类型,这意味着一旦值被创建,就不能直接修改它的内容。当你尝试"改变"一个原始类型的值时,实际上你是创建了一个新的原始值,并将变量指向这个新的值。原始值本身并没有被修改。
let num = 5; // 创建一个 Number 类型的原始值 5 并赋值给 num
num = 10; // 创建一个新的 Number 类型的原始值 10 并赋值给 num,num 不再指向 5
引用类型
也叫对象类型,Object(除基本数据类型外的都是引用数据类型,如 Array、Date、RegExp、Function)
区别
-
内存分配区别
-
基本数据类型 由于占据的空间大小固定且较小,会被存储在栈当中
-
引用数据类型 存储在堆当中,变量的值是一个指向堆内存中对象地址的指针( 这是因为:对象值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。而对象地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。)
-
-
访问方式区别
-
基本数据类型 值访问
-
引用数据类型 地址访问
-
-
变量拷贝区别
-
基本数据类型 完全复制,开辟一个新的内存空间,复制后 2 个变量是独立的
-
引用数据类型 只复制地址,复制后 2 个地址指向同一个堆内存对象,共享同一个内存空间,一旦一方修改,另一方也会受到影响
-
-
参数传递区别
-
基本数据类型 变量值传递给参数(互不影响)
-
引用数据类型 参数的值为对象地址(互相影响)
-
-
比较方式
-
基本数据类型 值比较
-
引用数据类型 比较的是内存地址
-
-
内存清理
-
基本数据类型 无需显式清理,执行上下文销毁时自动释放内存
-
引用数据类型 通过垃圾回收机制周期性检查并回收不再被引用的对象内存
-
举例说明:
// 基本数据类型
let a = 1
let b = a
console.log(a === b); // true
b = 2
console.log("a:",a); // 1
console.log("b:",b); // 2
console.log(a === b); // false
-----
// 引用数据类型
let obj1 = [1, 2]
let obj2 = obj1
console.log(obj1 === obj2); // true
-----
obj2[0] = 99
console.log("obj1:",obj1); // [99, 2]
console.log("obj2:",obj2); // [99, 2]
console.log(obj1 === obj2); // true
-----
let a = "5";
let b = "5";
console.log(a === b); // true,基本数据类型值比较
let objA = new String("5");
let objB = new String("5");
console.log(objA === objB); // false,因为它们是两个不同的对象
基本包装类型
-
String
-
Number
-
Boolean
在 JavaScript 中,"包装对象"(Wrapper Objects)主要涉及到基本数据类型(如 Number、String、Boolean)和它们的对象形式之间的转换。
当你尝试在一个基本数据类型上调用一个方法时,JavaScript 会自动将该基本数据类型转换为对应的对象,以便可以调用其方法。这个过程是自动的,并且是临时的,一旦方法调用完成,对象就会被销毁,重新返回基本数据类型。
例如:
let str = "abc";
console.log(str.charAt(0)); // 输出 "a"
实际上后台会发生以下过程:
let str = 'abc';
// 1 找到对应的包装对象类型,然后通过包装对象创建一个和基本类型值相同的对象
let _str = new String('abc');
// 2 然后这个对象就可以调用包装对象下的方法
console.log(_str.charAt(0)); // a
// 3 之后这个临时创建的对象就被销毁了,这整个过程是一瞬间的动作
_str = null;
// 实际上并没有改变字符串本身的值
console.log(str); // abc
为什么无法给基本包装类型添加属性?
因为引用类型所创建的对象,在执行的期间一直在内存中;而基本包装类型对象只是存在了一瞬间,所以我们无法给基本类型添加属性和方法。
那么我们怎么才能给基本类型添加方法或属性呢?
答案是在基本包装类型对象的原型添加属性
例如:String.prototype.age = 10
Number 的坑
坑1:0.1 + 0.2 !== 0.3
0.1 + 0.2 !== 0.3
0.1 + 0.2 = 0.30000000000000004
很奇怪对吧。在 JavaScript 中,当你使用浮点数进行运算时,可能会遇到精度问题。这是因为 JavaScript 中的数字(包括整数和浮点数)都是按照 IEEE 754 标准以 64 位双精度浮点数来存储的。这种表示方法在处理某些小数时无法精确表示,导致了一些看似简单的运算结果并不完全符合预期。
解决方法:
使用一种称为“epsilon 比较”的方法,即比较两个数的差值是否小于一个非常小的数(epsilon[ˈepsɪlɒn]),这个数通常是一个接近于 0 的正数,比如 1e-9
const epsilon = 1e-10; // 定义一个非常小的误差范围
const a = 0.1 +0.2; // 0.30000000000000004
const b = 0.3;
console.log(Math.abs(a - b) < epsilon); // 输出 true
Math.abs() 是取绝对值的意思。
注意:不能使用 toFixed,因为 toFixed 并不是四舍五入的,而是四舍六入五看情况。
或者使用第三方库如 decimal.js。
const a = '100.11'
const b = '99.1'
const c = Number(a)
const d = Number(b)
const e = c + d
console.log("a:", a)
console.log("b:", b)
console.log("c:", c)
console.log("d:", d)
console.log("e:", e)
const f = Number('199.21') // 199.20999999999998
console.log(e == f) // false
也是同样的问题,所以使用 Number 就一定要注意精度问题。
Java 中的 double 类型也有类似的问题,所以 Java 中一般用 bigDecimal 类型来处理小数。
坑2:"2" > "10"
console.log("2" > "10"); // true
当两个字符串进行比较时,JavaScript 会按照字符的 Unicode 码点值进行逐字符比较,而不是按照数值比较!
除此之外还要注意:
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
console.log("2" > 10); // false, 字符串 "2" 会被转换为数值 2
console.log("2" == 2); // true, 字符串 "2" 会被转换成数字 2
console.log("0" == false); // true, "0" 会被转换为数值 0,false 也等于 0
// parseInt 和 parseFloat 会从字符串的开头开始解析数字,直到遇到非数字字符为止
console.log(parseInt("123abc")); // 123, 只会解析到数字部分
console.log(parseFloat("12.34abc")); // 12.34, 同样会解析到小数部分
console.log(parseInt("abc123")); // NaN, 由于开头不是数字,返回 NaN
console.log(1 + "1"); // "11", 数字 1 被转换为字符串
console.log("1" + 1); // "11", 数字 1 被转换为字符串
substr 和 substring 的区别
-
substr(start, length)
截取从起始位置开始的指定数量字符,
第二个参数表示要提取的数量,默认是到字符串的结束位置。
-
substring(start, end)
截取字符串中两个指定索引号之间的字符,
第二个参数表示要提取的字符串的结束位置,默认是到字符串的结束位置。
相同点:
-
都用于截取字符串
-
第一个参数都是表示提取字符的开始索引位置
不同点:
-
第一个参数:
-
substr() 第一个参数可以是负整数,
-
substring() 第一个参数只能是正整数
-
-
第二个参数:
-
substr() 第二个参数表示截取长度,
-
substring() 第二个参数表示索引结束位置,不包括该索引的值
-
检测数据类型的方法
typeof
typeof 的返回值是一个字符串,用来说明变量的数据类型。
console.log(typeof ""); //string
console.log(typeof 1); //number
console.log(typeof true); //boolean
console.log(typeof undefined); //undefined
console.log(typeof {}); //object
console.log(typeof null); //object !!!
console.log(typeof []); //object
console.log(typeof function(){}); //function
console.log(typeof console.log); //function
console.log(typeof Error); //function
console.log(typeof Symbol()); //symbol(es6)
console.log(typeof 23423n); //bigint(谷歌67版本新提出)
console.log(typeof new Date()); //object,因为使用 new Date() 创建了一个日期对象
console.log(typeof Date()); //string,因为 Date() 返回的是一个代表当前日期和时间的字符串
console.log(typeof Date); //function,因为 Date 是 JavaScript 内置的日期构造函数,它本身就是一个函数
注意:
console.log(typeof null);输出的是 object ;- typeof 判断引用数据类型(如 Array、Date等)时,除了判断函数会输出 function,其它都是输出 object
instanceof
instanceof 的返回值是布尔值,用于判断一个变量是否属于某个对象的实例。
当使用 instanceof 运算符来检查基本数据类型时, 它会返回 false。
原理是检测左侧实例对象的原型链上,是否存在右侧构造函数的 prototype 属性。
右边必须为对象,否则会报错!
console.log(9 instanceof Number); // false,
/*
因为基本数据类型(如数字、字符串、布尔值)并不是对象,它们没有原型链,
它们只是原始值。
当使用 instanceof 运算符来检查基本数据类型时,
它会返回 false
*/
console.log(true instanceof Boolean); // false 基本数据类型
console.log('libo' instanceof String); // false 基本数据类型
console.log(String(1) instanceof String); // false
console.log(new String(1) instanceof String); // true
console.log(new String(1) instanceof Object); // true
/*
String(1) 返回的是字符串
new String(1) 返回的是 String 构造函数的实例对象
*/
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
console.log(typeof null); // object
console.log(null instanceof Object); // false !!! null instanceof 任何都是 false
console.log(typeof NaN); // number
console.log(NaN instanceof Number); // false
/*
NaN 虽然表示非数字,但是 js 规定它就是 number 类型。
NaN 不是一个对象,因此它不能是任何构造函数的实例,
所以 NaN instanceof Number 是 false
*/
console.log(typeof Number); // function
console.log(Number instanceof Function); // true,Number 是一个函数对象
console.log(Object instanceof String); // false,Object 并不是通过 String 构造函数创建的实例
console.log(String instanceof Object); // true,所有函数对象都继承自 Object.prototype
console.log(String instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
console.log({} instanceof Object); // true
console.log({} instanceof Function); // false
console.log(String); // ƒ String() { [native code] }
console.log(Object); // ƒ Object() { [native code] }
console.log(Function); // ƒ Function() { [native code] }
constructor(构造函数)
console.log('1'.constructor == String); // true
console.log((1).constructor == Number); // true
console.log(new Number(1).constructor == Number); // true
console.log([].constructor == Array); // true
console.log(new Function().constructor == Function); // true
console.log(true.constructor == Boolean); // true
console.log(new Date().constructor == Date); // true
console.log(new Error().constructor == Error); // true
console.log(Date().constructor == String); // true
console.log(document.constructor == HTMLDocument); // true
console.log(window.constructor == Window); // true
constructor 属性可以被覆盖或修改,因此它并不是检查数据类型的最可靠方法。