掘金博客:JavaScript的奇妙世界——从入门到“精通”(才怪!)
嘿,各位未来的前端大佬们,以及正在被JavaScript折磨的“小菜鸡”们!👋 欢迎来到我的掘金小课堂。今天,我们要一起踏上JavaScript的奇妙探险之旅。别担心,我不会讲那些让你头晕眼花的理论,咱们就用最通俗、最风趣的方式,把JavaScript的那些“爱恨情仇”给捋清楚!
你可能会问,JavaScript是啥?它能干啥?简单来说,它就是让网页“活”起来的魔法棒!没有它,你的网页可能就只是一堆静态的文字和图片,就像一张没有灵魂的报纸。有了它,你的网页就能动起来,能和用户互动,能变出各种花样!是不是听起来很酷?😎
那么,准备好了吗?系好安全带,咱们这就出发!🚀
JavaScript数据类型:你真的懂吗?
JavaScript的世界里,数据就像是各种各样的“小精灵”,它们有不同的形态和能力。了解它们,是咱们玩转JavaScript的第一步!
原始数据类型:简单纯粹的它们
想象一下,原始数据类型就像是那些“独来独往”的个体户,它们的值直接存储在变量里,简单纯粹,不掺杂任何“水分”。当你想复制它们的时候,就像是复印一份文件,原件和复印件之间没有任何关联,你改复印件,原件纹丝不动。
JavaScript中有7种原始数据类型,咱们来一一认识一下:
-
Number(数字):💰 整数、浮点数,统统都是它!比如
10、3.14。JavaScript里的数字可不分什么整型浮点型,都是Number。let age = 25; // 整数 let price = 99.99; // 浮点数 console.log(age); // 输出:25 console.log(price); // 输出:99.99 -
String(字符串):📝 文本的集合,用单引号、双引号或反引号(模板字符串)包起来的都是它。比如
\'Hello World\'、"JavaScript"。let greeting = \'Hello, JavaScript!\'; let name = "Manus"; let message = `你好,${name}!`; // 模板字符串 console.log(greeting); // 输出:Hello, JavaScript! console.log(name); // 输出:Manus console.log(message); // 输出:你好,Manus! -
Boolean(布尔值):✅❌ 只有两个值:
true(真)和false(假)。就像开关一样,只有开和关两种状态,常用于逻辑判断。let isLearning = true; let isSleeping = false; console.log(isLearning); // 输出:true console.log(isSleeping); // 输出:false -
Undefined(未定义):❓ 当你声明了一个变量,但没有给它赋值时,它的值就是
undefined。就像你预定了一个快递箱,但里面还没放东西。let myVariable; // 声明但未赋值 console.log(myVariable); // 输出:undefined -
Null(空):🚫 表示一个空值或者不存在的对象。它是一个“有意为之”的空,就像你清空了一个快递箱,里面什么都没有了。
let emptyValue = null; console.log(emptyValue); // 输出:null -
Symbol(符号):🔑 ES6新增的,表示独一无二的值。主要用于对象的属性名,防止属性名冲突。它就像一个独一无二的“身份证号”。
const id1 = Symbol(\'id\'); const id2 = Symbol(\'id\'); console.log(id1 === id2); // 输出:false (即使描述相同,它们也是不同的Symbol) -
BigInt(大整数):🔢 ES2020新增的,可以表示任意大的整数。当Number类型无法表示的超大整数时,BigInt就派上用场了。在数字后面加个
n就行。const bigNumber = 9007199254740991n + 1n; // 超过Number安全范围的整数 console.log(bigNumber); // 输出:9007199254740992n
这些原始数据类型,就像是JavaScript世界里的“基本粒子”,它们构成了最基础的数据单元。理解了它们,你就迈出了JavaScript学习的第一大步!
引用数据类型:复杂多变的它们
如果说原始数据类型是“个体户”,那么引用数据类型就是“公司”或者“组织”。它们的值不是直接存储在变量里,而是存储在内存的另一个地方(堆内存),变量里存储的只是一个“地址”(指向堆内存的指针)。当你复制它们的时候,复制的只是这个“地址”,而不是实际的数据。所以,如果你通过复制的“地址”去修改数据,原数据也会跟着变,因为它们指向的是同一个地方!
JavaScript中常见的引用数据类型有:
-
Object(对象):📦 JavaScript里的一切皆对象(除了原始数据类型)。它是一组无序的键值对的集合,就像一个“百宝箱”,里面可以装各种各样的数据和功能。
let person = { name: \'张三\', age: 30, isStudent: false, hobbies: [\'reading\', \'swimming\'], greet: function() { console.log(`大家好,我是${this.name}!`); } }; console.log(person.name); // 输出:张三 person.greet(); // 输出:大家好,我是张三! -
Array(数组):📊 数组是一种特殊的“对象”,它是有序的元素集合。你可以把它想象成一串糖葫芦,每个元素都有自己的位置(索引)。
let fruits = [\'apple\', \'banana\', \'orange\']; console.log(fruits[0]); // 输出:apple fruits.push(\'grape\'); // 添加新元素 console.log(fruits); // 输出:[\'apple\', \'banana\', \'orange\', \'grape\'] -
Function(函数):⚙️ 函数也是一种特殊的“对象”,它是一段可执行的代码块。你可以把它想象成一个“工具”,你给它一些原材料(参数),它就能帮你完成一些工作(执行代码),然后给你一个结果(返回值)。
function add(a, b) { return a + b; } let result = add(5, 3); console.log(result); // 输出:8 // 函数也可以作为变量的值 const sayHello = function(name) { console.log(`Hello, ${name}!`); }; sayHello(\'World\'); // 输出:Hello, World!
理解了原始数据类型和引用数据类型的区别,你就掌握了JavaScript数据存储的“玄机”!这对于后面理解深拷贝和浅拷贝至关重要哦!
内存的世界:栈与堆的秘密
咱们都知道,计算机要运行程序,就得有内存。JavaScript在执行的时候,也会把数据往内存里放。这内存啊,就像一个大仓库,但它可不是随便乱放的,而是分成了两个区域:栈(Stack)和堆(Heap)。理解这两个区域,能让你对JavaScript的数据存储有更深入的理解。
栈内存:高效的“小仓库”
栈内存就像一个“LIFO”(Last In, First Out,后进先出)的箱子。你放进去的东西,最后放进去的,最先拿出来。它主要用来存储原始数据类型(Number, String, Boolean, Undefined, Null, Symbol, BigInt)和引用数据类型的“地址”。
栈内存的特点是:
- 小而快:存取速度非常快,因为它的结构简单,数据大小固定。
- 自动管理:当函数执行完毕,栈内存中属于这个函数的数据就会自动销毁,不需要我们手动管理。
想象一下,你把一些小件物品(原始数据类型的值)直接放在一个格子里,或者把一张写着“大件物品在X号仓库”的纸条(引用数据类型的地址)放在格子里,这些格子就是栈内存。
堆内存:自由的“大空间”
堆内存就像一个“自由市场”,空间比较大,存储的东西也比较复杂,主要是引用数据类型(Object, Array, Function)的实际内容。这些数据的大小不固定,而且可能在程序运行过程中动态变化。
堆内存的特点是:
- 大而慢:相对于栈内存,存取速度会慢一些,因为它的管理机制更复杂。
- 手动管理(或垃圾回收):JavaScript有自己的垃圾回收机制,会自动清理不再使用的堆内存空间,但有时候也需要我们理解其工作原理,避免内存泄漏。
那些大件物品(引用数据类型的实际内容),就放在这个大市场里。而栈内存里存储的,只是这些大件物品在市场里的“门牌号”或者“地址”。
变量存储:数据何去何从?
现在,咱们结合一张图来理解变量在栈和堆中的存储方式。这张图完美地解释了原始数据类型和引用数据类型在内存中的区别:
从图中我们可以看到:
- 变量
a,b,c,d都是原始数据类型,它们的值直接存储在栈内存中。比如a存储了\"asd\",b存储了123。 - 变量
e是引用数据类型,它的实际内容(一个object)存储在堆内存中。而在栈内存中,e存储的只是这个object在堆内存中的“地址”。 - 变量
f也是引用数据类型,它存储的也是e所指向的那个object的“地址”。这意味着e和f指向的是堆内存中的同一个object!
理解了这一点,你就能明白为什么修改引用数据类型的一个变量,另一个变量也会跟着变了,因为它们“同居”在一个内存地址里!这为我们后面理解深拷贝和浅拷贝打下了坚实的基础。
深拷贝与浅拷贝:别再傻傻分不清!
好了,重头戏来了!JavaScript面试中,深拷贝和浅拷贝绝对是高频考点。很多人对它一知半解,今天咱们就用最直观的方式,彻底搞懂它!
浅拷贝:表面兄弟情
浅拷贝,顾名思义,就是只拷贝了“表面”的东西。对于原始数据类型,浅拷贝就是直接复制值,没啥好说的。但对于引用数据类型,浅拷贝复制的只是那个“地址”!就像你和你的兄弟,你们都有一把钥匙,但这把钥匙开的是同一个房间。你往房间里放东西,你兄弟也能看到,也能拿走。
让我们看看这张图,它完美诠释了浅拷贝的“表面兄弟情”:
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = obj1; // 浅拷贝,obj2 复制了 obj1 的地址
obj2.a = 10; // 修改 obj2 的属性a
console.log(obj1.a); // 输出:10,obj1 也被修改了!
obj2.b.c = 20; // 修改 obj2 内部对象的属性c
console.log(obj1.b.c); // 输出:20,obj1 内部对象也被修改了!
console.log(obj1 === obj2); // 输出:true,它们指向同一个对象
常见的浅拷贝方法:
- 直接赋值(
=):这是最直接的浅拷贝,也是最容易“踩坑”的。 Object.assign():可以用于对象的合并,但如果源对象有嵌套对象,依然是浅拷贝。- 展开运算符(
...):ES6的新特性,用于数组和对象的复制,同样是浅拷贝。 Array.prototype.slice()、Array.prototype.concat():用于数组的浅拷贝。
深拷贝:真爱无敌
深拷贝,就是彻底的“复制”,它不仅复制了值,如果遇到引用数据类型,还会递归地复制它们指向的实际内容,直到所有层级的数据都被复制过来。就像你和你的兄弟,你们都有自己的房间,房间里的东西都是独立的,你改你的房间,不会影响到你兄弟的房间。
再来看看这张图,它描绘了深拷贝的“真爱无敌”:
let obj1 = { a: 1, b: { c: 2 } };
// 简单实现深拷贝(只适用于纯JSON对象,不包含函数、undefined等)
let obj3 = JSON.parse(JSON.stringify(obj1));
obj3.a = 100; // 修改 obj3 的属性a
console.log(obj1.a); // 输出:1,obj1 不受影响
obj3.b.c = 200; // 修改 obj3 内部对象的属性c
console.log(obj1.b.c); // 输出:2,obj1 内部对象也不受影响
console.log(obj1 === obj3); // 输出:false,它们是不同的对象
常见的深拷贝方法:
JSON.parse(JSON.stringify(obj)):这是最简单粗暴的深拷贝方法,但它有局限性,比如不能拷贝函数、undefined、Symbol、BigInt,以及循环引用等。- 递归实现:自己手写一个递归函数,遍历对象的每一个属性,如果是原始类型就直接赋值,如果是引用类型就递归调用自身。
- 第三方库:例如
lodash库的_.cloneDeep()方法,功能强大且考虑了各种复杂情况。
理解深拷贝和浅拷贝,是你在JavaScript世界里避免“坑”的关键一步!当你需要完全独立的数据副本时,一定要选择深拷贝哦!
总结与展望:JavaScript的无限可能
恭喜你,已经成功闯过了JavaScript的几道“难关”!从数据类型的“小精灵”到内存的“大仓库”,再到深拷贝和浅拷贝的“真假美猴王”,相信你对JavaScript的理解又深入了一层。
JavaScript的世界远不止于此,它还在不断发展,不断给我们带来惊喜。从前端到后端(Node.js),从移动端(React Native)到桌面端(Electron),甚至人工智能和物联网,JavaScript的身影无处不在。它就像一个充满活力的年轻人,拥有无限的可能!
希望这篇博客能让你对JavaScript有一个全新的认识,也希望你能保持这份好奇心和学习的热情,继续在JavaScript的海洋里畅游。记住,编程的乐趣就在于不断探索和解决问题!
如果你觉得这篇博客对你有帮助,别忘了点赞、收藏、转发哦!你的支持是我继续创作的最大动力!💪
咱们下期再见!👋