模拟Vue.js响应式原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3wM6g5Bk-1652351202807)(模拟Vue.js响应式原理/image-20220505104638376.png)]

数据响应式的核心原理

vue 2.x

shim降级处理

vue 2.x 基于 Object.defineProperty()实现的 所以不支持IE8以及更低的版本

  • 如果有一个对象中多个属性需要转换 getter/setter 如何处理?

vue 3.x

基于proxy实现响应式原理

proxy直接监听对象,而非属性,因此不需要直接遍历对象

发布订阅模式

学生家长,学生每次考完试都要获取孩子的成绩,考完试催要成绩,到班级订阅孩子的信息,成绩出来由老师以短信的形式发送给孩子的家长,不需要再催要成绩

家长 订阅者

老师 发布者

班级 事件中心

事件中心将发布者和订阅者隔离开来,使用更灵活,

观察者模式

没有事件中心,只有发布者和订阅者,发布者需要知道订阅者的存在

订阅者(又叫观察者)

发布者和订阅者之间存在相互依赖的关系

Vue响应式原理模拟

class Vue {
  constructor (options) {
    // 1. 通过属性保存选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2. 把data中的成员转换成getter和setter,注入到vue实例中
    this._proxyData(this.$data)
   	// 3. 调用Observer对象,监听数据的变化
    // 4. 调用compiler对象,解析指令和插值表达式
  }
  _proxyData (data) {
    // 遍历data中的所有属性
    Object.keys(data).forEach(key => {
      // 把data的属性注入到vue实例中
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get () {
          return data[key]
        },
        set (newValue) {
          if (newValue === data[key]) {
            return
          }
          data[key] = newValue
        }
      })
    })
  }
}

Observer

数据劫持,监听data中的数据变化,将data中的所有属性转换为geter和seter

class Observer {
  constructor (data) {
    this.walk(data)
  }
  walk (data) {
    // 1. 判断data是否是对象
    if (!data || typeof data !== 'object') {
      return
    }
    // 2. 遍历data对象的所有属性
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }
  defineReactive (obj, key, val) { // 转换data的所有属性为geterheseter
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get () {
        return val
      },
      set (newValue) {
        if (newValue === val) {
          return
        }
        val = newValue
        // 发送通知
      }
    })
  }
}

vue

class Vue {
  constructor (options) {
      ...
   	// 3. 调用Observer对象,监听数据的变化
     new Observer(this.$data)
    // 4. 调用compiler对象,解析指令和插值表达式
  }
  ...
}

Observer-defineReactive1

为什么defineReactive1中要传第三个值,再get方法中调用obj[key]时会触发get方法,发生死递归,

defineReactive中的obj为vue. d a t a , data, datadata对象引用leget方法,所以get方法是一个闭包扩展了(引用着)val的作用域

Observer-defineReactive2

data对象属性的响应式实现

  class Observer {
  ...
  defineReactive (obj, key, val) { // 转换data的所有属性为geterheseter
    // 如果val是对象,把val内部的属性转换成响应式数据
    this.walk(val)
	...
  }
}

新赋值的属性是一个对象,将其转换为响应式的

class Observer {
  ...
  defineReactive (obj, key, val) { // 转换data的所有属性为geterheseter
      ...
      let that = this
      Object.defineProperty(obj, key, {
	 	...
      	set (newValue) {
			...
        	that.walk(val)
      	}
    })
  }
}

Compiler

class Compiler {
  constructor (vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // 编译模板,处理文本节点和元素节点
  compile (el) {
      
  }
  // 编译元素节点,处理指令
  compileElement (node) {
      
  }

  // 编译文本节点,处理差值表达式
  compileText (node) {
      
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {
    return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {
    return node.nodeType === 3
  }
  // 判断节点是否是元素节点
  isElementNode (node) {
    return node.nodeType === 1
  }
}

Compiler-compile方法的实现

compile(el){
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node=>{
        // 处理文本节点
        if(this.isTextNode(node)){
            this.compileText(node)
        // 处理元素节点
        }else if(this.isElementNode(node)){
            this.compileElement(node)
        }
        // 递归调用compile,处理子节点
        if(node.childNodes&&node.childNodes.length){
            this.compile(node)
        }
    })
}

Compiler-compileText的方法实现

处理文本节点,差值表达式

class Compiler{
	constructor(vm){
        ...
        this.compiler(this.el)
    }
    ...
    compileText(node){
		// console.dir(node)// 将变量以对象的形式打印
        let reg = /\{\{(.+?)\}\}/
        let value = node.textContent  // 获取文本节点的内容
        if(reg.test(value)){
            let key = RegExp.$1.trim() // 获取到正则表达式第一个分组的内容
            node.textContent = value.replace(reg,this.vm[key]) // 替换文本节点的值
        }
	}
	...
}


vue

class Vue{
    constructor(options){
       ...
       // 4调用compiler,解析指令和差值表达式
       new Compiler(this)
    }
}

Compiler-compileElement

处理属性节点,处理指令

class Compiler{
    ...
    compilerElement(node){
        // 遍历所有的属性节点
        Array.from(node.attributes).forEach(attr=>{
            let attrName = attr.name
            if(this.isDirective(attrName)){
                  // v-test --> text
                attrName = atttrName.substr(-2)
                let key = attr.value
                this.update(node,key,attrName)
            }
        })
    }
	update(node,key,attrName){ // 执行指令的方法
        let updateFn = this[attrName+'updater']
        updateFn && updateFn(node,this.vm[key])// thi
    }
	//  处理v-text 指令
	textUpdater(node,value){
        node.textContent = value
    }
	// v-model
	modelUpdater(node,value){
        node.value = value
    }
}

Dep

class Dep {
    constructop(){
        // 存储所有的观察者
        this.subs = []
    }
    // 添加观察者
    addSub(sub){
        if(sub && sub.update){
            this.subs.push(sub)
        }
    }
    // 发送通知
    notify(){
        this.subs.forEach(sub=>{
            sub.update()
        })
    }
}

observer.js

class observe {
    ...
    defineReactive(obj.key.val){
        let that = this
        let dep = new Dep()
        ...
        get(){
            // 收集依赖
            Dep.target && dep.addSub(Dep.target)
            return val
        }
        set(newValue){
            ...
            // 发送通知
            dep.notify()
        }
    }
}

Watcher

class Watcher {
    constructor(vm,key,cb){
        this.vm = vm
        this.key = key
        this.cb = cb
        // 把wtcher对象记录到Dep类的静态属性target
        Dep.target = this
        //触发get方法,在get方法中会调用addSub
        this.oldValue = vm[key]
        Dep.target = null
    }
    // 当数据发生改变的时更新视图
    update(){
        let newValue = this.vm[this.key]
        if(this.oldValue === newValue){
            return
        }
        this.cb(newValue)
    }
}

创建watcher对象

compiler.js

class compiler{
    ...
    update(node,key,attrName){
        ...
        updateFn && updateFn.call(this,node,this.vm[key],key) // call修正this指向
    }
    
    textUpdater(node,value,key){
        ...
        new Watcher(this.vm,key,(newValue)={ // 调用时需要修正this指向
            node.textContent = newValue
        })
    }
    compileText(node){
        ...
        // 创建Watcher对象,当数据改变更新视图
        new Watcher(this.vm,key,(newValue)=>{
            node.textContent = newValue
        })
    }
	modelUpdater(node,value,key){
        ...
        new Watcher(this.vm,key,(newValue)=>{
            node.value = newValue
        })
        // 双向绑定
        node.addEventListener('input',()=>{
            this.vm[key]=node.value
        })
    }
}

// 创建Watcher对象,当数据改变更新视图
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
modelUpdater(node,value,key){

new Watcher(this.vm,key,(newValue)=>{
node.value = newValue
})
// 双向绑定
node.addEventListener(‘input’,()=>{
this.vm[key]=node.value
})
}
}








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值