从内存角度思考vue中父组件向子组件传递对象可以直接被修改的问题

前言

在vue中,父子组件传值借助于props,子组件也不能直接修改父组件的值,需要使用$emit实现。当父组件向子组件传递的数据是对象时,子组件就可以直接修改了。

1.浅析JavaScript内存空间

JS的内存空间可以被分为两种:栈内存和堆内存。

(1)基本数据类型保存在栈内存中,如果删除一个栈原始数据,遵循先进后出;因为基本数据类型占用空间小,大小固定,通过按值来访问,属于被频繁使用的数据。

基本数据类型包括Boolean,Number,String,Underfined,Null以及对象变量的指针。

(2)引用数据类型保存在堆内存中,并且会有一个十六进制的内存地址,在栈内存中声明的变量的值就是十六进制的内存地址。因为引用数据类型占据空间大,大小不固定。如果存储在栈中,将会影响程序运行的性能。引用数据类型在栈中存储了该对象在堆内存的引用地址对象的内容存储在堆中,你不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存的引用地址。

引用数据类型包括Object,Array,Function。

2.问题解决

(1)为什么子组件可以修改父组件传递的对象

联系到内存空间中数据存储的方式,当js访问对象数据的时候,只能通过访问栈中的引用地址,所以父组件传递给子组件的,实际上是对象的引用地址子组件修改对象的属性值时,修改的是堆空间中的数据,所以父组件中的数据也会变化。

该对象指向的内存地址并没有改变,所以子组件修改父组件的值时并不会报错。

(2)子组件为什么不能直接修改父组件中的数据;

因为Vue采用的是单向数据流,所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警

3.子组件修改父组件数据的几种方法

(1)$emit

  1. 在子组件中,需要$emit发送时间(.$emit(自定义事件名,参数,……))
  2. 父组件中,在子组件的标签上写监听,绑定它$emit的事件

(2)使用v-model

原理也是借助于对象存储在堆地址中。将父组件传递进来的数据写成对象的数据,然后再使用v-model修改data中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//子组件
<template>
<div>
<input type='text' v-model="data.msg" />
</div>
</template>
<script>
export default{
name:'son',
props:{
data:{
msg:String,
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//父组件
<template>
<div>
<son :data="data" />
</div>
</template>
<script>
import son from '@/components/son.vue'
export default{
name:'Father',
components:{
son
},
data(){
return{
data:{msg:''}
}
}
}
</script>

(3)watch和.sync修饰符来实现

其实还是利用watch和emit实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default{
name:“Son",
props:{
msg:String,
},
//将父组件的值存到自己组件中,避免直接修改导致报错
watch:{
msg(newVal) {
this.my Msg=newVal;
},
my Msg(newVal) {
this.$emit("update:msg”, newVal) ;
},
},
data() {
return{
// 通知父组件修改值
myMsg:''
};
},
1
2
3
4
5
<template>
<div>
<Son :msg.sync="msg" />
</Son>
</template>