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 相关文章推荐
js计算精度问题小结
Apr 22 Javascript
原始XMLHttpRequest方法详情回顾
Nov 28 Javascript
js propertychange和oninput事件
Sep 28 Javascript
jQuery实现转动随机数抽奖效果的方法
May 21 Javascript
jQuery仿淘宝网产品品牌隐藏与显示效果
Sep 01 Javascript
基于Bootstrap实现tab标签切换效果
Apr 15 Javascript
easyui中combotree循环获取父节点至根节点并输出路径实现方法
Nov 10 Javascript
React.js中常用的ES6写法总结(推荐)
May 09 Javascript
详解AngularJS脏检查机制及$timeout的妙用
Jun 19 Javascript
详解小程序原生使用ES7 async/await语法
Aug 06 Javascript
解决vue中post方式提交数据后台无法接收的问题
Aug 11 Javascript
Ajax 的初步实现(使用vscode+node.js+express框架)
Jun 18 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 文件类型判断代码
2009/03/13 PHP
PHP反射类ReflectionClass和ReflectionObject的使用方法
2013/11/13 PHP
PHP内置的Math函数效率测试
2014/12/01 PHP
PHP 在数组中搜索给定的简单实例 array_search 函数
2016/06/13 PHP
php基于PDO连接MSSQL示例DEMO
2016/07/13 PHP
PHP实现webshell扫描文件木马的方法
2017/07/31 PHP
PHP实现多图上传和单图上传功能
2018/05/17 PHP
分享几种好用的PHP自定义加密函数(可逆/不可逆)
2020/09/15 PHP
基于jquery的一个拖拽到指定区域内的效果
2011/09/21 Javascript
nodejs中exports与module.exports的区别详细介绍
2013/01/14 NodeJs
javascript生成随机数的方法
2014/05/16 Javascript
jQuery中clone()方法用法实例
2015/01/16 Javascript
JQuery实现可直接编辑的表格
2015/04/16 Javascript
JS+CSS实现简单滑动门(滑动菜单)效果
2015/09/19 Javascript
Javascript 计算字符串在localStorage中所占字节数
2015/10/21 Javascript
JavaScript实现下拉菜单的显示和隐藏
2016/01/05 Javascript
详解JavaScript的AngularJS框架中的作用域与数据绑定
2016/03/04 Javascript
js格式化时间的简单实例
2016/11/27 Javascript
JavaScript 字符串常用操作小结(非常实用)
2016/11/30 Javascript
Vue结合后台导入导出Excel问题详解
2019/02/19 Javascript
node删除、复制文件或文件夹示例代码
2019/08/13 Javascript
微信小程序实现侧边栏分类
2019/10/21 Javascript
Express 配置HTML页面访问的实现
2020/11/01 Javascript
Python中super的用法实例
2015/05/28 Python
python实现简易版计算器
2020/06/22 Python
Python 装饰器实现DRY(不重复代码)原则
2018/03/05 Python
Python基础之字典常见操作经典实例详解
2020/02/26 Python
python用Configobj模块读取配置文件
2020/09/26 Python
python字典通过值反查键的实现(简洁写法)
2020/09/30 Python
Python 中如何使用 virtualenv 管理虚拟环境
2021/01/21 Python
网络安全方面的面试题
2015/11/04 面试题
图书室管理制度
2014/01/19 职场文书
《菜园里》教学反思
2014/04/17 职场文书
2015年秋季运动会前导词
2015/07/20 职场文书
pycharm无法导入lxml的解决办法
2021/03/31 Python
详解Python中的for循环
2022/04/30 Python