深拷贝和浅拷贝

一.原始类型和引用类型

1.常见的基本数据类型

  • Sting
  • Number
  • Boolean
  • null
  • undefined
  • Symbol

基本数据类型保存在栈内存中,因为基本数据类型占用空间小,大小固定,通过按值来访问,属于被频繁使用的数据。

2.引用数据类型

  • Object
  • Date
  • function
  • RegExp
  • Array ...

引用数据类型存储在堆内存中,因为引用数据类型占用空间大,占用内存不固定。如果存储在栈中

将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中实体的起始地址。

3.基本数据类型和引用数据类型区别

基本数据类型存在栈中,引用数据类型在栈里存地址,而在堆里存内容,如果定义数组(对象) m与数组n相等,表示地址相同,所以m与n的指针指向同一个内容,改变内容,则m与n都会改变。

图示:

4.js内存分为栈内存(stack)和堆内存(heap)

栈内存:是一种特殊的线性表,它具有先进后出的特性,存放基本类型。

堆内存:存放引用数据类型(在栈内存中存一个基本类型值保存对象在堆内存中的地址,用于这个对象)

图示:

5.原始类型的不可变性和引用类型的可变性

(1)例如 let str = 'ConardLi' 对这个字符串进行操作

可以看出这是一个原始类型的字符串,我们想像它在栈中,开辟了一块空间,来存放'ConardLi'这个字符串。

 当我们调用操作字符串的方法时,没有任何方法直接修改字符串

var str = 'ConardLi';
str.slice(1);
str.substr(1);
str.trim(1);
str.toLowerCase(1);
str[0] = 1;
console.log(str);  // ConardLi

在上的代码中我们对str调用了几个方法,无一例外,这些方法都在原字符串的基础上产生了一个新字符串,而非直接去改变str,这就印证了字符串的不可变性。

那么,当我们继续调用:

str += '6'
console.log(str);  // ConardLi6

你会发现,str的值改变了,这不就打脸了字符串的不可变性么?其实不然,我们从内存上来理解:由于栈中的内存空间的大小是固定的,那么注定了存储在栈中的变量是不可变的。 

在上面的代码中,我们执行了str += '6'的操作,实际上是在栈中又开辟了一块内存空间用于存储ConardLi6',然后将变量str指向这块空间,所以这并不违背不可变性的特点。

图示:

 (2)引用类型就不再具有不可变性了,我们可以轻易改变它们:

 以数组为例子,它的很多方法都可以改变它自身。

  • pop() 删除数组最后一个元素,如果数组为空,则不改变数组,返回undefined,改变原数组,返回被删除的元素
  • push()向数组末尾添加一个或多个元素,改变原数组,返回新数组的长度
  • shift()把数组的第一个元素删除,若空数组,不进行任何操作,返回undefined,改变原数组,返回第一个元素的值
  • unshift()向数组的开头添加一个或多个元素,改变原数组,返回新数组的长度
  • reverse()颠倒数组中元素的顺序,改变原数组,返回该数组
  • sort()对数组元素进行排序,改变原数组,返回该数组
  • splice()从数组中添加/删除项目,改变原数组,返回被删除的元素

6.复制的表现形式

(1)原始类型的复制

    var name = 'lisa'
    var name2 = name
    name = 'kakaxi'
    console.log(name);
    console.log(name2);

解析:可以看出name这个变量保存’lisa‘这个字符串,把‘lisa’赋值给name2,那么此时name2的值为'lisa‘,现在也修改了name的值为'kakaxi',开辟了一块新的内存

图示:

表现的形式两者是相互不干扰的。

(2)引用数据类型

代码:

<script>
    let obj = {
      name: 'wangwu',
      age: 20
    }
    // 将obj的值赋值给obj1
    let obj1 = obj
    // 修改obj的值
    obj.name = 'kakaxi'
    console.log(obj);
    console.log(obj1);
  </script>

图示:

 可以理解成:

 7.值传递和引用传递

(1)引用传递:

let name = 'ConardLi';
function changeValue(name){
  name = 'code秘密花园';
}
changeValue(name);
console.log(name);

指向上面的代码,如果最终打印出来的name是'ConardLi',没有改变,说明函数参数传递的是变量的值,即值传递。如果打印的是 'code秘密花园'  ,函数内部的操作可以改变传入的变量,说明函数传递的是引用,即引用传递。   

打印的结果是’ConardLi‘,即函数参数仅仅是被传入变量复制给了的一个局部变量,改变这个局部变量不会对外部变量产生影响。

在看下列的代码:

let obj = {name:'ConardLi'};
function changeValue(obj){
  obj.name = 'code秘密花园';
}
changeValue(obj);
console.log(obj.name); // code秘密花园

解析:创建了一个对象,把这个对象当作参数进行传递,最终打印出来的结果是在函数体里面修改的值,可见,函数参数传递的并不是变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本是值本身,当变量是引用类型时,这个副本指向堆内存的地址。所以,再次记住:

 

二.深拷贝和浅拷贝

(1)浅拷贝

图示:

 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是原始类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另外一个对象。

(2)深拷贝

 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟了一个新的区域存放对象,且修改新对象不会影响到原对象。

(3)深拷贝对象的方法

1.在不使用第三方库的情况下,我们想要深拷贝一个对象,用的最多的是

JSON.parse(JSON.stringify());

2..实现一个对象的深拷贝

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let obj = {
      name: '张三',
      age: 20,
      nvpemngy: {
        name: '小花'
      }
    }
    // 1.封装一个函数
    function cope(obj) {
      let newobj = {}
      for (let i in obj) {
        if (obj[i] instanceof Object) {
          // 如果是一个对象的话,利用递归完成深度的拷贝
          newobj[i] = cope(obj[i])
        } else {
          // 如果不是一个对象,完成一次浅拷贝
          newobj[i] = obj[i]
        }
      }
      // 最后把处理的结果return给函数
      return newobj
    }
    // 传入一个对象完成一次浅拷贝
    let obj1 = cope(obj)
    obj.nvpemngy.name = '小赵'
    console.log(obj);
    console.log(obj1);
  </script>
</body>

</html>

3.函数库lodash的_.cloneDeep方法

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

(4)浅拷贝的实现方式

 (1).Object.assign()

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

(2)函数库lodash的_.clone方法

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true

(3).展开运算符...

let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }

 (4)Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2 = arr.concat();    
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]

5.Array.prototype.slice()

let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]

三.利用深拷贝实现一个简单的功能

代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./vue.js"></script>
</head>

<body>
  <div id="app">
    <form action="" label="for">
      <input type="text" v-model="studnet.name">
      <input type="text" v-model="studnet.age">
    </form>
    <button @click="fn" id="for">
      点击提交
    </button>
    <ul>
      <li v-for="item in list">{{item.name}}----{{item.age}}</li>

    </ul>

  </div>


</body>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      studnet: {
        name: '张三',
        age: 23,
      },
      list: []
    },
    methods: {
      fn() {
        this.list.push(this.studnet)
      }
    },
  })
</script>

</html>

图示:

发现一修改全部修改

 分析原因:push进去的是一个对象,对象的地址是一样的。

解决方法:封装一个简单的函数

 function cope(obj) {
    let newObj = {}
    for (let i in obj) {
      newObj[i] = obj[i]
    }
    return newObj
  }

this.list.push(cope(this.studnet))

图示:

 那么问题来了如果里面还有一层对象呢?

data里面的数据:

  data: {
      studnet: {
        name: '张三',
        age: 23,
        aa: {
          sex: '男'
        }
      },

 

提交了两条数据:

 但是但在修改的时候

 这显然不是我们想要的,所以得继续去进行优化。

代码:

  function cope(obj) {
      let newobj = {}
      for (let i in obj) {
        if (obj[i] instanceof Object) {
          // 如果是一个对象的话,利用递归完成深度的拷贝
          newobj[i] = cope(obj[i])
        } else {
          // 如果不是一个对象,完成一次浅拷贝
          newobj[i] = obj[i]
        }
      }
      // 最后把处理的结果return给函数
      return newobj
    }

图示:

 解决了这个问题。利用了对象的深度拷贝,和递归的知识。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值