Vue父子组件传值的一些坑

在用 Vue 的父子组件传值时遇到一个冷门的问题,子组件改变值后父组件的值也随之改变了,特此记录下原因和解决方式。
再系统梳理下 JavaScript 的深拷贝与浅拷贝相关知识点。

1. 问题描述

父组件传值给子组件,子组件改变传过来的值后,父组件的值也会跟着改变。
这个问题比较冷门,平时如果对组件通信使用得比较简单,一般不会遇到。

2. 原因剖析

  • 核心:双向绑定

父子组件传值的时候涉及双向绑定,当传值为 object 类型时,传值之后数据源会被改变。

  • 深拷贝与浅拷贝

下文详细讲。

3. 解决方案

我目前采用的解决办法是:
传值的时候不要直接传数据源,而是通过拷贝或者定义新变量等方式传值。

简单处理就 JSON.parse(JSON.stringify(obj)),但是这种简单粗暴的方法有其局限性。当值为 undefined、function、symbol 会在转换过程中被忽略。所以,对象值有这三种的话用这种方法会导致属性丢失。

剩下的就是自写深拷贝的工具函数,或者直接借助第三方的库函数,下面展开讲。

4. 深拷贝和浅拷贝

JavaScript中的浅拷贝与深拷贝,只是针对复杂数据类型(Object,Array)的复制问题。浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别。

下图为JavaScript复杂数据类型的浅拷贝示意图:

  • 浅拷贝

浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响。

值得注意的是: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", 3, "4"]
console.log(arr2); // [1, "test", 3, "4"]

接下来看第二类浅拷贝 - 源对象拷贝实例,其属性对象拷贝引用:

这种情况,外层源对象是拷贝实例,如果其属性元素为复杂数据类型(Object、Array)时,内层元素拷贝引用。

对源对象直接操作,不影响另外一个对象,但是对其属性操作时候,会改变另外一个对象的属性的值。

/**
 * 对象的浅拷贝
 * jQuery的 $.extend(a,b) 或 $.extend({},a,b)
 */
var obj1 = {
 name:'wenyuan',
 age: 22,
 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',
 age: 22,
 social: {
  blog: 'www.wenyuanblog.com'
 },
 skills: ['js', 'html', 'css', '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',
 age: 22,
 social: {
  blog: 'www.wenyuanblog.com'
 },
 skills: ['js', 'html', 'css', '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,a,b)
 */
var obj1 = {
 name:'wenyuan',
 age: 22,
 social: {
  blog: 'www.wenyuanblog.com'
 },
 skills: ['js', 'html', 'css', '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',
 age: 22,
 social: {
  blog: 'www.wenyuanblog.com'
 },
 skills: ['js', 'html', 'css', '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父子组件传值内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue 组件间的通信之子组件向父组件传值的方式

    父组件向子组件传值:父组件通过属性向下传值的方式和子组件通信: 使用步骤: 定义组件:现有自定义组件com-a.com-b,com-a是com-b的父组件: 准备获取数据:com-b要获取父组件data中的name属性: 在<com-b :name="name"></com-b> 使用v-bind 绑定name属性,红色部分为属性名称,可以随意写. 在子组件定义部分里添加选项,值是个字符串数组 props:['name'],将上边红色的属性名称写在这里: 之后就

  • VUE子组件向父组件传值详解(含传多值及添加额外参数场景)

    一.子组件向父组件传递一个值 子组件: this.$emit('change', this.value); 父组件: <!-- 在父组件中使用子组件 --> <editable-cell :text="text" :inputType="inputType" @change="costPlannedAmountChange($event)" /> // 事件处理函数 async costPlannedAmountChang

  • vue父组件向子组件动态传值的两种方法

    在一些项目需求中需要父组件向子组件动态传值,比如我这里的需求是,父组件动态通过axios获取返回的图片url数组然后传给子组件,上传图片的子组件拿到该数组后进行遍历并展示图片 方法有两种, 方法一: props传值,这里注意一个问题,传过来的值需要用watch监听并赋值,否则这里获取到的是空数组   父组件: <uploadImg :width="200" :height="200" name="productImage" size=&qu

  • vue 父组件给子组件传值子组件给父组件传值的实例代码

    父组件如何给子组件传值 使用props 举个例子: 子组件:fromTest.vue,父组件 app.vue fromTest.vue <template> <h2>{{title}}</h2> //title必须是父组件传递的 </template> <script> export default (){ props:["title"] //可以是数组,也可以是对象 //如何对title进行校验 //props:{ // t

  • vue-prop父组件向子组件进行传值的方法

    vue-prop是父组件向子组件进行传递数据时使用的. 例如子组件为 child.vue template: '<div>msg: {{msg}}</div>' props: ['msg'], 我们在子组件里规定了一个数据名字叫msg 父组件里面写 <child msg="hello, vue.js!"></child> 这样我们就可以在子组件里看到msg:hello, vue.js! 以上这篇vue-prop父组件向子组件进行传值的方法

  • vue2.0 子组件改变props值,并向父组件传值的方法

    为什么我们会有修改 prop 中数据的冲动呢?通常是这两种原因: prop 作为初始值传入后,子组件想把它当作局部数据来用: prop 作为初始值传入,由子组件处理成其它数据输出. 对这两种原因,正确的应对方式是: 定义一个局部变量,并用 prop 的值初始化它: props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } 定义一个计算属性,处理 prop 的值并返回. prop

  • vue 子组件和父组件传值的示例

    一.子组件向父组件传值 1:子组件中(例:) 2:父组件中(例:) 二.父组件向子组件传值 1:父组件中(例:) 2:子组件中(例:) 以上就是vue 子组件和父组件传值的示例的详细内容,更多关于vue 子组件和父组件传值的资料请关注我们其它相关文章!

  • Vue实现的父组件向子组件传值功能示例

    本文实例讲述了Vue实现的父组件向子组件传值功能.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 父组件向子组件传值</title> <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></scrip

  • vue 子组件向父组件传值方法

    子组件注册触发事件,父组件注册 触发子组件事件后的方法写在method里面 父组件这么写 <component-a v-on:child-say="listenToMyBoy"></component-a> <p>Do you like me? {{childWords}}</p> methods: { listenToMyBoy: function (somedata){ this.childWords = somedata } } 子

  • Vue父组件向子组件传值以及data和props的区别详解

    1.在父组件中定义 msg 属性 data:{ msg:'123 -我是父组件中的数据' }, 2.引用子组件 父组件可以在引用子组件的时候,通过属性绑定的形式,把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用. 把父组件传递过来的 msg 属性,绑定到子组件的 parentmsg 属性上. <com1 :parentmsg="msg"></com1> 3. 在子组件定义部分,需要把父组件传递过来的 parentmsg 属性,先在pro

  • vue父组件给子组件的组件传值provide inject的方法

    一般情况下我们父子之间的传值用的是props,这个就不多说了,但是如果想让父给子组件的组件传值怎么办呢,这里我们就可以用到 provide 和 inject(依赖注入) provide/inject需要一起使用,我们可以从父组件的provide传值,子组件或者孙组件,就可以用inject来接受子组件的provide属性值 具体的可以看官网介绍provide/inject:https://cn.vuejs.org/v2/api/#provide-inject 下面我们可以写个简单的例子来演示一下

  • 图文介绍Vue父组件向子组件传值

    1:在src/components/child/文件夹下,创建一个名为:child.vue的子组件 2:在父组件中,设置好需要传递的数据 3:在App.vue中引入并注册子组件 4:通过v-bind属性绑定并赋值给子组件 5:子组件通过 props 接收父组件传递过的数据 6:查看 7:总结: 子组件在props中创建一个属性,用以接收父组件传过来的值父组件中注册子组件在子组件标签中添加子组件props中创建的属性把需要传给子组件的值赋给该属性 您可能感兴趣的文章: VUEJS 2.0 子组件访

随机推荐