let arr=[1,2,3] arr[0]=10没有效果
这种情况,是因为Object.definePropoty监视不了数组的索引
let arr2 = [{name:"张三"}] arr2[0].name = "李四"有效果
这种情况是因为这里arr2[0]拿到的是{name:"张三"}这个对象,Object.defineProperty本身只能监视「已存在的属性」的「读取 / 修改」行为,这里对象的name属性已经存在,对name属性修改可以
let arr3 = [{name:"张三"}] arr3[0] = {name:"李四"}没有效果[视图不更新]
这个操作的本质是修改数组的索引 0 对应的「值」(把原来的对象替换成新对象),而非修改原有对象的属性。
就像arr[0]=10一样,数组索引的修改无法被Object.defineProperty监听,因此不会触发视图更新。
总结:vue2的响应式实现就是通过Object.definePropoty,Object.definePropoty监视不了对象属性的新增和删除,也处理不了数组索引,所以为啥对象新增和删除属性不会触发视图更新,数组通过索引修改,也不会触发视图更新
2关于$set解决Vue2响应式丢失
// 给对象添加属性 this.$set(目标对象, 要新增/修改的属性名, 属性值) // 给数组修改元素 this.$set(目标数组, 数组索引, 新值)在$set里,它的作用就是给原本没有监听的新属性(如age)补上这两个开关,让 Vue 能 “看到” 这个属性的读写操作。
2. dep.notify ():触发 “更新通知”
dep是依赖收集器(Dep 实例),里面存着所有用到这个属性的视图 /watch 对应的 Watcher 实例;dep.notify()就是dep的 “通知方法”:
在$set里,它的作用有两个:
补全响应式劫持:Vue2 初始化时只给
data里已有的属性加getter/setter(用来监听读写),新增的属性 / 数组索引修改不会自动加。$set会手动调用Object.defineProperty(对象场景)或重写的splice方法(数组场景),给新内容补上这个 “监听开关”。关联依赖更新:光有监听还不够,
$set会把新内容的监听逻辑,挂靠到目标对象 / 数组的__ob__.dep(依赖收集器)上。这样修改新内容时,能触发dep.notify()通知视图更新,相当于把新内容 “拉进” 了 Vue 的响应式体系里。主动触发一次更新:最后
$set会主动调用dep.notify(),确保新增的内容能立刻在视图上显示,不用等下一次更新周期。1. Object.defineProperty:给属性装 “监听开关”
它是 Vue2 实现响应式的底层核心 API,作用就是手动给对象的属性绑定
getter(读监听)和setter(写监听):- getter(读开关):当页面读取这个属性(比如
<p>{{user.age}}</p>)时触发,核心是把 “当前用到这个属性的视图 /watch” 加入dep(依赖收集); - setter(写开关):当你修改这个属性(比如
user.age = 25)时触发,核心是调用dep.notify()通知依赖更新。 - 调用它时,
dep会遍历自己存储的所有 Watcher 实例,挨个告诉它们 “你依赖的属性变了,赶紧更新”; - 新属性的
setter里调用:属性修改时自动触发更新; $set最后主动调用一次:确保新增属性能立刻显示在视图上。- 收到通知的 Watcher 会执行对应的逻辑:比如视图 Watcher 会刷新页面,watch Watcher 会执行你写的回调函数。