Vue父子组件传值的一些坑


Posted in Javascript onSeptember 16, 2020

在用 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", 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父子组件传值内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Javascript this 的一些学习总结
Aug 02 Javascript
判断是否安装flash player及当前版本的JS代码
Aug 08 Javascript
jQuery实现的背景动态变化导航菜单效果
Aug 24 Javascript
js选择器全面解析
Jun 27 Javascript
浅谈JS中String()与 .toString()的区别
Oct 20 Javascript
Bootstrap基本样式学习笔记之按钮(4)
Dec 07 Javascript
js登录滑动验证的实现(不滑动无法登陆)
Jan 03 Javascript
JavaScript设计模式之缓存代理模式原理与简单用法示例
Aug 07 Javascript
css配合JavaScript实现tab标签切换效果
Oct 11 Javascript
详解Vue底部导航栏组件
May 02 Javascript
Node 搭建一个静态资源服务器的实现
May 20 Javascript
在elementui中Notification组件添加点击事件实例
Nov 11 Javascript
vue-cli3项目打包后自动化部署到服务器的方法
Sep 16 #Javascript
vue项目打包后提交到git上为什么没有dist这个文件的解决方法
Sep 16 #Javascript
vue 自定指令生成uuid滚动监听达到tab表格吸顶效果的代码
Sep 16 #Javascript
vue中选中多个选项并且改变选中的样式的实例代码
Sep 16 #Javascript
vue实现div可拖动位置也可改变盒子大小的原理
Sep 16 #Javascript
Vue项目打包编译优化方案
Sep 16 #Javascript
Vue封装Axios请求和拦截器的步骤
Sep 16 #Javascript
You might like
php Notice: Undefined index 错误提示解决方法
2010/08/29 PHP
php下批量挂马和批量清马代码
2011/02/27 PHP
PHP获取文件夹大小函数用法实例
2015/07/01 PHP
PHP输入流php://input实例讲解
2015/12/22 PHP
php连接oracle数据库的方法(测试成功)
2016/05/26 PHP
为jquery的ajaxfileupload增加附加参数的方法
2014/03/04 Javascript
点击A元素触发B元素的事件在IE8下会识别成A元素
2014/09/04 Javascript
jQuery多媒体插件jQuery Media Plugin使用详解
2014/12/19 Javascript
js+HTML5基于过滤器从摄像头中捕获视频的方法
2015/06/16 Javascript
JavaScript深度复制(deep clone)的实现方法
2016/02/19 Javascript
浅析$.getJSON异步请求和同步请求
2016/06/06 Javascript
详解ElementUI之表单验证、数据绑定、路由跳转
2017/06/21 Javascript
Vue写一个简单的倒计时按钮功能
2018/04/20 Javascript
Vue递归组件+Vuex开发树形组件Tree--递归组件的简单实现
2019/04/01 Javascript
详解Vue依赖收集引发的问题
2019/04/22 Javascript
vuex存储复杂参数(如对象数组等)刷新数据丢失的解决方法
2019/11/05 Javascript
Vue Element校验validate的实例
2020/09/21 Javascript
JS中多层次排序算法的实现代码
2021/01/06 Javascript
[01:32]DOTA2 2015国际邀请赛中国区预选赛第四日战报
2015/05/29 DOTA
python uuid模块使用实例
2015/04/08 Python
在Python中操作字典之setdefault()方法的使用
2015/05/21 Python
python 全文检索引擎详解
2017/04/25 Python
浅谈flask截获所有访问及before/after_request修饰器
2018/01/18 Python
Python实现JSON反序列化类对象的示例
2018/01/31 Python
python文件拆分与重组实例
2018/12/10 Python
python如何实现不用装饰器实现登陆器小程序
2019/12/14 Python
如何利用python进行时间序列分析
2020/08/04 Python
python如何实现递归转非递归
2021/02/25 Python
一款基于css3和jquery实现的动画显示弹出层按钮教程
2015/01/04 HTML / CSS
HTML5和CSS3实例教程总结(推荐)
2016/07/18 HTML / CSS
德国机场停车位比较和预订网站:Ich-parke-billiger
2018/01/08 全球购物
会计与审计毕业生自荐信范文
2013/12/30 职场文书
酒店开业庆典主持词
2014/03/21 职场文书
小学生综合素质评语
2014/04/23 职场文书
2016应届毕业生自荐信范文
2016/01/28 职场文书
Vue OpenLayer测距功能的实现
2022/04/20 Vue.js