关键词: 数据劫持、发布者-订阅者模式、Observe(监听器)、Dep(消息订阅器)、Watcher(订阅者)、Compile(解析器)、Object.defineProperty、Proxy、Reflect
vue的双向绑定原理就是通过数据劫持+发布者-订阅者模式实现的,可以借助下图参照
首先在初始化时,vue就会监听data,就是上图的Observe(监听器),监听所有属性;
如果属性发生变化就会通知Watcher(订阅者);
但是订阅者会有若干个,所以就引申出了 Dep(消息订阅器) 这个概念,用来收集所有的watcher,将所有watcher进行统一管理;
Compile(指令解析器) 对每一个节点元素进行扫描解析,将相关的指令(v-model, v-on…)初始化成一个 Watcher ,并替换模板数据或是相应的更新函数;
整体流程:
- 先创建一个Observe,劫持监听所有的属性,Dep 会收集所有watcher,当observe发现改动时就通知Dep,再由Dep通知Watcher;
- 每个watcher都绑定一个更新函数,watcher可以收到更新的内容,并执行相关更新函数;
- 实现一个解析器Compile,可以扫描和解析每个节点的相关指令,则Compile初始化这类节点的模板数据(使其显示在视图上),以及初始化相应的订阅者。
数据劫持
- vue2 - Object.defineProperty
vue2的数据劫持是使用Object.defineProperty(obj, prop, descriptor)方法实现的,方法一共有三个参数, 分别是劫持的对象(obj),监听对象的某个属性就是对象的某个key(descriptor),改变方法(descriptor)
let student = { name: '张三' }
Object.defineProperty(student, 'name', {
get: function () {
console.log('get')
return 1
},
set: function (newValue) {
console.log('set', newValue)
}
})
student.name // get
student.name = 2 // set 2
console.log('student.name', student.name) // get 1
上面是一个简单的例子,简单来对上文的数据劫持使用的defineProperty进行讲解
1) 当执行student.name语句的时候,就是一个get的操作,因此执行了我们定义的get方法,输出了get;
2)当我们想要给student的name赋值,此时就执行了定义的set方法;
3) 此时我们打印student.name 可以发现输出的不是定义student时的张三也不是重新赋值的1,这是因为我们在get方法里定写了 return 1, 因此无论进行什么操作都只能拿到1。
2 vue3 - Proxy
let student = {
name: '张三',
age: 12
}
const handle = {
get: (target, prop) => {
console.log('get')
return target[prop]
},
set: (target, prop, newValue) => {
console.log('set')
target[prop] = newValue
}
}
function reactive(target, handle) {
return new Proxy(target, handle)
}
let student1 = reactive(student, handle)
console.log(student1) // 返回一个Proxy实例
student1.sex = 1 // set
student.name = '李四' // set
console.log(student1.name) // name: 李四 sex: 1
Proxy的相关内容在这里就不具体讲述了,通过上例,细心的读者应该可以发现,Object.defineProperty每次只能劫持对象的一个属性。而Proxy是直接劫持了整个对象。所以前者还需要对对象进行遍历,显然使用proxy的性能更好
数据劫持总结 通过上例,小编把数据劫持的这个模式,理解为劫持的 对象/对象属性 是 ‘艺人’, 使用数据劫持后相当于艺人找到了一个公关团队,所有外部想要看要 ‘艺人’ 的 形象(对象的值),都是会在‘公关团队’(自定义方法)下进行‘包装’(处理)后展示在大众面前。
以上仅是小编的个人理解,如有问题欢迎大家指出