不管是 react 还是 vue, 父级组件与子组件的通信都是通过 props 来实现的, 在 vue 中父组件的 props 遵循的是单向数据流, 用官方的话说就是, 父级的 props 的更新会向下流动到子组件中, 反之则不行. 也就是说, 子组件不应该去修改 props. 但实际开发过程中, 可能会有一些情况试图去修改 props 数据:
1, 这个 props 只是传递一个初始值, 子组件把它当做一个局部变量来使用, 这种情况一般定义一个本地的 data 属性, 将 props 的值赋值给它. 如下:
- props: ['initialCounter'],
- data: function () {
- return {
- counter: this.initialCounter
- }
- }
2, 这个 props 的值以原始数据传入, 但是子组件对其需要转换. 这种情况, 最好使用 computed 来定义一个计算属性, 如下:
- props: ['size'],
- computed: {
- normalizedSize: function () {
- return this.size.trim().toLowerCase()
- }
- }
以上两种情况, 传递的值都是基本数据类型, 但是大多数情况下, 我们需要向子组件传递一个引用类型数据, 那么问题就来了.
JavaScript 中对象和数组是通过引用传入的, 所以对于一个数组或对象类型的 prop 来说, 在子组件中改变这个对象或数组本身将会影响到父组件的状态.
比如, 在父组件中有一个列表, 双击其中一个元素进行编辑, 该元素的数据作为 props 传递给一个子组件, 在子组件中需要对该数据进行编辑, 你会发现如上所说, 编辑后父组件的值也发生了变化. 实际上我们想父组件影响子组件, 但是子组件修改不要影响父组件. vue 官网上貌似没说明这种情况应该如何处理.
这里情况相对简单点, 在传递 props 时用 Object.assign 拷贝一份数据 (这里数据是一个单层级对象), 然后在子组件里面对其进行编辑. Object.assign 能实现对象的合并, 但是它是浅拷贝, 也就是说如果对象的熟悉也是对象就不行.
于是查阅了相关资料, 再次巩固下 JS 中深拷贝与浅拷贝的相关知识.
1, 基本数据类型和引用数据类型的存储位置
基本数据类型是存储在栈内存中, 比如 var a=1;
当进行复制操作 b=a 时, 会在栈内存中再开一个内存, 如下
变量 a 和变量 b 的存储互补影响, 如果此时修改 b 的值不会影响 a 的值.
引用类型数据存储在堆内存中, 引用类型的名是存储在栈内存中, 值是存储在堆内存中, 但是栈内存会提供引用地址指向堆内存中的值.
当进行 b=a 的复制操作时, 复制的是引用地址, 而不是堆内存中的值.
而当我们 a[0]=1 时进行数组修改时, 由于 a 与 b 指向的是同一个地址, 所以自然 b 也受了影响, 这就是所谓的浅拷贝了.
而实际上我们希望的效果应该是这样:
好, 到这里, 到底什么是深浅拷贝:
对于仅仅是复制了引用 (地址), 换句话说, 复制了之后, 原来的变量和新的变量指向同一个东西, 彼此之间的操作会互相影响, 为 浅拷贝.
而如果是在堆中重新分配内存, 拥有不同的地址, 但是值是一样的, 复制后的对象与原来的对象是完全隔离, 互不影响, 为 深拷贝.
回顾下 JS 里实现拷贝的方法有哪些:
针对数组有这些方法:
- Array.slice()
- var a=[1,2,3];
- var b=a.slice();
- b[0]=4;
- console.log(b);//[4,2,3]
- console.log(a);//[1,2,3]
- Array.concat
- var a=[1,2,3];
- var b=a.concat();
- b[0]=4;
- console.log(b);//[4,2,3]
- console.log(a);//[1,2,3]
当然, 也可以遍历数组赋值.
但是以上两种只对单级结构的数组有效, 如果数组的元素是一个引用类型, 就不行了, 比如:
- let a=[0,1,[2,3],4],
- b=a.slice();
- a[0]=1;
- a[2][0]=1;
- console.log(a,b);
修改二维数组的元素还是会影响原数组, 也就是说 slice 和 concat 实际上是浅拷贝.
针对对象:
- Object.assign()
- var a={
- "name":"张三"
- };
- b=Object.assign({},a);
- b.name="李四";
- console.log(b.name);// 李四
- console.log(a.name);// 张三
同样该方法也是浅拷贝, 如果对象属性值是引用类型也不行;
那么到底有哪些办法可以实现深拷贝呢
1, 递归
- function deepClone(obj){
- let objClone = Array.isArray(obj)?[]:{};
- if(obj && typeof obj==="object"){
- for(key in obj){
- if(obj.hasOwnProperty(key)){
- // 判断 ojb 子元素是否为对象, 如果是, 递归复制
- if(obj[key]&&typeof obj[key] ==="object"){
- objClone[key] = deepClone(obj[key]);
- }else{
- // 如果不是, 简单复制
- objClone[key] = obj[key];
- }
- }
- }
- }
- return objClone;
- }
- let a=[1,2,3,4],
- b=deepClone(a);
- a[0]=2;
- console.log(a,b);
2,jQuery 中的 $.extend();
- var obj = {
- name:'xixi',age:20,company : {
- name : '腾讯', address : '深圳'
- }
- };
- var obj_extend = $.extend(true,{
- }, obj); //extend 方法, 第一个参数为 true, 为深拷贝, 为 false, 或者没有为浅拷贝.
- console.log(obj === obj_extend);
- obj.company.name = "ali";
- obj.name = "hei";
- console.log(obj);
- console.log(obj_extend);
3,JSON 对象的 JSON.parse() 和 JSON.stringify();
- var obj = {
- name:'xixi',age:20,company : {
- name : '腾讯', address : '深圳'
- }
- };
- var obj_json = JSON.parse(JSON.stringify(obj));
- console.log(obj === obj_json);
- obj.company.name = "ali";
- obj.name = "hei";
- console.log(obj);
- console.log(obj_json);
4,Lodash 中的_.cloneDeep()
- var objects = [{
- 'a': 1
- }, {
- 'b': 2
- }];
- var deep = _.cloneDeep(objects);
- console.log(deep[0] === objects[0]);
- // => false
虽然通过拷贝 props 数据解决了问题, 但是拷贝后修改新数据的属性并不会触发 vue 的更新机制, 需要强制更新 $forceUpdate(), 总觉得很奇怪, 不知道大家有什么更好的办法没有, 欢迎大家留言讨论.
参考文章:
- https://zhuanlan.zhihu.com/p/26282765
- https://zhuanlan.zhihu.com/p/26282765
来源: https://www.cnblogs.com/hutuzhu/p/10119698.html