vue的响应式原理
1.vue内部如何监听数据的改变的?
Object.defineProperty 监听对象属性的改变
Object.keys(obj).forEach(key=>
{
let value = obj[key]
obj.defineProperty(obj,key,{
//当value值被修改的时候,执行set()方法
set(newValue) {
//可以监听到key的改变了,谁用?告诉谁
value=newValue
},
//当读取value值的时候,执行get()方法
get() {
// 可以获取到key对应的值了
return value
}
})
})
在vue实例中的data属性中保存的数据obj,传递给Observer观察者对象,在Observer中对data中所有的数据进行监听,首先通过Object.keys获取obj的所有key组成的数组,为每一个key创建一个dep对象, 再通过forEach遍历这些key,找到对应的value,再通过Object.defineProperty()监听value的改变
Object.defineProperty() - JavaScript | MDNObject.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
2.当数据发生改变,vue是如何通知哪些人需要改变的
解析HTML代码获取哪里在用这些数据(mastach{{}},v-model),每解析出一个地方使用,就创建一个Watcher实例并添加到对应的dep实例的subscribes数组中,subscribes数组中就保存了所有该数据的订阅者对象,在订阅者类中实现update()的方法,根据data中最新的数据更新视图。
如何实现?
发布者订阅者模式
class Dep {
constructor(){
//用来保存哪里使用了这个变量,记录所有订阅者
this.subscribes = []
}
addSub(watch){
this.subscribes.push(watch)
}
}
class Watcher{
constructor(node,name,vm){
this.node=node
this.name=name
this.vm=vm
update()
}
update(){
//更新界面
this.node.nodeValue = this.vm[name] //触发name的get()方法,
}
}
?当compiler解析到{{}},就new一个Watch的实例w1,w1保存了该node节点和使用变量name,并调用update()方法读取data[name]去赋值给node节点的nodeValue,当读取data[name]的时候,触发get()方法,调用dep.addSub()将w1添加到subscribes数组中,通过这种方式,所有使用数据的地方都会对应一个Watch实例,并且添加到subscribes数组中,当数据发送改变的时候,dep对象会调用notify()方法,在notify()方法中,会遍历subscribes数组,调用每一个元素的update方法,是使用到这个数据的所有地方都更新界面,达到响应式的效果
简易版源码:?
class Vue {
constructor(options){
// 1.保存数据
this.$options = options
this.$data = options.data
this.$el = options.el
// 2.将data通过Observer添加到响应式系统中
new Observer(this.$data)
// 3.代理this.$data的数据,可以在vue实例上直接添加数据,也可以通过vue实例直接读取数据
Object.keys(options.data).forEach(key=>{
this._proxy(key)
})
//4.处理el
new Compiler(this.$el,this)
}
//数据代理 读取vue实例数据的时候,实际上是从data中读取,给vue实例赋值的时候实际上给data赋值
_proxy(key){
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
set(value){
this.$data[key]=value
},
get(){
return this.$data[key]
}
})
}
}
class Observer {
constructor(data){
this.data = data
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}
function defineReactive(data,key,val) {
const dep = new Dep()
Object.defineProperty(data,key, {
configurable: true,
enumerable: true,
// 当数据被修改时
set(newValue) {
// val保存的是原来的数据 newValue是新的数据
if (newValue === val) {
return
}
val = newValue
dep.notify()
},
// 当数据被读取时
get() {
if(Dep.target){
dep.addSub(Dep.target)
}
return val
}
})
}
//发布者类
class Dep {
constructor() {
this.subscribes =[]
}
addSub(watcher){
this.subscribes.push(watcher)
}
notify(){
this.subscribes.forEach(item=>{
item.update()
})
}
}
//订阅者类
class Watcher {
constructor(node,name,vm) {
this.node = node
this.name = name
this.vm = vm
Dep.target = this
this.update()
// 这里必须要清空是因为:当数据修改之后,会通过dep.notify重新调用watcher的update,对数据进行读取,会再次调用数据的get()方法
// 为了避免重复添加watcher,所以调用完update之后就把Dep.target清空
Dep.target = null
}
update(){
//数据改变之后,更新视图
this.node.nodeValue = this.vm[this.name] //数据劫持
// 这里读取了vue实例的data中的数据,会触发对应属性的get()方法
}
}
const reg = /\{\{(.*)\}\}/ //匹配{{}}
class Compiler {
constructor(el,vm) {
this.el = document.querySelector(el)
this.vm = vm
this.frag = this._createFragment()
this.el.appendChild(this.frag)
}
_createFragment(){
const frag = document.createDocumentFragment()
let child;
while (child = this.el.firstChild){
this._compile(child) //child node节点
frag.appendChild(child)
}
return frag
}
_compile(node){
// 标签节点
if(node.nodeType === 1 ){
const attrs = node.attributes
if(attrs.hasOwnProperty('v-model')){
const name = attr['v-model'].nodeValue
node.addEventListener('input',e=>{
this.vm[name] = e.target.value
})
}
// 文本节点
}
if (node.type==3){
console.log((reg.test(node.nodeValue)))
if(reg.test(node.nodeValue)){
const name = RegExp.$1.trim() ///\{\{(.*)\}\} $s1是(./)中的内容
console.log(name)
//解析HTML 解析到{{}}
// node:包含{{}}的node节点
// name:{{name}}data中的变量名
// vm: vue实例
new Watcher(node,name,this.vm)
}
}
}
}
|