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 相关文章推荐
jquery 应用代码 方便的排序功能
Feb 06 Javascript
简洁短小的 JavaScript IE 浏览器判定代码
Mar 21 Javascript
基于jquery的loading 加载提示效果实现代码
Sep 01 Javascript
javascript列表框操作函数集合汇总
Nov 28 Javascript
js超时调用setTimeout和间歇调用setInterval实例分析
Jan 28 Javascript
JavaScript、C# URL编码、解码总结
Jan 21 Javascript
vue的基本用法与常见指令
Aug 15 Javascript
Angular客户端请求Rest服务跨域问题的解决方法
Sep 19 Javascript
vue 实现类似淘宝星级评分的示例
Mar 01 Javascript
详解webpack import()动态加载模块踩坑
Jul 17 Javascript
JS Object.preventExtensions(),Object.seal()与Object.freeze()用法实例分析
Aug 25 Javascript
使用iView Upload 组件实现手动上传图片的示例代码
Oct 01 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调用三种数据库的方法(1)
2006/10/09 PHP
一个颜色轮换的简单例子
2006/10/09 PHP
php5中date()得出的时间为什么不是当前时间的解决方法
2008/06/30 PHP
获取用户Ip地址通用方法与常见安全隐患(HTTP_X_FORWARDED_FOR)
2013/06/01 PHP
php数组查找函数总结
2014/11/18 PHP
php jsonp单引号转义
2014/11/23 PHP
PHP使用strtotime计算两个给定日期之间天数的方法
2015/03/18 PHP
十大使用PHP框架的理由
2015/09/26 PHP
php字符串比较函数用法小结(strcmp,strcasecmp,strnatcmp及strnatcasecmp)
2016/07/18 PHP
PHP解耦的三重境界(浅谈服务容器)
2017/03/13 PHP
php实现自定义中奖项数和概率的抽奖函数示例
2017/05/26 PHP
深入解析PHP底层机制及相关原理
2020/12/11 PHP
JavaScript使用prototype定义对象类型
2007/02/07 Javascript
jquery 简短几句代码实现给元素动态添加及获取提示信息
2011/09/01 Javascript
JavaScript图片放大技术(放大镜)实现代码分享
2013/11/14 Javascript
JavaScript实现点击自动选择TextArea文本的方法
2015/07/02 Javascript
jQuery表格插件datatables用法详解
2020/11/23 Javascript
EasyUI中在表单提交之前进行验证
2016/07/19 Javascript
Jquery EasyUI Datagrid右键菜单实现方法
2016/12/30 Javascript
javascript实现延时显示提示框效果
2017/06/01 Javascript
在vue项目中使用sass的配置方法
2018/03/20 Javascript
详解vue如何使用rules对表单字段进行校验
2018/10/17 Javascript
vue-cli3 项目优化之通过 node 自动生成组件模板 generate View、Component
2019/04/30 Javascript
微信小程序后台持续定位功能使用详解
2019/08/23 Javascript
Python 除法小技巧
2008/09/06 Python
用Python进行基础的函数式编程的教程
2015/03/31 Python
Pycharm 2020年最新激活码(亲测有效)
2020/09/18 Python
Python基于Webhook实现github自动化部署
2020/11/28 Python
SportsDirect.com马来西亚:英国第一体育零售商
2018/11/21 全球购物
DeinDesign德国:设计自己的手机壳
2019/12/14 全球购物
实现strstr功能,即在父串中寻找子串首次出现的位置
2016/08/05 面试题
教师党员思想汇报
2014/01/06 职场文书
骨干教师考核方案
2014/05/09 职场文书
销售员岗位职责
2015/02/10 职场文书
那些美到让人窒息的诗句,值得你收藏!
2019/08/20 职场文书
Android开发手册TextInputLayout样式使用示例
2022/06/10 Java/Android