https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDMQ%2Fmvvm
1.数据代理
(1)前言:数据代理就是通过vm对象来代理data对象中所有属性的操作。例如我们要访问vue中data的数据应该是vm.data.msg,访问vue中的方法应该是vm.methods.getMsg(),但是实际上我们直接使用vm.msg或者vm.getMsg()来访问data或者methods中的数据。这就是数据代理
(2)实现原理:遍历要代理对象的所有key,通过Object.defineProperty为vm对象添加同名的key属性,设置get方法和set方法来动态获取或者修改data对象的属性值
<script>
function MVVM (option) {
//保存传入的配置
this.$option = option
//保存data对象
var data = this._data = this.$option.data
//遍历data中的所有key
Object.keys(data).forEach(key => {
//为vm添加与key相同属性,且配置相应的属性描述符
Object.defineProperty(this,key,{
configurable: false,//可重新定义
enumerable: true,//可枚举
get () {
return this._data[key]
},
set (newVlue) {
this._data[key] = newVlue
}
})
})
}
//实例化
var vm = new MVVM({
data: {
msg:'Hello World'
}
})
//vm.msg相当于调用get方法返回this._data[key]
console.log(vm.msg) //Hello World
//给vm.msg赋值相当于调用set方法,更新this._data[key]的值
vm.msg = "msg is change"
console.log(vm.msg) //msg is change
</script>
2.数据劫持
数据劫持通俗的说就是监听属性的变化,然后触发相应的回调函数。例如vue模版中的数据发生了变化,页面也随之响应,他的实现原理就是对数据进行监听,一旦发生变化,则在回调函数中更新相应的dom。
为对象添加属性的方法有3种,我们前面已经学过2种:
//初始化时添加属性
var person = {name:"谢耳朵"}
//点语法添加属性
person.age = 25
console.log(person) //{name: "谢耳朵", age: 25}
这2种方法添加的属性,无论是获取属性值还是修改属性值,操作的对象都是自身。而使用第3种方法添加属性时,可以为属性设置其他的配置,包括get方法和set方法。
语法:Object.defineProperty(obj, key,options)
数据劫持的实现原理:
(1)数据代理是不能操作自身,因为调用get方法时要访问自身的值,而访问自身又会触发get方法,从而造成死循环。给属性赋值时也是一样的,set方法把新的值赋值给自己,会触发自身的set方法,也会造成死循环
(2)既然不能操作自身,那就需要为每个属性配置的一个独立的变量。无论是get方法还是set方法都是操作这个独立的变量,从而避免死循环。
(3)解决办法:为Object.defineProperty包裹一层函数,将属性值当作参数val传入。而这个val就是前面所说的独立变量,当访问get方法时可以return val。当触发set方法时,则修改val的值
<script>
//这个例子没有深度劫持
function proxyData(obj,key,val){
//val作为一个中间变量,负责存储这个属性的值
Object.defineProperty(obj, key, {
configurable:false,
enumerable:true,
get(){
//get方法返回这个val
console.log('通过get方法获取了'+key+'属性的值')
return val
},
set(newVal){
//set方法修改这个val
if(newVal === val){
return
}
console.log(key+'属性的值发生了变化')
console.log('可以在这个回调函数中做其他事件,例如更新页面dom等')
val = newVal
}
})
}
var p = {name: 'kyo', age: 20}
//调用proxyData()进行数据劫持
Object.keys(p).forEach((key) => {
var val = p[key]
proxyData(p,key,val)
})
//获取属性值
var name = p.name
//修改属性值
p.age = 27
</script>
控制台打印:
通过get方法获取了name属性的值
age属性的值发生了变化
可以在这个回调函数中做其他事件,例如更新页面dom等
上面的方法只代理了对象的第一层属性,如果对象中包含多层对象,需要对每层次的属性都进行数据劫持的时候,就需要用到递归调用。
思路:对所有的属性进行数据劫持,如果他的属性值类型是object,则对这个属性值进行递归调用
<script>
//利用递归调用深度劫持
function proxyData(obj,key,val){
//val作为一个中间变量,负责存储这个属性的值
Object.defineProperty(obj, key, {
configurable:false,
enumerable:true,
get(){
//get方法返回这个val
console.log('通过get方法获取了'+key+'属性的值')
return val
},
set(newVal){
//set方法修改这个val
if(newVal === val){
return
}
console.log(key+'属性的值发生了变化')
console.log('可以在这个回调函数中做其他事件,例如更新页面dom等')
val = newVal
}
})
//判断属性值是否为object,如果是则对这个值进行递归调用
if(val instanceof Object){
Object.keys(val).forEach(key => {
proxyData(val,key,val[key])
})
}
}
var data = {
name: 'kyo',
age: 20,
skill: ['大蛇薙', '最终决战奥义“无式'],
father: {
name: '草薙柴舟',
age: 50
}
}
Object.keys(data).forEach((key) => {
var val = data[key]
proxyData(data,key,val)
})
//读取属性值
var fatherName = data.father.name
//修改属性值
data.father.age = 52
</script>
控制台打印:
//data.father.name
通过get方法获取了father属性的值
通过get方法获取了name属性的值
//data.father.age = 52
通过get方法获取了father属性的值
age属性的值发生了变化
可以在这个回调函数中做其他事件,例如更新页面dom等