在vue中子組件修改props引發的對js深拷貝和淺拷貝的思考


在vue中子組件修改props引發的對js深拷貝和淺拷貝的思考

 

不管是react還是vue,父級組件與子組件的通信都是通過props來實現的,在vue中父組件的props遵循的是單向數據流,用官方的話說就是,父級的props的更新會向下流動到子組件中,反之則不行。也就是說,子組件不應該去修改props。但實際開發過程中,可能會有一些情況試圖去修改props數據:

1、這個props只是傳遞一個初始值,子組件把它當做一個局部變量來使用,這種情況一般定義一個本地的data屬性,將props的值賦值給它。如下:

1
2
3
4
5
6
props: [ 'initialCounter' ],
data: function () {
   return  {
     counter:  this .initialCounter
   }
}

2、這個props的值以原始數據傳入,但是子組件對其需要轉換。這種情況,最好使用computed來定義一個計算屬性,如下:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

以上兩種情況,傳遞的值都是基本數據類型,但是大多數情況下,我們需要向子組件傳遞一個引用類型數據,那么問題就來了。

JavaScript 中對象和數組是通過引用傳入的,所以對於一個數組或對象類型的 prop 來說,在子組件中改變這個對象或數組本身將會影響到父組件的狀態。

比如,在父組件中有一個列表,雙擊其中一個元素進行編輯,該元素的數據作為props傳遞給一個子組件,在子組件中需要對該數據進行編輯,你會發現如上所說,編輯后父組件的值也發生了變化。實際上我們想父組件影響子組件,但是子組件修改不要影響父組件。vue官網上貌似沒說明這種情況應該如何處理。

這里情況相對簡單點,在傳遞props時用Object.assign拷貝一份數據(這里數據是一個單層級對象),然后在子組件里面對其進行編輯。Object.assign能實現對象的合並,但是它是淺拷貝,也就是說如果對象的熟悉也是對象就不行。

於是查閱了相關資料,再次鞏固下JS中深拷貝與淺拷貝的相關知識。

1、基本數據類型和引用數據類型的存儲位置

 基本數據類型是存儲在棧內存中,比如 var a=1;

當進行復制操作b=a時,會在棧內存中再開一個內存,如下

變量a和變量b的存儲互補影響,如果此時修改b的值不會影響a的值。

引用類型數據存儲在堆內存中,引用類型的名是存儲在棧內存中,值是存儲在堆內存中,但是棧內存會提供引用地址指向堆內存中的值。

當進行b=a的復制操作時,復制的是引用地址,而不是堆內存中的值。

而當我們a[0]=1時進行數組修改時,由於a與b指向的是同一個地址,所以自然b也受了影響,這就是所謂的淺拷貝了。

而實際上我們希望的效果應該是這樣:

好,到這里,到底什么是深淺拷貝:

對於僅僅是復制了引用(地址),換句話說,復制了之后,原來的變量和新的變量指向同一個東西,彼此之間的操作會互相影響,為 淺拷貝

而如果是在堆中重新分配內存,擁有不同的地址,但是值是一樣的,復制后的對象與原來的對象是完全隔離,互不影響,為 深拷貝

 

回顧下JS里實現拷貝的方法有哪些:

針對數組有這些方法:

Array.slice()

1
2
var  a=[1,2,3];
var  b=a.slice();<br>b[0]=4;<br>console.log(b); //[4,2,3]<br>console.log(a);//[1,2,3]

Array.concat

1
2
3
4
5
var  a=[1,2,3];
var  b=a.concat();
b[0]=4;
console.log(b); //[4,2,3]
console.log(a); //[1,2,3]

 當然,也可以遍歷數組賦值。

但是以上兩種只對單級結構的數組有效,如果數組的元素是一個引用類型,就不行了,比如:

1
2
3
4
5
let  a=[0,1,[2,3],4],
         b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);

 

修改二維數組的元素還是會影響原數組,也就是說slice和concat實際上是淺拷貝。

針對對象:

Object.assign()

1
2
3
4
5
6
7
var  a={
   "name" : "張三" 
};
b=Object.assign({},a);
b.name= "李四"
console.log(b.name); //李四
console.log(a.name); //張三

同樣該方法也是淺拷貝,如果對象屬性值是引用類型也不行;

那么到底有哪些辦法可以實現深拷貝呢

1、遞歸

復制代碼
function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判斷ojb子元素是否為對象,如果是,遞歸復制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,簡單復制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);
復制代碼

2、jquery中的$.extend();

復制代碼
var obj = {name:'xixi',age:20,company : { name : '騰訊', address : '深圳'} };
var obj_extend = $.extend(true,{}, obj); //extend方法,第一個參數為true,為深拷貝,為false,或者沒有為淺拷貝。
console.log(obj === obj_extend);
obj.company.name = "ali";
obj.name = "hei";
console.log(obj);
console.log(obj_extend);
復制代碼

3、JSON對象的JSON.parse()和JSON.stringify();

復制代碼
var obj = {name:'xixi',age:20,company : { name : '騰訊', address : '深圳'} };
var obj_json = JSON.parse(JSON.stringify(obj));
console.log(obj === obj_json);
obj.company.name = "ali";
obj.name = "hei";
console.log(obj);
console.log(obj_json);
復制代碼

4、Lodash中的_.cloneDeep()

var objects = [{ 'a': 1 }, { 'b': 2 }];
 
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

 

雖然通過拷貝props數據解決了問題,但是拷貝后修改新數據的屬性並不會觸發vue的更新機制,需要強制更新$forceUpdate(),總覺得很奇怪,不知道大家有什么更好的辦法沒有,歡迎大家留言討論。

 

參考文章:

https://zhuanlan.zhihu.com/p/26282765

https://zhuanlan.zhihu.com/p/26282765


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM