首頁 >web前端 >js教程 >JavaScript專題之五:深淺拷貝

JavaScript專題之五:深淺拷貝

coldplay.xixi
coldplay.xixi轉載
2021-03-08 09:36:142037瀏覽

JavaScript專題之五:深淺拷貝

了解拷貝背後的過程,避免不必要的錯誤,Js專題系列之深淺拷貝,我們一起加油~

目錄

  • 一、拷貝範例
  • 二、淺拷貝
  • 三、深拷貝的方法?
  • 四、自己實作深淺拷貝

#免費學習推薦:##javascript影片教學

一、拷貝範例

#當我們在操作資料之前,可能會遇到這樣的情況:

    會經常改變一組數據,但可能會用到原始數據
  1. 我需要兩組一樣的數據,但我不希望改動一個另一個隨之改動
  2. #我需要對資料操作前後進行對比
當我們遇到類似需要場景時,首先想到的就是拷貝它,殊不知拷貝也大有學問哦~

下面簡單的例子,你是否覺得熟悉?

1.1 基本型別拷貝範例
var str = 'How are you';var newStr = str;newStr = 10console.log(str); // How are youconsole.log(newStr); // 10
大家都能想到,字串是基本型,它的值保存在堆疊中,在對它進行拷貝時,其實是為新變數開闢了新的空間。

strnewStr就好比兩個一模一樣的房間,佈局一致卻毫無關聯。

1.2 引用類型拷貝範例
var data = [1, 2, 3, 4, 5];var newData = data;newData[0] = 100;console.log(data[0]); // 100console.log(newData[0]); // 100
類似的程式碼段,但這次我們使用陣列這個引用型別舉例,你會發現修改賦值後的數據,原始資料也跟著改變了,這顯然不滿足我們的需要。本篇文章就來聊一聊

引用資料拷貝的學問

如果大家對Js的資料型別有疑問,不妨看看《JavaScript中的基本資料型別》

JavaScript專題之五:深淺拷貝

二、淺拷貝

拷貝的劃分都是針對引用類型來討論的,淺拷貝——顧名思義,淺拷貝就是“淺層拷貝”,實際上只做了表面功夫:

var arr = [1, 2, 3, 4];var newArr = arr;console.log(arr, newArr); // [1,2,3,4] [1,2,3,4]newArr[0] = 100;console.log(arr, newArr) // [100,2,3,4] [100,2,3,4]
不發生事情(操作)還好,一旦對新數組進行了操作,兩個變數中所保存的資料都會改變。

發生這類情況的原因也是因為

引用類型的基本特性:

    儲存在變數處的值是一個指標(point),指向存儲對象的記憶體位址。賦值給新變數相當於配了一把新鑰匙,房間並沒有換。
陣列中的slice和concat都會回傳一個新數組,我們一起來試試看:

var arr = [1,2,3,4];var res = arr.slice();// 或者res = arr.concat()res[0] = 100;console.log(arr); // [1,2,3,4]
這個問題這麼快就解決了?雖然對這一層資料進行了這樣的處理後,確實解決了問題,但!

var arr = [ { age: 23 }, [1,2,3,4] ];var newArr = arr.concat();arr[0].age = 18;arr[1][0] = 100;console.log(arr) // [ {age: 18}, [100,2,3,4] ]console.log(newArr) // [ {age: 18}, [100,2,3,4] ]
果然事情沒有那麼簡單,這也是因為資料類型的不同。

S 不允許我們直接操作記憶體中的位址,也就是說不能操作物件的記憶體空間,所以,我們對物件的操作都只是在操作它的引用而已。

既然

淺拷貝達不到我們的要求,本著效率的原則,我們找找有沒有幫助我們實現深拷貝的方法。

JavaScript專題之五:深淺拷貝

三、深拷貝的方法?

資料的方法失敗​​了,還有沒有其他辦法?我們需要實現真正意義上的拷貝出獨立的資料。

3.1 JSON
這裡我們利用JSON的兩個方法,

JSON.stringify()JSON.parse()來實作最簡潔的深拷貝

var arr = ['str', 1, true, [1, 2], {age: 23}]var newArr = JSON.parse( JSON.stringify(arr) );newArr[3][0] = 100;console.log(arr); // ['str', 1, true, [1, 2], {age: 23}]console.log(newArr); // ['str', 1, true, [100, 2], {age: 23}]
這個方法應該是實現深拷貝最簡潔的方法,但是,

它仍然存在問題,我們先來看看剛才都做了些什麼:

    定義一個包含都過類型的陣列
  1. arr
  2. JSON.stringify(arr), 將一個JavaScript 物件或值轉換為
  3. JSON 字串
  4. JSON.parse(xxx), 方法用來解析JSON字串,建構由字串描述的
  5. 值或物件
## 來理解:

我們可以理解為,將原始資料轉換為

新字串

,再透過新字串還原為一個新物件,這中改變資料型態的方式,間接的繞過了拷貝物件參考的過程,也就談不上影響原始資料。

限制:

這種方式成立的根本就是保證資料在「中轉」時的完整性,而

JSON.stringify()

將值轉換為對應的JSON格式時也有缺陷:<ul> <li>undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。</li> <li>函数、undefined 被单独转换时,会返回 undefined,<ul> <li>如JSON.stringify(function(){})</li> <li>JSON.stringify(undefined)</li> </ul> </li> <li>对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。</li> <li>NaN 和 Infinity 格式的数值及 null 都会被当做 null。</li> <li>其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。</li> </ul> <p>所以当我们拷贝函数、undefined等<code>stringify转换有问题的数据时,就会出错,我们在实际开发中也要结合实际情况使用。

举一反三:

既然是通过改变数据类型来绕过拷贝引用这一过程,那么单纯的数组深拷贝是不是可以通过现有的几个API来实现呢?

var arr = [1,2,3];var newArr = arr.toString().split(',').map(item => Number(item))newArr[0] = 100;console.log(arr); // [1,2,3]console.log(newArr); // [100,2,3]

注意,此时仅能对包含纯数字的数组进行深拷贝,因为:

  1. toString无法正确的处理对象和函数
  2. Number无法处理 false、undefined等数据类型

但我愿称它为纯数字数组深拷贝

JavaScript專題之五:深淺拷貝

3.2 Object.assign()

有的人会认为Object.assign(),可以做到深拷贝,我们来看一下

var obj = {a: 1, b: { c: 2 } }var newObj = Object.assign({}, obj)newObj.a = 100;newObj.b.c = 200;console.log(obj); // {a: 1, b: { c: 200 } }console.log(newObj) // {a: 100, b: { c: 200 } }

神奇,第一层属性没有改变,但第二层却同步改变了,这是为什么呢?

因为 Object.assign()拷贝的是(可枚举)属性值。

假如源值是一个对象的引用,它仅仅会复制其引用值。MDN传送门

四、自己实现深浅拷贝

既然现有的方法无法实现深拷贝,不妨我们自己来实现一个吧~

4.1 浅拷贝

我们只需要将所有属性即其嵌套属性原封不动的复制给新变量一份即可,抛开现有的方法,我们应该怎么做呢?

var shallowCopy = function(obj) {
    if (typeof obj !== 'object') return;

    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;}

我们只需要将所有属性的引用拷贝一份即可~

4.2 深拷贝

相信大家在实现深拷贝的时候都会想到递归,同样是判断属性值,但如果当前类型为object则证明需要继续递归,直到最后

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;}

我们用白话来解释一下deepCopy都做了什么

const obj = [1, { a: 1, b: { name: '余光'} } ];const resObj = deepCopy(obj);
  • 读取 obj,创建 第一个newObj
    • 判断类型为 []
    • key为 0 (for in 以任意顺序遍历,我们假定按正常循序遍历)
    • 判断不是引用类型,直接复制
    • key为 1
    • 判断是引用类型
    • 进入递归,重新走了一遍刚才的流程,只不过读取的是obj[1]

另外请注意递归的方式虽然可以深拷贝,但是在性能上肯定不如浅拷贝,大家还是需要结合实际情况来选择。

写在最后

JavaScript專題之五:深淺拷貝

前端专项进阶系列的第五篇文章,希望它能对大家有所帮助,如果大家有什么建议,可以在评论区留言,能帮到自己和大家就是我最大的动力!

相关免费学习推荐:javascript(视频)

以上是JavaScript專題之五:深淺拷貝的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除