1. 程式人生 > 程式設計 >Vue父子元件傳值的一些坑

Vue父子元件傳值的一些坑

在用 Vue 的父子元件傳值時遇到一個冷門的問題,子元件改變值後父元件的值也隨之改變了,特此記錄下原因和解決方式。
再系統梳理下 JavaScript 的深拷貝與淺拷貝相關知識點。

1. 問題描述

父元件傳值給子元件,子元件改變傳過來的值後,父元件的值也會跟著改變。
這個問題比較冷門,平時如果對元件通訊使用得比較簡單,一般不會遇到。

2. 原因剖析

  • 核心:雙向繫結

父子元件傳值的時候涉及雙向繫結,當傳值為 object 型別時,傳值之後資料來源會被改變。

  • 深拷貝與淺拷貝

下文詳細講。

3. 解決方案

我目前採用的解決辦法是:
傳值的時候不要直接傳資料來源,而是通過拷貝或者定義新變數等方式傳值。

簡單處理就 JSON.parse(JSON.stringify(obj)),但是這種簡單粗暴的方法有其侷限性。當值為 undefined、function、symbol 會在轉換過程中被忽略。所以,物件值有這三種的話用這種方法會導致屬性丟失。

剩下的就是自寫深拷貝的工具函式,或者直接藉助第三方的庫函式,下面展開講。

4. 深拷貝和淺拷貝

JavaScript中的淺拷貝與深拷貝,只是針對複雜資料型別(Object,Array)的複製問題。淺拷貝與深拷貝都可以實現在已有物件上再生出一份的作用。但是物件的例項是儲存在堆記憶體中然後通過一個引用值去操作物件,由此拷貝的時候就存在兩種情況了:拷貝引用和拷貝例項,這也是淺拷貝和深拷貝的區別。

下圖為JavaScript複雜資料型別的淺拷貝示意圖:

Vue父子元件傳值的一些坑

  • 淺拷貝

淺拷貝是拷貝引用,拷貝後的引用都是指向同一個物件的例項,彼此之間的操作會互相影響。

值得注意的是:Object.assgin() 是淺拷貝,它只能深拷貝第一層,深層的還是淺拷貝。因為 Object.assign() 拷貝的是屬性值。假如源物件的屬性值是一個物件的引用,那麼它也只指向那個引用。(摘選自MDN)

MDN講述 assign 的時候,就有一個典型的例子,這裡是文章連結。

下面列舉第一類淺拷貝 - 拷貝原物件的引用:

/**
 * 物件的淺拷貝
 */
var obj1 = {
 name:'wenyuan',age: 22
}
var obj2 = obj1;
obj2['job'] = 'coder';
console.log(obj1); //Object {name: "wenyuan",age: 22,job: "coder"}
console.log(obj2); //Object {name: "wenyuan",age: 0,job: "coder"}


/* ------------------------- 華麗的分割線 ------------------------- */


/**
 * 陣列的淺拷貝
 */
var arr1 = [1,2,3,'4'];
var arr2 = arr1;
arr2[1] = "test"; 
console.log(arr1); // [1,"test","4"]
console.log(arr2); // [1,"4"]

接下來看第二類淺拷貝 - 源物件拷貝例項,其屬性物件拷貝引用:

這種情況,外層源物件是拷貝例項,如果其屬性元素為複雜資料型別(Object、Array)時,內層元素拷貝引用。

對源物件直接操作,不影響另外一個物件,但是對其屬性操作時候,會改變另外一個物件的屬性的值。

/**
 * 物件的淺拷貝
 * jQuery的 $.extend(a,b) 或 $.extend({},a,b)
 */
var obj1 = {
 name:'wenyuan',social: {
  blog: 'www.wenyuanblog.com'
 },skills: ['js','html','css','python']
}
var obj2 = $.extend({},obj1);
console.log(obj1 === obj2) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.social === obj2.social) // 輸出true,說明對於Object型別的屬性是拷貝引用
console.log(obj1.skills === obj2.skills) // 輸出true,說明對於Array型別的屬性是拷貝引用


/**
 * 物件的淺拷貝
 * ES6的 Object.assign() 和 物件擴充套件運算子...
 */
var obj1 = {
 name:'wenyuan','python']
}
var obj2 = Object.assign({},obj1);
console.log(obj1 === obj2) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.social === obj2.social) // 輸出true,說明對於Object型別的屬性是拷貝引用
console.log(obj1.skills === obj2.skills) // 輸出true,說明對於Array型別的屬性是拷貝引用
var obj3 = {...obj1};
console.log(obj1 === obj3) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.skills === obj3.skills) // 輸出true,說明對於Array型別的屬性是拷貝引用
console.log(obj1.skills === obj3.skills) // 輸出true,說明對於Array型別的屬性是拷貝引用


/* ------------------------- 華麗的分割線 ------------------------- */


/**
 * 陣列的淺拷貝
 * Array.prototype.slice()
 */
var arr1 = [{name: "wenyuan"},{name: "Evan You"}];
var arr2 = arr1.slice(0);
console.log(arr1 === arr2); // 輸出false,說明外層陣列拷貝的是例項
console.log(arr1[0] === arr2[0]); // 輸出true,說明其元素拷貝的是引用


/**
 * 陣列的淺拷貝
 * Array.prototype.concat()
 */
var arr1 = [{name: "wenyuan"},{name: "Evan You"}];
var arr2 = arr1.concat();
console.log(arr1 === arr2); // 輸出false,說明外層陣列拷貝的是例項
console.log(arr1[0] === arr2[0]); // 輸出true,說明其元素拷貝的是引用


/**
 * 陣列的淺拷貝
 * ES6的 Object.assign() 和 物件擴充套件運算子...
 * 由於陣列是特殊的物件,所以ES6中的這種方式也可以用於陣列
 */
var arr1 = [{name: "wenyuan"},{name: "Evan You"}];
var arr2 = Object.assign([],arr1)
var arr3 = { ...arr1 };
console.log(arr1 === arr2); // 輸出false,說明外層陣列拷貝的是例項
console.log(arr1 === arr3); // 輸出false,說明外層陣列拷貝的是例項
console.log(arr1[0] === arr2[0]); // 輸出true,說明其元素拷貝的是引用
console.log(arr1[0] === arr3[0]); // 輸出true,說明其元素拷貝的是引用
  • 深拷貝

在堆中重新分配記憶體,並且把源物件所有屬性都進行新建拷貝,以保證深拷貝的物件的引用圖不包含任何原有物件或物件圖上的任何物件,拷貝後的物件與原來的物件是完全隔離,互不影響。

下面列舉一些深拷貝的例子:

/**
 * 物件的深拷貝
 * JSON.stringify()和JSON.parse()
 * 這種深拷貝最簡單,但有其侷限性,上文已經提到過了
 */
var obj1 = {
 name:'wenyuan','python']
}
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1 === obj2) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.social === obj2.social) // 輸出false,說明對於Object型別的屬性也是拷貝例項
console.log(obj1.skills === obj2.skills) // 輸出false,說明對於Array型別的屬性也是拷貝例項


/**
 * 物件的深拷貝
 * jQuery的 $.extend(true,'python']
}
var obj2 = $.extend(true,obj1);
console.log(obj1 === obj2) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.social === obj2.social) // 輸出false,說明對於Object型別的屬性也是拷貝例項
console.log(obj1.skills === obj2.skills) // 輸出false,說明對於Array型別的屬性也是拷貝例項


/**
 * 物件的深拷貝
 * 也可以自己寫一個函式實現,用遞迴+判斷,注意別進入死迴圈就好
 * 這裡不舉例了,以前我整理過一篇常用工具類函式的部落格,裡面包含了深拷貝函式
 */


/**
 * 物件的深拷貝
 * lodash的_.cloneDeep
 */
var obj1 = {
 name:'wenyuan','python']
}
var obj2 = _.cloneDeep(obj1);
console.log(obj1 === obj2) // 輸出false,說明外層陣列拷貝的是例項
console.log(obj1.social === obj2.social) // 輸出false,說明對於Object型別的屬性也是拷貝例項
console.log(obj1.skills === obj2.skills) // 輸出false,說明對於Array型別的屬性也是拷貝例項

以上就是JavaScript中的淺拷貝與深拷貝的知識點,以程式碼的形式記錄下來,方便回顧。

到此這篇關於Vue父子元件傳值的一些坑的文章就介紹到這了,更多相關Vue父子元件傳值內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!