data-types
类别
截止目前为止(2024),分为两大类(简单/复杂),八种数据类型
- 简单数据类型(原始)
- 简单数据类型占用内存小,存储在栈空间,栈空间存储值
- 分别为
- String
- Number
- Boolean
- Undefined
- Null
- Symbol
- BigInt
- 复杂数据类型
- 复杂数据类型占用内存大,存储在堆空间,栈空间存储其引用(指针)指向堆空间
- Object
- 复杂数据类型占用内存大,存储在堆空间,栈空间存储其引用(指针)指向堆空间
查看以下代码内存图
js
const obj = { name: "ice", age: 24 };
const name = "ice";
区分数据类型
- typeof
- instanceof
- Object.prototype.toString.call(this)
typeof
它可以用来区分出简单数据类型或函数
js
typeof "ice"; // 'string'
typeof 25; // 'number'
typeof 25n; // 'bigint'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof (() => {}); // 'function'
typeof {}; // 'object'
typeof []; // 'object'
typeof null; // 'object'
它不能用来区分对象类型,因为都会是 object
(除了 func),另外 typeof null === 'object'
,即诞生以来即使如此。因为在 js 中 存储值,分为两种
- 类型标签
- 存储值
object 和 null 的类型标签都是 0 ,null 在大多数语言中代表空指针 (0x00),所以它 typeof null === 'object'
instanceof
用来区分复杂数据类型,后续在原型链一文会详细介绍
js
/**
* 1. A instanceof B
* A 的原型链中是否出现过 B 的原型对象
*/
({}) instanceof Object; // true
// 等同于
// ({}).__proto__ === Object.prototype
[] instanceof Array; // true
toString
Object.prototype.toString
方法,可以判断复杂/简单数据类型,大部分包装类都会实现自己的 toString
方法(重写),利用它以及 call
改变其 this 指向,可以拿到精确的类型
js
Object.prototype.toString.call(0); // '[object Number]'
Object.prototype.toString.call(""); // '[object String]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(() => {}); // [object Function]
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call({}); // '[object Object]'
getDataType
我们知道,可以通过 toString 拿到更精确的数据类型,那么我们来实现一个 getDataType 工具函数
js
function getDataType(value) {
const type = Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
return type;
}
包装类
js
// 1. 包装类
const name2 = new String("ice");
// 2. 字面量
const name1 = "ice";
从上面的代码,我们可以得知 name1 只是一个单纯的字符串,而 name2 是一个对象。
js
const name1 = new String("ice");
name1.toUpperCase(); // ICE
const name2 = "ice";
name2.toUpperCase(); // ICE
- name1 它的“隐式原型”是
String.prototype
,因为它是 String 的实例对象,所以它继承了父类原型上的方法,因此可以调用 toUpperCase 方法 - name2(原始值) 它也可以调用 toUpperCase,这是为什么呢? 它不就是一个简单的字符串吗?
- 每当用到某个原始值的方法,后台都会创建对应的包装类型对象,从而暴露出操作原始值的各种方法
- 创建 String 类型的实例
- 调用实例上的特定方法
- 销毁实例
- 每当用到某个原始值的方法,后台都会创建对应的包装类型对象,从而暴露出操作原始值的各种方法
也就是说
js
const name2 = "ice";
const uName2 = name2.toUpperCase(); // ICE
// 相当于
let name3 = new String("ice");
const uName3 = name3.toUpperCase(); // ICE
name3 = null;
前面我们有说道原始类型/复杂数据的区别,这也是这样设计的原因
隐式类型转换
有三种隐式类型转换
- toPrimitive
- toNumber
- toString
思考以下代码:
js
const info = {
age: 24,
valueOf() {
return this.age++;
},
};
if (info == 24 && info == 25) {
console.log("age: ", info.age); // age:25
}
通过以上代码,我们可以知道 == ,比较的时候调用了 valueOf 方法,接下来我们开始探究这一行为
toPrimitive
toPrimitive(input, PreferredType?: number | string)
- input 为输入的值
- PreferredType 为首选类型,如果没有传入参数,为 number 类型
TIP
PreferredType 只是一个类型符号,不一定返回的值就是该类型值
type:string
当 PreferredType:string
有以下规则
js
// pseudocode
function toPrimitive(input, PreferredType) {
if (input === 原始值) {
return input;
} else if (input.toString() === 原始值) {
return input.toString();
} else if (input.valueOf() === 原始值) {
return input.valueOf();
}
throw new TypeError();
}
toPrimitive(unknown, string);
根据以上规则转为 type:string 规则
- 如果是原始值,直接返回
- 如果
input.toString()
是原始值,直接返回 - 如果
input.valueOf()
是原始值,直接返回 - 抛出 TypeError
type:number
js
// pseudocode
function toPrimitive(input, PreferredType) {
if (input === 原始值) {
return input;
} else if (input.toString() === 原始值) {
return input.toString();
} else if (input.valueOf() === 原始值) {
return input.valueOf();
} else if (input.valueOf() === 原始值) {
return input.valueOf();
} else if (input.toString() === 原始值) {
return input.toString();
}
throw new TypeError();
}
toPrimitive(unknown, string);
toPrimitive(unknown, number);
- 从代码中,我们可以得知 type 的类型不同,也只是 toString | valueOf 调用的先后顺序不同而已
- number -> valueOf -> toString
- string -> toString -> valueOf
ToString
input | output |
---|---|
string | 无需转换 |
undefined | 'undefined' |
null | 'null' |
123 | '123' |
false | 'false' |
{} | toPrimitive({}, string)获得原始值,再进行 toString |
ToNumber
input | output |
---|---|
number | 无需转换 |
'' | 0 |
'123' | 123 |
'abc' | NaN |
false | 0 |
null | 0 |
undefined | NaN |
{} | toPrimitive({}, number)获得原始值,再进行 toNumber |
实战案例
+ 运算符
js
// + 运算符,可以是值运算也可以是字符串相加
1 + null; // 1
1 + undefined; // NaN (Not a Number)
// 如果一段出现了字符串,字符串类型优先
1 + ""; // 1
1 + "A"; // '1A'
1 + "1"; // 一端出现了字符串,直接拼接 -> '11'
/**
* {} 空对象,不是原始数据类型,不能进行相加操作,所有要通过 toPrimitive 转为原始数据类型,type 没有指定的情况为 number
* toPrimitive({}, number) 先 valueOf (返回自身,不是原始类型),toString(原始数据类型 即:'[object, Object]')
* 1 + '[object, Object]' -> '1[object, Object]' 一端出现了 str 为字符串拼接
*/
1 + {}; // 1[object, Object]
1 + []; // '1' 推导过程:[].toString() -> "" -> 1 + "" -> '1'
== 运算符
- null 和 undefined 相等,且不能转换为其他类型进行比较
- 如果一端出现 Number 类型,其他类型都转为 Number 类型,如果是复杂数据类型,先进行 toPrimitive(input, number) 也就是先拿到 valueOf 的原始值,在进行 ToNumber 操作
- 如果是复杂数据类型,则进行引用比较
- NaN 不等于自身
js
null == undefined; // true
NaN == NaN; // false
NaN != NaN; // true
null == 0; // false
undefined == 0; // false
"" == 0; // true
1 == "1"; // true
false == 0; // true
1 == "true"; // false "true" -> NaN (toNumber)
({}) == {}; // false