关于赋值, 浅拷贝, 深拷贝, 以前也思考良久, 很多时候都以为记住了, 但是, 我太难了. 今天我特地写下笔记, 希望可以完全掌握这个东西, 也希望可以帮助到任何想对学习这个东西的同学.
一, 栈, 堆, 指针地址
栈内存: 个人理解是, 基本数据类型和引用数据类型都会用到的一个空间, 这个空间以 key-value 形式存在, value 本身不可修改, 只能赋值替换;
堆内存: 堆, 就是堆积, 每一个被开辟的空间可以想象成一个空纸盒子, 纸盒子所在的纸盒子堆就是 "堆" . 基本数据类型没有堆的概念. 堆, 只针对引用数据类型. 存储方式应该是以对象 (object) 形式保存, 对象内容包含 key-value 形式数据, value 本身同样不可修改, 只能赋值替换;
指针地址: 针对引用数据类型在栈保存的值就是指针地址, 地址指向保存在堆里面的对象.
二, 赋值
赋值分两个, 一个是基本数据类型的赋值, 一个是引用数据类型的赋值, 基本数据类型赋的是 "值", 引用数据类型赋的是 "指针地址".
1. 基本数据类型赋值
- // 在栈内开辟一个空间, 空间名称叫 a, 存放值 1;
- var a = 1;
- // 在栈内开辟一个空间, 空间名字叫 b. 接着先把 a 的值 1 复制一份, 然后存放进 b
- var b = a;
如下图:
2. 引用数据类型赋值
- // 首先在栈开辟一个空间 a 存放指针地址, 设指针地址为 address1; 同时会在堆里面开辟一个空间放置对象数据
- var a = {
- no: 1,
- per: {
- name: "jack"
- },
- per2: {
- name: "rose"
- }
- }
- //a 赋值给 b, 此时 b 会在栈开辟一个空间 b, 用来放置 address1, 这个指针指向 a 所在堆的对象数据
- var b = a;
- // 修改赋值后的值 b, 其实就是修改 b 的指针 address1 所指向的对象数据
- b.no = 1314;
- // 修改 b 会影响原数据(所有层次的数据都会影响)
- // 这个原数据其实不是原数据, 因为 a 和 b 其实都是同一个数据
- // 就像从中国去美国, 可以从 a 地点 (比如北京) 或者 b 地点 (比如上海) 坐飞机去, 但是到达的都是同一个地方(也就是对象数据)
- b.per.name = "王五";
- console.log(a, b)
上面代码打印如图:
对 b 的修改会影响 a 原本的值. 对 a 的修改同样会同步 b 的值, 对 a 的修改本人没有写出, 你们可以自己试试, 结果是一样的.
针对上面的代码, 引用数据类型赋值, 如下图所示:
无论修改 a 对象还是 b 对象, 都是在修改 "obj" 这个对象
三, 浅拷贝
引用数据类型的浅拷贝, 代码如下:
- // 在栈开辟一个空间 a, 存放 a 的指针地址, 设指针地址为 address2a, 同时在堆开辟一个空间, 设这空间为 A, 存放 a 对象数据
- var a = {
- no: 1,
- per: {
- name: "jack",
- },
- per2: {
- name: "rose"
- }
- }
- // 在栈开辟一个空间 b, 存放 b 的指针地址, 设指针地址为 address2b, 同时在堆开辟一个空间, 设这空间为 B, 存放 b 对象数据
- var b = {};
- // 对 a 的数据进行循环, 判断如果有 key, 就把值赋到 B 对应的 key 位置
- // 这个循环, 遇到数据类型为基本数据类型, 赋的是值; 遇到引用数据类型, 赋的是指针地址
- for(var p in a) {
- if(a.hasOwnProperty(p)) {
- b[p] = a[p]
- }
- }
- // 对 b 的第一层修改
- b.no = 1314;
- b.per2 = [];
- // 对 b 的第二层修改
- b.per.name = "王五";
- // 浅拷贝, 修改 b 后, 第一层修改都不影响原数据, 第二层以及以上层次的修改都影响原数据
- // 当前没有写第三层及以上层次, 可自行测试.
- console.log(a, b)
运行结果如图:
a 的 no 和 per(这个值表示整个值 "{name:"jack"}" , 不是指属性值 "jack".a.no 和 a.per 都属于第一层, a.per.name 是第二层)还是原本的值. b 的修改对 a 没有任何影响, 而 b 对 per 的属性值的修改却导致 a 的 per 的属性值也变成的 "王五" , 也就是第二层或以上层次的修改会影响原数据. 可以理解为, 第二层或以上的浅拷贝, 其实是上一节讲的 "引用数据类型的赋值".
总结如下图:
上图所示中, B 空间内的数据含有 no 的值, 也就是 1;a 对象数据的 per 和 per2 都是属于引用对象数据, 所以 b 保存的是它门的指针地址, 分别指向了 per 和 per2 所在的地址位置. 所以修改 b 对象数据的 no 的值不会影响 a 的 no, 修改 per 或 per2 的值就会影响 a 的 per 和 per2.
四, 深拷贝
深拷贝, 说白了, 就是对浅拷贝的递归, 也就是浅拷贝章节所述的, 浅拷贝第一层已经被完全拷贝到新的地方, 然后第二层以及以上层次, 它们的属性值又将都会被拷贝到新的地方, 最后就井水不犯河水了.
代码如下:
- // 在栈开辟一个空间 a, 存放 a 的指针地址, 设指针地址为 address3a, 同时在堆开辟一个空间, 设这空间为 space1a, 存放 a 对象数据
- var a = {
- no: 2,
- per: {
- name: "jack"
- },
- per2: {
- name: "rose"
- }
- }
- // 用递归的方式对 a 进行拷贝属性和值, 然后赋值给 temp, 然后 return 出去. 此时不拷贝指针地址.
- function getDeep(obj) {
- var temp = Array.isArray(obj) ? [] : {};
- for(var p in obj) {
- if(typeof obj[p] == "object") {
- temp[p] = getDeep(obj[p])
- } else {
- temp[p] = obj[p]
- }
- }
- return temp;
- }
- // 在栈开辟一个空间 b, 存放 b 的指针地址, 设指针地址为 address3b. 同时 b 在堆开辟一个空间, 设这空间为 D, 存放 temp 的对象数据
- var b = getDeep(a);
- // 深拷贝后, 修改 b 的值, 不论修改属性值, 还是整个值替换都不影响原数据 a
- b.no = 1314;
- b.per = []
- b.per2 = {
- name:"王五"
- }
- console.log(a, b)
浅拷贝只拷贝了第一层, 深拷贝是拷贝到最后一层. 代码运行结果如图:
可以理解为, a 原本的东西被完全复制了一份, 放到了 b 里面, 然会对 b 的操作, 就只关 b 的事情了. a 原本是什么值, 现在依然是什么值, b 的修改对 a 完全没有影响.
最后, 可用下图表示深拷贝:
四, 总结
1. 赋值:
基本数据类型就是类似 a 同学有一台电脑, b 同学也想要, 就也给 b 同学买了一台一模一样的电脑 b, 电脑 a 和电脑 b 各自怎么被操作都是 a 同学和 b 同学各自的事, 电脑显示互不影响(数据结果);
引用数据类型就是只有一台电脑, 放在了电脑室, a 同学和 b 同学各自从宿舍到电脑室操作电脑, 都能影响电脑显示; 在 a 同学和 b 同学的眼里, 最后结果这台电脑显示什么, 取决于最后一个操作电脑的同学(数据结果);
2. 浅拷贝:
a 同学有一台笔记本电脑并且配置了全套装备, 触感舒爽的鼠标, 按键响亮的机械键盘等. b 同学没钱买电脑, 但是又很想体验, 所以先买了和 a 同学一样的鼠标键盘自个先看着爽. 然后向 a 同学借电脑过来玩. a 同学和 b 同学各自的鼠标键盘出了啥问题, 两个人之间互不影响对方的使用. 而对电脑的操作就是谁最后操作了电脑, 电脑就是显示最后那个人的操作界面(数据修改).
3. 深拷贝:
a 同学有笔记本 + 全套装备, b 同学羡慕不已, 自己让 a 同学照着买了一整套一模一样的给自己, 但是他们各自的使用电脑情况, 取决于他们各自的操作, 电脑之间互不影响(数据结果).
以上纯属个人理解, 有误勿喷请指出, 谢谢!
来源: https://www.cnblogs.com/Chansea/p/copy.html