数据类型

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型

原始数据类型:保存在栈(stack)中,
引用类型类型:存储的是该对象在栈中引用,真实的数据存放在堆内存里

js引擎需要用栈来维护执行上下午的状态,如果栈空间太大,效率就低了。引用类型存放在堆空间了,栈里只是存放引用地址


值和引用

原始值总是通过值复制(value-copy)的方式来赋值/传递
复合值–对象(包括数组和封装对象)和函数,总是通过引用复制的方式来赋值/传递

1
2
3
4
5
// 简单值 通过值复制的方式赋值
let a = 2
let b = a // b是a的值的副本
a ++
console.log(b) /* 2 浅拷贝:基础数据类型,不会改变原始值*/
1
2
3
4
5
6
7
8
9
10
//  复合值 通过引用复制的方式赋值
let a = {
name: 'keke',
book: { title: "You Don't Know JavaScript",price: '45' }
}
let b = a
a.name = 'xixi'
a.book.price = '55'
console.log(b) /* 引用类型执行的复制操作叫浅拷贝*/
console.log(a) /* xixi, 55 */

我们无法自行决定使用值复制还是引用复制,一切由值的类型决定
对原始值进行复制操作会对值进行一份拷贝;而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
所以,如果我们要通过值复制的方式来传递复合值,就需要为其创建一个副本,这样传递的就不再是原始值


赋值与浅拷贝的区别

  • 深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
  • 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,两个对象是联动的,具有映射关系,你变我也变。
  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
和原始值是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

浅拷贝

引用赋值,相当于取了一个别名。新旧对象还是共享同一块内存,对其中一个修改,会影响另一个。浅拷贝只复制指向某个对象的指针,而不复制对象本身,新对象只是拿到了原对象的一个引用。

如果属性是基本类型,那么拷贝的就是基本类型的值,如果属性是引用类型,那么拷贝的就是内存地址,

实现方法

(1) Object.assign的方式

1
2
3
4
5
6
7
8
9
// -------------assign() 对象的拼接
let a = [
{name: 'keke'},
{book: { title: "You Don't Know JavaScript",price: '45' } }
]
let b = Object.assign({}, a)/* Object.assign既不是深拷贝也不是浅拷贝,看值的数据类型 会把a中的一级属性都拷贝到{}中, */
a.name = '珂珂'
a[1].book.price = '55'
console.log(b) /* keke, 55 */

(2) 通过对象扩展运算符

1
2
3
4
5
//取出参数对象的所有可遍历属性,拷贝到当前对象之中
let b = {...a}
b[0].name = '珂珂';
b[1].book.price= '45'
console.log(b); //{'珂珂' ,'45'}

(3) 通过数组的slice方法

1
2
3
4
let b = a.slice()   
b[0].name = '珂珂';
b[1].book.price= '45'
console.log(b); //{'珂珂' ,'45'}

(4) 通过数组的concat方法

1
2
3
4
let b = a.concat()   
b[0].name = '珂珂'
b[1].book.price = '45'
console.log(b) //{name: '珂珂'},{price: 45}

slice()和concat() 不带参数会返回当前数组的浅拷贝,返回一个新的数组对象,由于传递给函数的是指向该副本的引用,所以不改变原数组


深拷贝

赋值时值完全复制,深拷贝会另外创建一个一模一样的对象,完完全全扒下来的,新对象跟原对象不共享内存修改新对象不会改到原对象对其中一个作出改变,不会影响另一个

实现方法

(1) 通过JSON.stringify来序列化对象

1
2
3
4
5
6
7
8
9
let a = {
name: 'keke',
book: { title: 'hi', price: '11', }
}
let b = JSON.parse(JSON.stringify(a)) //典型的深拷贝 对象转成字符串,再转换为对象
console.log(b)
a.name = 'xixi'
a.book.price = '22'
console.log(b) //前后输出相同

  • 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
  • 但JSON.stringify()在遇到不安全的JSON值(undefined、function、symbol、和包含循环引用的对象都不符合JSON结构标准,无法处理,会自动将不安全的JSON值忽略,在数组中返回null。
    既然JSON.parse(JSON.stringify())不能处理函数,则需要手动遍历

(2) 手写实现递归的方式。

先写一个浅拷贝的实现

1
2
3
4
5
6
7
8
9
10
----------------浅拷贝:只进行一次拷贝------------
function cloneShallow(source) {
var target = {}
for (var key in source) {
// js不拷贝原型链上的值 判断是否是继承来的属性
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
return target

Object.prototype.hasOwnProperty.call() 用来检测一个对象是否含有特定的自身属性;并且忽略掉那些从原型链上继承到的属性。

接着是深拷贝,深拷贝要拿到原始值类型,所以要一步步深入找原始值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ------------深拷贝:无限层级拷贝-------------
// 从引用值一直往里面找到原始值 递归
function cloneDeep(source) {
// 判断是否是引用类型,如果判断为基础数据类型,则没有必要深拷贝
if (typeof source !== 'object') {
return source
}
// 兼容数组 判断当前source是数组还是对象
var target = Array.isArray(source) ? [] : {}
// var target = {}
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
//在这里找到引用值还不够,再往里找 判断是不是引用类型
// typeof不能判断复杂数据类型,但对象可以用typeof判断
if (typeof source[key] === 'object') {
// 递归 再筛一遍
target[key] = cloneDeep(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
let a = {
name: 'keke',
book: { title: 'hi', price: '11', }
}
let b = cloneDeep(a)
a.name = 'xixi'
a.book.price = '22'
console.log(b) // 22 book中引用数据类型被修改

虽然递归比较好理解,但有一个更好的实现深拷贝的办法 就是借用栈规则,避开递归调用的缺陷。链表查结点 循环置换

  • 递归过程:用循环遍历一棵树,需要借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点
    首先我们往栈里放入种子数据,key用来存储放哪一个父元素的那一个子元素拷贝对象
    然后遍历当前节点下的子元素,如果是引用对象就放到栈里,否则直接拷贝
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    function cloneDeep2(x) {
    const root = {}
    // 栈结构
    const loopList = [{
    parent: root, //相对父节点
    key: undefined, //key用来存储放哪一个父元素的那一个子元素的拷贝对象
    data: x
    }]

    while (loopList.length) {
    // 深度优先
    const node = loopList.pop() //删除并返回栈底元素
    const parent = node.parent //第一次遍历为空对象,之后拿到第二层的key
    const key = node.key //第一层数据
    const data = node.data //传来的对象结点

    let res = parent // {}
    // 初始化赋值目标,key为undefined的话拷贝到父元素,否则拷贝到子元素
    if (typeof key !== 'undefined') {
    res = parent[key] = {}
    }
    // 循环对象
    for (let k in data) {
    if (data.hasOwnProperty(k)) {
    if (typeof data[k] === 'object') {
    // 下一次循环 把再一次找的引用值放到栈中
    loopList.push({
    parent: res,
    key: k,
    data: data[k] //下一次循环 data存放循环后找到的对象中的引用数据类型
    })
    } else {
    res[k] = data[k]
    }
    }
    }
    }
    return root
    }

    let a = {
    name: 'keke',
    book: {
    title: 'hi',
    price: '11',
    me: {my: 'yy'}
    }
    }
    let b = cloneDeep2(a)
    a.name = 'xixi'
    a.book.price = '22'
    a.book.me.my = 'zz'
    console.log(b) /*b拿到原始值*/

参考:
深拷贝的终极探索
浅拷贝与深拷贝

动手写文章总结后,思维会比之前清晰很多,就是自己太懒了 :pig: ,希望之后能勤快点,翻看自己的总结知识复现的更快呀 :running:

相关文章
评论
分享
  • es6 note

    1.ES6怎么来的 ECMAScript 和 JavaScriptECMA 是标准,JS 是实现ECMAScript 简称 ECMA 或 ES 历史版本1996, ES1.0 Netscape 将 JS 提交给 ECMA 组织,E...

    es6 note
  • 我的三月碎碎念

    前言现在三月中旬了,是待在家为祖国做贡献的第二个月,不在学校的日子,每天在家学习,也过的蛮充实。少了很多社交,昨天还和谷歌娘唠嗑些有的没的。每天的小结越写越短,那就这一次给自己写个阶段性的总结吧。 基础 这段时间赶上春招,社...

    我的三月碎碎念
  • 关于二叉树、递归、DFS、BFS

    前言前言:这是作为一个正在学习的前端开发者整理一下最近写的题,这文章是我对二叉树算法的浅显的理解,希望可以让你在看完文章之后对常见的二叉树操作有一定的了解,文中列举了我觉得比较经典的一些题目。有不对的地方欢迎指出。😮😮😮 本文...

    关于二叉树、递归、DFS、BFS
  • 李佳琦可视化list

    前言做这个李佳琦推荐口红可视化倒也不是因为自己喜欢买口红或者喜欢看李佳琦,是一次在车上逛微博看到热搜上一个帖子,很详细的整理了这些口红,用手机自带的备忘录写的,但略显简陋,颜色展示仅仅是在色号后面加了个很小的色卡,当时就想,如果可以做...

    李佳琦可视化list
  • 给阿姨倒一杯卡布奇诺

    前言图床老挂, 在掘金看吧,阅读体验更好 👉看这里 平安夜那天晚上下课打着伞想到的idea做出来了,写个总结 页面结构12345678"pages": [ "pages/buy/buy", ...

    给阿姨倒一杯卡布奇诺
  • 傲慢与偏见 Mr.Darcy

    淋雨的自杀式告白– Miss Elizabeth , I have struggled in vain and can bear it no longer. These past months have been a tor...

    傲慢与偏见 Mr.Darcy
  • 随笔杂谈

    关于《胜利之光》如果你正在经历挫折与失败,心灰意冷时,一定要看看这部电影 以后你可能再也找不到能让你这么热血拼搏的事情了趁现在你还有热血,趁现在你还有激情去做那些只要想到就会让你两眼放光的事情吧因为过了这段时间,你可能再也难以找到那种...

    随笔杂谈
  • Hello World

    Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using ...

    Hello World
Please check the comment setting in config.yml of hexo-theme-Annie!