前言
在 JavaScript 中,数据类型及其计算是非常重要的基础知识。JavaScript 的数据类型可以按其存储方式的不同分为基本数据类型和引用数据类型。JavaScript 还支持不同类型之间的自动类型转换和手动类型转换。以下是相关的知识点
栈和堆
栈内存(Stack):
- 存储简单的值类型数据。
- 数据大小固定、较小,便于快速存取。
- 自动管理内存分配和释放(由 JavaScript 引擎负责)。
栈内存的工作流程:
当一个值类型的变量被创建时,JavaScript 引擎会在栈中为它分配一个固定大小的内存空间。变量的值直接存储在该空间中,当变量超出作用域时,栈内存会自动回收。
堆内存(Heap):
- 存储复杂的引用类型数据(如对象、数组)。
- 数据大小不固定,适合存储动态变化的数据结构。
- 需要垃圾回收机制(Garbage Collection)来管理内存的释放。
堆内存的工作流程:
引用类型的数据存储在堆内存中,栈内存中的变量保存的是指向堆中该数据的地址。当变量超出作用域或不再被引用时,垃圾回收器(GC)会自动检测并释放这些不再使用的堆内存。
let str = 'hello world'
let num = 1200
let arr1 = [1,2,3,4]
let obj2 = {name:'张三',age:20}
let obj3 = obj2
obj3.age = 30
console.log(obj2.age)
obj2 = null
console.log(obj3)
基本数据类型(Primitive Types)
基本数据类型:Number、String、Boolean、Undefined、Null、Symbol、BigInt直接存储在内存的**栈(stack)**中。栈是一种用于存储小数据的内存结构,速度很快,具有先进后出的特性。
特点:
- 存储位置:存储在栈内存中。
- 直接存储值:变量保存的是实际的值,而不是对其他内存位置的引用。
- 独立存储:每个变量都有自己的存储空间,即使两个变量的值相同,它们在内存中的存储是相互独立的。
引用数据类型(Reference Types)
引用类型Object、Array、Function等存储在**堆(heap)**内存中。堆是一种存储大块数据的内存结构。由于对象可能会很大,且大小动态变化,所以堆适合用于管理对象的存储。引用类型的变量保存的不是数据本身,而是指向堆内存中实际数据的地址(引用)。
特点:
- 栈和堆的结合:引用类型的变量在栈中存储了对堆中数据的引用地址,实际的数据存储在堆中。
- 共享引用:如果两个变量指向同一个引用地址,它们会共享相同的堆内存中的数据。
- 可变性:对象本身是可变的,对同一个引用所做的修改会影响所有指向该对象的变量。
数据类型的判断
因为数据类型的不同,程序需要对不同数据类型作出判断,然后做出对应的代码逻辑,因此判断数据类型就尤为重要。
- typeof判断数据类型
能判断所有值类型和函数,因为
null
的特殊性,会返回object
,注意不是函数输出的都是字符串
console.log(typeof 'hello') // string
console.log(typeof 123) // number
console.log(typeof true) // boolean
console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof Symbol()) // symbol
console.log(typeof function(){}) // function
- instanceof 通过对比隐式原型和显式原型,如果相等即返回true, 这里有个代码片段有点意思:
let arr = []
console.log(arr instanceof Array) // true
console.log(arr.__proto__ === Array.prototype) // true
// 如果此时我改动了原型 那么就可能无法判断了,但是Array.isArray(arr)还是为true
arr.__proto__ = null
console.log(arr instanceof Array) // false
- Object.prototype.toString终极方式
Object.prototype.toString.call('hello') //[object String]
Object.prototype.toString.call(123) // [object Number]
Object.prototype.toString.call(true) //[object Boolean]
Object.prototype.toString.call(null) //[object Null]
Object.prototype.toString.call(undefined) //[object Undefined]
Object.prototype.toString.call(Symbol()) //[object Symbol]
Object.prototype.toString.call(() => {}) //[object Function]
Object.prototype.toString.call([]) //[object Array]
Object.prototype.toString.call({}) //[object Object]
Object.prototype.toString.call(/^abc/) //[object RegExp]
Object.prototype.toString.call(new Date()) //[object Date]
数据类型转换
JavaScript 中的类型转换是一个非常重要的概念,分为隐式转换(自动转换)和显式转换(手动转换)。在实际编程中,类型转换在不同数据类型之间发生,主要涉及 Number
、String
、Boolean
等基本数据类型。
1. 隐式类型转换
隐式类型转换是 JavaScript 自动将一种数据类型转换为另一种类型的过程,通常发生在表达式求值、比较和运算时。
1.1 字符串与其他类型的隐式转换
-
如果一个表达式中包含字符串,JavaScript 通常会将其他类型的值转换为字符串进行拼接。
let x = 1 + "2"; // 结果为 "12",1 被转换为字符串 let y = "Hello" + true; // 结果为 "Hellotrue",true 被转换为字符串
1.2 布尔值的隐式转换
在条件语句或逻辑运算中,JavaScript 会将非布尔类型转换为布尔值。假值会被转换为 false
,其他值会转换为 true
。
-
假值(Falsy values):
0
""
(空字符串)null
undefined
NaN
false
if (0) { console.log("Won't run"); } // 0 为假值,不会执行 if ("Hello") { console.log("Runs"); } // 非空字符串为真值,执行
1.3 数字与其他类型的隐式转换
-
在算术运算中,JavaScript 会尝试将操作数转换为数字。
- 字符串中的数值会被转换为数字。
true
会转换为1
,false
会转换为0
。
let x = "5" - 2; // 结果为 3,"5" 被转换为数字 let y = true + 1; // 结果为 2,true 被转换为 1 let z = "5" * "2"; // 结果为 10,"5" 和 "2" 都被转换为数字
-
如果字符串不能被转换为有效的数字,结果会是
NaN
。let x = "abc" - 2; // 结果为 NaN,"abc" 不是有效的数字
1.4 对象与原始值的隐式转换
对象在参与算术运算时,会尝试转换为原始值(通常是数字或字符串)。这种转换通常通过对象的 toString()
或 valueOf()
方法来完成。
let obj = {
valueOf: function() { return 10; }
};
let result = obj + 5; // 结果为 15,obj 被转换为数字 10
2. 显式类型转换
显式类型转换是开发者手动将一种类型转换为另一种类型,通常通过内置函数进行。
2.1 转换为数字
-
使用
Number()
函数可以将其他类型转换为数字。let x = Number("123"); // 结果为 123 let y = Number("abc"); // 结果为 NaN,无法转换为数字 let z = Number(true); // 结果为 1,true 转换为 1
-
其他方法:
parseInt()
:将字符串解析为整数。parseFloat()
:将字符串解析为浮点数。
let x = parseInt("123.45"); // 结果为 123,解析为整数 let y = parseFloat("123.45"); // 结果为 123.45,解析为浮点数
2.2 转换为字符串
-
使用
String()
函数可以将其他类型转换为字符串。let x = String(123); // 结果为 "123" let y = String(true); // 结果为 "true"
-
使用
toString()
方法也可以将数值、布尔值等转换为字符串。let x = (123).toString(); // 结果为 "123" let y = (true).toString(); // 结果为 "true"
2.3 转换为布尔值
-
使用
Boolean()
函数可以将其他类型转换为布尔值。let x = Boolean(1); // 结果为 true let y = Boolean(0); // 结果为 false let z = Boolean(""); // 结果为 false,空字符串是假值
3. 类型转换的细节
3.1 比较运算中的类型转换
-
==
(宽松相等):在比较不同类型时,JavaScript 会进行隐式类型转换。例如,"1" == 1
会返回true
,因为字符串"1"
被转换为数字1
。console.log(1 == "1"); // true console.log(true == 1); // true
-
===
(严格相等):不会进行类型转换,类型不同则直接返回false
。console.log(1 === "1"); // false console.log(true === 1); // false
3.2 加法与减法的类型转换
-
加法运算符
+
:如果有字符串参与,通常会将所有操作数转换为字符串,然后进行字符串拼接。console.log(1 + "2"); // "12"
-
减法运算符
-
:试图将所有操作数转换为数字,再进行运算。console.log("5" - "3"); // 2,字符串被转换为数字
4. 类型转换的规则和陷阱
-
null
和undefined
在转换时有特殊的规则:null
转换为数字是0
,undefined
转换为数字是NaN
。
console.log(Number(null)); // 0 console.log(Number(undefined)); // NaN
-
null
和undefined
在==
比较时会被认为相等,但在===
比较时不同。console.log(null == undefined); // true console.log(null === undefined); // false
-
数值
NaN
和任何值(包括它自己)比较总是返回false
。console.log(NaN == NaN); // false
逻辑运算
JavaScript 中的逻辑运算符主要有三种:逻辑与 (&&
)、逻辑或 (||
) 和 逻辑非 (!
)。它们用于对布尔值进行操作,也可以用于非布尔值的数据类型,这时会涉及到隐式类型转换或短路求值等特性。
1. 逻辑与 (&&
)
基本用法
- 逻辑与运算符
&&
会在两个操作数都为真时返回true
,否则返回false
。
布尔值的例子
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false
短路求值(Short-circuit evaluation)
&&
运算符 会先计算第一个操作数,如果第一个操作数为false
,则直接返回该值,不再计算第二个操作数。如果第一个操作数为true
,则返回第二个操作数的值。- 这种行为不仅限于布尔值,JavaScript 会根据真值(truthy) 和**假值(falsy)**的特性进行操作。
console.log(0 && "Hello"); // 0,因为 0 是假值,直接返回 0
console.log(1 && "Hello"); // "Hello",因为 1 是真值,返回第二个操作数
console.log("Hi" && null); // null,因为 "Hi" 是真值,返回第二个操作数 null
常见用法
- 常用来在某个条件为真时执行某个表达式或函数。
let user = { name: "John" };
user && console.log(user.name); // 输出 "John",因为 user 存在
2. 逻辑或 (||
)
基本用法
- 逻辑或运算符
||
会在两个操作数中至少有一个为真时返回true
,只有当两个操作数都为false
时才返回false
。
布尔值的例子
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
短路求值
||
运算符 会先计算第一个操作数,如果第一个操作数为真值,则直接返回它,不再计算第二个操作数。如果第一个操作数为假值,返回第二个操作数的值。
console.log(0 || "Hello"); // "Hello",因为 0 是假值,返回第二个操作数
console.log(1 || "Hello"); // 1,因为 1 是真值,直接返回第一个操作数
console.log(null || "Hi"); // "Hi",因为 null 是假值
常见用法
- 常用于为变量设置默认值。
let name = null;
let defaultName = name || "Guest"; // 如果 name 是 null 或 undefined,则使用 "Guest"
console.log(defaultName); // 输出 "Guest"
3. 逻辑非 (!
)
基本用法
- 逻辑非运算符
!
会将一个布尔值取反,true
变为false
,false
变为true
。
布尔值的例子
console.log(!true); // false
console.log(!false); // true
非布尔值的隐式转换
- 逻辑非运算符会首先将操作数转换为布尔值,然后对其取反。
console.log(!0); // true,0 是假值,取反后为 true
console.log(!1); // false,1 是真值,取反后为 false
console.log(!""); // true,空字符串是假值,取反后为 true
console.log(!"Hello"); // false,非空字符串是真值,取反后为 false
双重否定 (!!
)
- 使用两个
!
可以将任何值显式转换为布尔值。
console.log(!!0); // false,0 是假值
console.log(!!1); // true,1 是真值
console.log(!!"Hello"); // true,非空字符串是真值
4. 组合使用
&&
与 ||
的组合
&&
和||
可以结合使用来进行更复杂的逻辑判断,组合时会根据运算符的优先级来确定计算顺序。&&
的优先级高于||
,因此会优先执行&&
。
console.log(true || false && false); // true,因为 && 优先执行,结果为 true
console.log((true || false) && false); // false,先计算 ||,再计算 &&
例子:检查多个条件
let isLoggedIn = true;
let isAdmin = false;
let canEdit = isLoggedIn && isAdmin; // 用户必须登录并且是管理员才能编辑
console.log(canEdit); // false
5. 短路求值的应用场景
5.1 赋值操作中的默认值
可以利用 ||
提供默认值,即当变量为 null
或 undefined
时,赋值为默认值。
let username;
let displayName = username || "Guest"; // 如果 username 没有值,使用默认值 "Guest"
console.log(displayName); // "Guest"
5.2 条件执行函数
利用 &&
在满足条件时执行某个函数。
let isUserLoggedIn = true;
isUserLoggedIn && console.log("Welcome back!"); // 如果用户已登录,输出欢迎信息
6. 运算符优先级
- 逻辑非
!
的优先级最高,其次是逻辑与&&
,然后是逻辑或||
。当在同一个表达式中混合使用多个逻辑运算符时,注意使用括号来明确优先级。
let result = true || false && !false; // true,先计算 !false -> true,然后 false && true -> false,最后 true || false -> true
逻辑运算总结
&&
和||
运算符不仅适用于布尔值,还可以用于非布尔值,返回第一个能决定结果的操作数。!
运算符用于布尔值的取反,也可以用来将非布尔值转换为布尔值。- 通过逻辑运算符的短路特性,可以简化代码逻辑,如条件判断、赋默认值等。
手写深拷贝
/**
* 这里忽略函数,利用递归来写
*
* @param {*} obj
* @returns
*/
function deepClone(obj) {
if (typeof obj !== "object" || obj == null) {
// 如果是普通值
return obj;
}
let result;
if (Array.isArray(obj)) {
result = [];
} else {
result = {};
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
最后
本人目前寻找武汉地区前端工作机会,有需要我搬砖的大佬欢迎骚扰,鄙人不甚感激,可以评论区留言讨论哦😲