JavaScript 核心概念全解析:数据类型、拷贝、运算符与类型判断
在 JavaScript 学习中,数据类型的内存存储机制、深浅拷贝差异以及运算符的灵活使用,是理解代码执行逻辑和排查 Bug 的关键
一、基本数据类型 vs 引用数据类型(内存模型)
JavaScript 数据类型分为两大类,其核心差异在于内存存储位置不同,这直接影响变量赋值和修改的行为。
1. 核心区别
类型分类 | 包含类型 | 内存存储位置 | 赋值 / 修改特性 |
---|---|---|---|
基本数据类型 | string、number、boolean、undefined、null(特殊)、symbol、bigint | 栈空间(Stack) | 值独立存储,赋值后修改互不影响 |
引用数据类型 | object(含数组、对象)、function | 栈存地址,堆存值(Heap) | 赋值传递地址,修改会影响原对象 |
2. 代码示例与运行结果
javascript
运行
// 1. 基本数据类型:值存在栈中,独立不影响
let a = 10;
let b = a; // 复制栈中值 10
b = 20; // 修改 b 的值,仅改变 b 对应的栈数据
console.log("a =", a); // 输出:a = 10(a 不受影响)
console.log("b =", b); // 输出:b = 20// 2. 引用数据类型:栈存地址,堆存值,赋值传递地址
let obj1 = { name: "张三" };
let obj2 = obj1; // 复制栈中地址,obj1 和 obj2 指向堆中同一个对象
obj2.name = "李四"; // 修改 obj2 指向的堆数据
console.log("obj1.name =", obj1.name); // 输出:obj1.name = 李四(原对象被修改)
console.log("obj2.name =", obj2.name); // 输出:obj2.name = 李四
二、深拷贝与浅拷贝(针对引用数据类型)
由于引用数据类型传递地址,“拷贝” 操作需区分浅拷贝(仅复制地址)和深拷贝(复制完整值),避免意外修改原对象。
1. 核心定义
- 浅拷贝:仅复制栈中的地址,新变量与原变量指向同一个堆对象。修改新变量会影响原对象。
- 深拷贝:完全复制堆中的对象内容,新变量与原变量指向不同堆对象。修改新变量不影响原对象。
2. 代码示例与运行结果
javascript
运行
// 1. 浅拷贝示例(Object.assign 或扩展运算符 ... 仅浅拷贝一层)
let obj3 = { name: "张三", hobby: ["篮球", "游戏"] };
let obj4 = { ...obj3 }; // 浅拷贝:基本类型 name 复制值,引用类型 hobby 复制地址obj4.name = "王五"; // 修改基本类型,不影响原对象
obj4.hobby.push("跑步"); // 修改引用类型,影响原对象console.log("浅拷贝 - obj3:", obj3);
// 输出:obj3:{ name: '张三', hobby: [ '篮球', '游戏', '跑步' ] }(hobby 被修改)
console.log("浅拷贝 - obj4:", obj4);
// 输出:obj4:{ name: '王五', hobby: [ '篮球', '游戏', '跑步' ] }// 2. 深拷贝示例(JSON.parse + JSON.stringify,适合无函数的对象)
let obj5 = { name: "张三", hobby: ["篮球", "游戏"] };
let obj6 = JSON.parse(JSON.stringify(obj5)); // 深拷贝:完全复制堆中内容obj6.hobby.push("跑步"); // 修改新对象的引用类型,不影响原对象console.log("深拷贝 - obj5:", obj5);
// 输出:obj5:{ name: '张三', hobby: [ '篮球', '游戏' ] }(原对象无变化)
console.log("深拷贝 - obj6:", obj6);
// 输出:obj6:{ name: '张三', hobby: [ '篮球', '游戏', '跑步' ] }
三、类型判断:typeof 运算符
typeof
是判断数据类型的常用工具,但对引用数据类型的判断存在局限性,需注意特殊情况。
1. typeof 判断规则
数据类型 | typeof 返回值 | 特殊说明 |
---|---|---|
string | "string" | - |
number | "number" | - |
boolean | "boolean" | - |
undefined | "undefined" | - |
null | "object" | 历史 Bug,null 本质是基本类型 |
object(数组、普通对象) | "object" | 无法区分数组和普通对象 |
function | "function" | 唯一能被 typeof 正确识别的引用类型 |
2. 代码示例与运行结果
javascript
运行
console.log("typeof 'abc' =", typeof 'abc'); // 输出:typeof 'abc' = string
console.log("typeof 123 =", typeof 123); // 输出:typeof 123 = number
console.log("typeof true =", typeof true); // 输出:typeof true = boolean
console.log("typeof undefined =", typeof undefined); // 输出:typeof undefined = undefined
console.log("typeof null =", typeof null); // 输出:typeof null = object(特殊情况)
console.log("typeof [1,2] =", typeof [1,2]); // 输出:typeof [1,2] = object(无法区分数组)
console.log("typeof {a:1} =", typeof {a:1}); // 输出:typeof {a:1} = object
console.log("typeof function(){} =", typeof function(){}); // 输出:typeof function(){} = function
四、JavaScript 常用运算符全解析
运算符是代码逻辑的 “工具”,不同运算符有特定规则,尤其是隐式类型转换容易引发问题,需重点掌握。
1. 算术运算符(+、-、*、/、%)
核心规则:+
有字符串则拼接,其他运算符会先将非数字转为数字再计算。
javascript
运行
// 1. +:有字符串则拼接
console.log(10 + "20"); // 输出:1020(字符串拼接)
console.log(10 + 20 + "30"); // 输出:3030(先算 10+20=30,再拼接 "30")// 2. -、*、/、%:非数字转数字计算,无法转则为 NaN
console.log(10 - "5"); // 输出:5("5" 转数字 5)
console.log(10 * "a"); // 输出:NaN("a" 无法转数字)
console.log(7 % 3); // 输出:1(取余)
2. 一元运算符(+、-、!、++、--、delete、typeof、void)
+/-
:正 / 负号,可将字符串转数字;!
:取反,将值转为布尔类型后取反。++/--
:递增 / 递减,i++
先使用后加,++i
先加后使用。delete
:删除对象属性或数组元素(数组长度不变)。
javascript
运行
// 1. +:转数字;!:转布尔取反
console.log(+"123"); // 输出:123(字符串转数字)
console.log(!0); // 输出:true(0 转布尔为 false,取反为 true)
console.log(!""); // 输出:true(空字符串转布尔为 false,取反为 true)// 2. ++:i++ 与 ++i 区别
let i = 5;
console.log(i++); // 输出:5(先使用 i=5,再 i 变为 6)
console.log(++i); // 输出:7(先 i 变为 7,再使用)// 3. delete:删除对象属性和数组元素
let obj7 = { a: 1, b: 2 };
delete obj7.a;
console.log("delete 后 obj7:", obj7); // 输出:{ b: 2 }(a 属性被删除)let arr = [10, 20, 30];
delete arr[1];
console.log("delete 后 arr:", arr); // 输出:[ 10, <1 empty item>, 30 ](长度仍为 3)
3. 赋值运算符(=、+=、-=、*=、/=、%=)
用于变量赋值和简化运算,x += y
等价于 x = x + y
。
javascript
运行
let x = 10;
x += 5; // 等价于 x = x + 5
console.log("x += 5 后:", x); // 输出:15x *= 2; // 等价于 x = x * 2
console.log("x *= 2 后:", x); // 输出:30
4. 比较运算符(==、===、<、>、!=、!==)
==
:只判断值是否相等,类型不同会先做隐式转换。===
:严格判断,类型和值都必须相等(推荐使用,避免转换问题)。null == undefined
为 true(特殊关系),null === undefined
为 false。
javascript
运行
console.log(10 == "10"); // 输出:true(类型转换后值相等)
console.log(10 === "10"); // 输出:false(类型不同)
console.log(null == undefined); // 输出:true(特殊情况)
console.log(null === undefined); // 输出:false(类型不同)
console.log(5 > "3"); // 输出:true("3" 转数字 3,5>3)
5. 逻辑运算符(&&、||、!)
&&
:与运算,同真则真;若前值为假,直接返回前值(短路运算)。||
:或运算,有真则真;若前值为真,直接返回前值(短路运算)。!
:非运算,转布尔后取反(同一元运算符!
)。
javascript
运行
// 1. &&:短路运算,前假返前,前真返后
console.log(0 && "hello"); // 输出:0(0 为假,直接返回 0)
console.log("hi" && "hello"); // 输出:hello(前真,返回后值)// 2. ||:短路运算,前真返前,前假返后
console.log("hi" || "hello"); // 输出:hi(前真,直接返回 hi)
console.log(0 || "hello"); // 输出:hello(前假,返回后值)// 3. !:取反
console.log(!true); // 输出:false
console.log(!null); // 输出:true(null 转布尔为 false,取反为 true)
6. 三目运算符(条件?表达式 1 : 表达式 2)
简化 if-else
判断,条件为真执行表达式 1,否则执行表达式 2。
javascript
运行
let score = 85;
let result = score >= 60 ? "及格" : "不及格";
console.log("成绩结果:", result); // 输出:成绩结果:及格
总结
本文梳理了 JavaScript 中数据类型的内存差异、深浅拷贝的核心区别,以及各类运算符的使用规则与隐式转换特性。这些知识点是编写高效、无 Bug 代码的基础,建议结合实际场景多做练习,加深理解。