vue 实现数据劫持 通过 new 避免溢出的原理是什么?

最近开始尝试理解 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 了,为什么就可以避免溢出呢?


回答:

方案一中,通过立即执行函数创建的观察者对象(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

}

})

})


回答:

方案二可以改成这样

        let data={

name:"张三",

add:'山东',

}

// 方案二

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=obs

这样不会溢出
会溢出的原因是 obs 和 data 是同一个引用,因此 data[k] 执行时 等价于 obs[k]执行会导致 obs.get()执行 然后 get 又调用了 data[k],所以问题的关键是只要让 data上不挂载 get /set 方法

关键点再于
data = obs 这一行代码

data = obs 看着很正常对吧 ?

但是 我们要去讨论在 obs.name 的时候 defineProperty get 中的data 此时是否还是第一行的data呢?还是已经是obs了呢?很显然是data已经不是最初的data了他已经是obs了所以才会溢出。

那我们看方案一为什么不会溢出

开始之前我们把方案二再微调一下

const obs ={}

const tmpData = data

keys.forEach((k)=>{

Object.defineProperty(obs,k,{

get(){

return tmpData[k]

},

set(val){

console.log(`${k}被改了,我要去解析模板.....`)

tmpData[k]=val

}

})

})

const vm={}

vm._data=data=obs

我们增加两个一个tmpData 作为中转 但是 obs.name就不溢出了,为什么会这样呢?

因为 data 虽然已经变成了obs 但 tmpData并为受影响它还是最初的data。

有点罗嗦了,回到问题,为什么一没有溢出

在来一个例子

function warp(t){

return ()=> console.log(t)

}

let t = 1

let g = warp(t)

t = 2

console.log(t, g())

问会打印什么呢?

会打印 2 2 吗?
还是打印2 1?
为什么会打印 2 1呢?

这又要说到方法调用是传的引用拷贝的值还是引用本身

再次回到方案一 new xx 方法执行后 data 的引用被复值给了obj 对obj.name 的修改会影响 data.name ,但如果对obj赋值并不影响data ,对data赋值也不会影响obj。

因此方案一 defineProperty get中的data[k] 还是最初的data ,方案二中的data已经不是最初的data了。

完结...

补充 参考变量 值 引用 等内容 或 图片等方便理解。


回答:

单看一次执行,这两个写法都是正确的;
但是如果第二次执行,data 会被修改掉
`
let data = {}

// 省略上诉代码

// 在初始化下一个组件时候
data = {
}

`

此时之前的defineProperty 在 set 时候,改的是新值。
这地方其实就是多了个函数立即执行,创造一个闭包而已。

以上是 vue 实现数据劫持 通过 new 避免溢出的原理是什么? 的全部内容, 来源链接: utcz.com/p/934513.html

回到顶部