JavaScript(一):数据类型

171 阅读8分钟

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)

区别

  1. 内存分配区别

    • 基本数据类型 由于占据的空间大小固定且较小,会被存储在当中

    • 引用数据类型 存储在当中,变量的值是一个指向堆内存中对象地址的指针( 这是因为:对象值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。而对象地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。)

  2. 访问方式区别

    • 基本数据类型 值访问

    • 引用数据类型 地址访问

  3. 变量拷贝区别

    • 基本数据类型 完全复制,开辟一个新的内存空间,复制后 2 个变量是独立的

    • 引用数据类型 只复制地址,复制后 2 个地址指向同一个堆内存对象,共享同一个内存空间,一旦一方修改,另一方也会受到影响

  4. 参数传递区别

    • 基本数据类型 变量值传递给参数(互不影响)

    • 引用数据类型 参数的值为对象地址(互相影响)

  5. 比较方式

    • 基本数据类型 值比较

    • 引用数据类型 比较的是内存地址

  6. 内存清理

    • 基本数据类型 无需显式清理,执行上下文销毁时自动释放内存

    • 引用数据类型 通过垃圾回收机制周期性检查并回收不再被引用的对象内存

举例说明:

// 基本数据类型
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。

decimal.js - npm

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 内置的日期构造函数,它本身就是一个函数

注意:

  1. console.log(typeof null); 输出的是 object ;
  2. 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 属性可以被覆盖或修改,因此它并不是检查数据类型的最可靠方法。