vue双向绑定(手写)

231 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

此为简易版本

vue

        class Vue{
            constructor(options){
                // 保存数据
                this.$options = options
                this.$data = options.data
                this.$el = options.el
                // 将data添加到响应式系统中
                new Observer(this.$data)
                // 代理this.$data的数据
                Object.keys(this.$data).forEach(key=>{
                    this._proxy(key)
                })
                // 处理el
                new Compiler(this.$el,this)
            }
            _proxy(key){
                Object.defineProperty(this,key,{
                    configurable:true,
                    enumerable:true,
                    set(newValue){
                        this.$data[key]=newValue
                    },
                    get(){
                        return this.$data[key]
                    }
                })
            }
        }

observer

首先双向绑定分为两条线 一条observer 一条 compile

observer

        class Observer{
           constructor(data){
               this.data=data
               Object.keys(data).forEach(key=>{
                   this.defineReactive(this.data,key,data[key])
               })
           }
           defineReactive(data,key,val){
               const dep = new Dep() // [watcher,watcher]
               Object.defineProperty(data,key,{
                   enumerable:true,
                   configurable:true,
                   get(){
                       if(Dep.target){ //Dep.target指向Watcher的this 
                           dep.addSub(Dep.target)
                       }
                       return val
                   },
                   set(newValue){
                       if(newValue === val){
                           return
                       }
                       val=newValue
                       dep.notify()
                   }
               })
           }
       }

observer 其中的原理是通过 遍历data中的属性 然后利用 object.defineProperty中的setter 和getter 的方法进行监听 当数据被调用的时候会触发get 当数据被修改的时候会触发set 然后利用开发者订阅者模式 给每个对象一对一的生成一个dep对象管理数据依赖 然后把watcher添加到dep中

Dep

 class Dep{
            constructor(){
                this.subs=[]
            }
            addSub(sub){
                this.subs.push(sub)
            }
            notify(){
             this.subs.forEach(sub =>{
                 sub.update()
             })
            }
        }

watcher

        class Watcher{
           constructor(node,name,vm){
               this.node = node
               this.name = name
               this.vm =  vm
               Dep.target = this
               this.update()
               Dep.target = null
           }

           update(){
               this.node.nodeValue = this.vm[this.name] // 触发getter获取数据
           }
       }

compile

compile 是解析模板中的变量替换成数据 会触发getter获取数据,Dep.notify 给遍历后每个对象赋予update方法,当数据发送改变时会触发Dep.notify 通知watcher调用update方法更新视图

      const reg = /\{\{(.+)\}\}/     //正则匹配节点

      class Compiler {  //el:this.$el, vm:  this
          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() // 创建代码片段  操作dom树一次一次地添加会导致dom树多次重排重绘
              // 创建代码片段后 先把要添加的 添加到该片段 后面由该片段统一添加到dom树上 只进行了一次重排重绘
              let child 
              while (child = this.el.firstChild) {
                  this._compile(child) // 解析节点
                  frag.appendChild(child)
              }
              return frag
          }
          _compile(node){// 解析节点   node节点
              if(node.nodeType === 1){ //标签节点
                 const attrs = node.attributes
                 if(attrs.hasOwnProperty('v-model')){
                     const name = attrs['v-model'].nodeValue
                     node.addEventListener('input',e=>{
                         this.vm[name] = e.target.value
                     })
                 }
              }
              if(node.nodeType === 3){ //文本节点
                   console.log(reg.test(node.nodeValue));
                   if(reg.test(node.nodeValue)){
                       const name = RegExp.$1.trim()
                       new Watcher(node,name,this.vm) 
                   }
              }
          }
      }

补充: 它会先 判断 节点是否为 标签节点还是文本节点 当节点的nodeType等于1 的时候 为 标签节点 当节点的nodeType 等于3的时候 为文本节点 当为文本节点的时候 会根据 正则去判断所写的内容