最近开始尝试理解 vue 实现 mvvm 实时响应的原理,有一个地方不理解,抽离出来的代码模型如下:
let data={ name:"张三", add:'山东', } const keys=Object.keys(data) // 方案一 const obs=new function (obj){ keys.forEach((k)=>{ Object.defineProperty(this,k,{ get(){ return obj[k] }, set(val){ console.log(`${k}被改了,我要去解析模板.....`) obj[k]=val } }) }) }(data) // 方案二 // const obs ={} // keys.forEach((k)=>{ // Object.defineProperty(obs,k,{ // get(){ // return data[k] // }, // set(val){ // console.log(`${k}被改了,我要去解析模板.....`) // data[k]=val // } // }) // }) const vm={} vm._data=data=obs
其中,方案二会产生溢出我能理解,vm._data=data=obs 后vm._data 和 data 就指向了同一个对象,当读取该对象的一个属性时会调用其set,set中return又要重新读取这个属性无限循环导致溢出。
但是为什么方案一(源码中抽离出来的模型),就不会产生这个问题?它和方案二的区别不就是使用new 函数创建的对象吗?但是最后也赋值给 vm._data 和 data 了,为什么就可以避免溢出呢?
单看一次执行,这两个写法都是正确的;
但是如果第二次执行,data 会被修改掉
`
let data = {}
// 省略上诉代码
// 在初始化下一个组件时候
data = {
}
`
此时之前的defineProperty
在 set 时候,改的是新值。
这地方其实就是多了个函数立即执行,创造一个闭包而已。
方案一中,通过立即执行函数创建的观察者对象(obs)内部的obj变量指向的是全局的data对象。在执行完vm._data=data=obs后,data的指向发生了变化,指向了obs对象。此时,如果要访问原来data指向的对象,只能通过obs内部的obj来访问。
在方案二中,没有类似于方案一中的obj变量来引用原来data指向的对象。在执行完vm._data=data=obs后,就没有办法再访问到原来data指向的对象了。此时,对data进行修改或访问实际上是在访问新创建的obs对象,而obs对象内部又依赖于data,这就形成了一个循环引用的情况,导致溢出错误。
在方案二中可以创建一个变量来引用 data 原来指向的对象,以此来保留对其的访问和修改途径,如下:
// 方案二 const obs ={} keys.forEach((k)=>{ // 通过 timeObj 指向原对象的引用 let timeObj=data Object.defineProperty(obs,k,{ get(){ return timeObj[k] }, set(val){ console.log(`${k}被改了,我要去解析模板.....`) timeObj[k]=val } }) })