了解拷貝背後的過程,避免不必要的錯誤,Js專題系列之深淺拷貝,我們一起加油~
目錄
四、自己實作深淺拷貝
#免費學習推薦:##javascript影片教學
一、拷貝範例
#當我們在操作資料之前,可能會遇到這樣的情況:下面簡單的例子,你是否覺得熟悉? 1.1 基本型別拷貝範例
var str = 'How are you';var newStr = str;newStr = 10console.log(str); // How are youconsole.log(newStr); // 10
str和
newStr就好比兩個一模一樣的房間,佈局一致卻毫無關聯。
var data = [1, 2, 3, 4, 5];var newData = data;newData[0] = 100;console.log(data[0]); // 100console.log(newData[0]); // 100
引用資料拷貝的學問。
如果大家對Js的資料型別有疑問,不妨看看《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]不發生事情(操作)還好,一旦對新數組進行了操作,兩個變數中所保存的資料都會改變。 發生這類情況的原因也是因為
引用類型的基本特性:
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 不允許我們直接操作記憶體中的位址,也就是說不能操作物件的記憶體空間,所以,我們對物件的操作都只是在操作它的引用而已。 既然
淺拷貝達不到我們的要求,本著
效率的原則,我們找找有沒有幫助我們實現深拷貝的方法。
三、深拷貝的方法?
資料的方法失敗了,還有沒有其他辦法?我們需要實現真正意義上的拷貝出獨立的資料。 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}]這個方法應該是實現深拷貝最簡潔的方法,但是,
它仍然存在問題,我們先來看看剛才都做了些什麼:
我們可以理解為,將原始資料轉換為
新字串,再透過新字串
還原為一個新物件
,這中改變資料型態的方式,間接的繞過了拷貝物件參考的過程,也就談不上影響原始資料。
這種方式成立的根本就是保證資料在「中轉」時的完整性,而
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]
注意,此时仅能对包含纯数字的数组进行深拷贝,因为:
但我愿称它为纯数字数组深拷贝!
有的人会认为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传送门
四、自己实现深浅拷贝
既然现有的方法无法实现深拷贝,不妨我们自己来实现一个吧~
我们只需要将所有属性即其嵌套属性原封不动的复制给新变量一份即可,抛开现有的方法,我们应该怎么做呢?
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;}
我们只需要将所有属性的引用拷贝一份即可~
相信大家在实现深拷贝的时候都会想到递归,同样是判断属性值,但如果当前类型为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[]
0
(for in
以任意顺序遍历,我们假定按正常循序遍历)1
obj[1]
另外请注意递归的方式虽然可以深拷贝,但是在性能上肯定不如浅拷贝,大家还是需要结合实际情况来选择。
写在最后
前端专项进阶系列的第五篇文章
,希望它能对大家有所帮助,如果大家有什么建议,可以在评论区留言,能帮到自己和大家就是我最大的动力!
相关免费学习推荐:javascript(视频)
以上是JavaScript專題之五:深淺拷貝的詳細內容。更多資訊請關注PHP中文網其他相關文章!