Vue原理解析之响应式原理

Vue响应式原理

一、简述

响应式原理即数据变化的同时改变界面,Vue2.x通过Object.defineProperty()的getter和setter函数结合观察者模式实现,Vue3.x通过ES6的proxy代理劫持对象实现。

二、Object.defineProperty(obj, key, {})

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。其中get()方法为获取属性值,set()方法为设置属性值。

// vue通过此方法对data的每个属性进行数据劫持
function convert (obj) {
    Object.keys(obj).forEach(key) {
        let internalValue = obj[key]
        Object.defineProperty(obj, key, {
            get() {
                console.log('getter log' + internalValue);
                return internalValue
            },
            set(newValue) {
                internalValue = newValue
                console.log('setter log' + internalValue);
            }
        })
    }
}

三、观察者模式

什么是观察者模式?它分为注册环节跟发布环节

比如我去买芝士蛋糕,但是店家还没有做出来。这时候我又不想在店外面傻傻等,我就需要隔一段时间来回来问问蛋糕做好没,对于我来说是很麻烦的事情,说不定我就懒得买了。

店家肯定想要做生意,不想流失我这个吃货客户。于是,在蛋糕没有做好的这段时间,有客户来,他们就让客户把自己的电话留下,这就是观察者模式中的注册环节。然后蛋糕做好之后,一次性通知所有记录了的客户,这就是观察者的发布环节

// 观察者类
class Dep {
    constructor() {
        this.subscribers = new Set()
    }
    depend(activeUpdate) {
        if (activeUpdate) {
            this.subscribers.add(activeUpdate)
        }
    }
    notify() {
        this.subscribers.forEach(subscriber => subscriber())
    }
}

四、原理解析

Vue官网响应式原理图解:
在这里插入图片描述

1.组件初始化时,将data属性reactive化,即加上getter/setter函数,getter注册函数,setter通知函数,这个过程称为数据劫持

2.在每个组件compile时,都会创建一个Watcher对象,在Watcher对象里面会触发属性的getter函数收集依赖,即收集update函数

3.当数据发生变化时,触发setter函数,调用dep.notify()函数通知Watcher更新相关组件。

ps:一个组件对应一个Watcher

class Dep {
    constructor() {
        this.deps = new Set()
    }
    depend() {
        this.deps.add(Dep.target)
    }
    notify() {
        this.deps.forEach(dep => dep.update())
    }
}

class Watcher {
    constructor(vm, key, cb) {
        this.vm = vm // vue实例
        this.key = key // data的key
        this.cb = cb // 数据更新回调函数
        Dep.target = this // 将Dep.target指向this,用于跟Dep类通信
        this.vm[this.key] // 触发get函数收集依赖
        Dep.target = null
    }
    update() {
        this.cb.call(this.vm, this.vm[this.key]) // 执行更新界面方法
    }
}

class Compile {
    constructor(el, vm) {
        this.$vm = vm
        this.$el = el
        if (this.$el) {
            this.$fragment = this.node2Fragment(this.$el)
            this.compileElement(this.$fragment)
            this.$el.appendChild(this.$fragment)
        }
    }
    node2Fragment(el) {
        let fragment = document.createDocumentFragment()
        let child
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    }
    compileElement() {
        // 编译元素代码
        let childNodes = el.childNodes
    Array.from(childNodes).forEach((node)=> {
      let text = node.textContent
      // 表达式文本
      // 识别{{}}中的数据
      let reg = /\{\{(.*\}\})/
      // 按元素节点类型编译
      if (this.isElementNode(node)){
        this.compile(node)
      }else if(this.isTextNode(node) && reg.test(text)){
        // 文本 并且有个{{}}
        this.compileText(node, RegExp.$1)
      }
      // 遍历编译子节点
      if (node.childNodes && node.childNodes.length){
        this.compileElement(node)
      }
    })
    }
    compile(node) {
    let nodeAttrs = node.attributes
    Array.from(nodeAttrs).forEach((attr) => {
      // 指令以v-xxx 命名
      // 如<span v-text = "content"></span>
      let attrName = attr.name // v-text
      let exp = attr.value // content
      if (this.isDirective(attrName)) {
        let dir = attrName.substring(2) // text
        // 普通指令
        this[dir] && this[dir](node, this.vm, exp)
      }
      if (this.isEventDirective(attrName)) {
        let dir = attrName.substring(1) // text
        this.eventHandle(node, this.vm, exp, dir)
      }
    })
  }
  compileText(node, exp){
    this.text(node, this.vm, exp)
  }
  isDirective(attr) {
    return attr.indexOf("v-") == 0
  }
  isEventDirective(dir){
    return dir.indexOf('@') === 0
  }
  isElementNode(node) {
    return node.nodeType == 1
  }
  isTextNode(node) {
      return node.nodeType == 3
    }
  text(node, vm, exp) {
    this.update(node, vm, exp, 'text')  
  }
  html(node, vm, exp) {
    this.update(node, vm, exp, 'html')  
  }
  model(node, vm, exp) {
    this.update(node, vm, exp, 'model')
    let val = vm.exp
    node.addEventListener("input", (e)=>{
      let newValue = e.target.value
      vm[exp] = newValue
      val = newValue
    })
  }
    update(node, vm, key, dir) {
        let updater = this[dir + 'Updater']
        updater && updater(node, vm, vm[key]) // 执行更新界面方法
        new Watcher(vm, key, function(value) {
            updater && updater(node, value) // 保存更新界面方法
        })
    }
    eventHandler(node, vm, exp, dir){
    let fn = vm.$options.methods && vm.$options.methods[exp]
    if (dir && fn) node.addEvent(dir, fn.bind(vm), false)
   }  
   textUpdater(node, value){
     node.textContent = value
   }
   htmlUpdater(node, value) {
    node.innerHTML = value
   }
   modelUpdater(node, value){
     node.value = value
   }
}

function isObject(obj) {
    return typeof obj === 'object' && !Array.isArray(obj) && obj !== null && obj!== undefined
}

function observe(obj) {
    if (!isObject(obj)) {
        throw new TypeError()
    }
    Object.keys(obj).forEach(key => {
        const dep = new Dep()
        let internalValue = obj[key]
        if (isObject(internalValue)) observe(internalValue)
        Object.defineProperty(obj, key, {
            get () {
                dep.depend(Dep.target)
                return internalValue
            },
            set(newValue) {
                const isChange = internalValue !== newValue
                if (isChange) {
                    internalValue = newValue
                    dep.notify()
                }
            }
        })
    })
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值