# 注意对象是否已经被侦测

# 采坑回顾

项目中定义了一份配置文件作为两个组件data的初始值

// config.js
export default {
  a: {
    aa: 1,
  },
};

然后分别在两个组件First,Two中引用:

// First
<template>
  <div>First {{ first }}</div>
</template>

<script>
import config from '@/config';
export default {
  data() {
    return {
      first: {
        config,
      },
    };
  },
};
</script>
// Two
<template>
  <div>
    <div @click="handleClick">Two {{ two }}</div>
  </div>
</template>

<script>
import config from '@/config';
export default {
  data() {
    return {
      two: {},
    };
  },
  methods: {
    handleClick() {
      this.two.config.a.bb = 3;
    },
  },
  mounted() {
    config.a.bb = 2;
    this.$set(this.two, 'config', config);
  },
};
</script>

我们在Two这个组件中对引入的config添加了一个属性,打算通过$set统一设置响应式

我们将两个组件引用到Home组件中

<template>
  <div class="home">
    <First></First>
    <Two></Two>
  </div>
</template>

<script>
import First from '@/components/First';
import Two from '@/components/Two';
export default {
  components: { First, Two },
};
</script>

此时我们clickTwo组件时数据没有发生变化,查看具体的data发现后添加的bb没有被纳入响应式

image-20210117145943146

我们就觉得奇怪,按理说this.$set(this.two, 'config', config);应该会递归将所有属性都变成响应式的getter和setter才对,为什么没有生效呢?

后来发现,原来在First组件中,config对象已经被observe过一次了,于是在Two组件中就不会被再次observe,所以实际上我们的this.$set没有生效。

# 核心知识

observe方法中会判断要设置的对象是否存在__ob__,如果存在则说明已经被纳入侦测,就直接返回__ob__。这实际上是为了防止重复侦测,提高效率,但是我们在使用的时候也要注意预防这一点带来的坑。

export function observe (value: any, asRootData: ?boolean): Observer | void {
 	...
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } ...
  return ob
}

# 解决方案

克隆目标对象。

不论是prop还是模块,我们最好都以只读形式对待,这样既可以保证数据的单向流动性,又能做到互相隔离。

上次更新: 2/13/2025, 3:29:47 AM