模式实现一个小版本vue

212 阅读3分钟

整体结构分析

image.png

vue类实现

需要实现的功能

  • 负责接受初始化参数options
  • 负责把data中的属性输入到vue实例,并且转换成getter/setter
  • 负责调用observer监听data所有属性的变化
  • 调用compiler 解析指定/差值表达式
    /* 结构
       名称 Vue
       属性 $option $data $el
       方法 _proxyDada
    */ 
    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对象监听数据变化
            new Oberver(this.$data)
            // 4 通过compiler对象解析对应的指令和差值表达式
            new Compiler(this)
        }
        _proxyData (data) {
            // 遍历data所有属性
            Object.keys(data).forEach(key=>{
                // 把data的属性注入到vue实例上
                Object.defineProperty(this,key,{
                     configurable:true,
                     enumerable:true,
                     get () {
                         return data.key
                     },
                     set (newValue) {
                         if(newValue === data.key){
                             return
                         }
                         data.key = newValue
                     }
                })
            
            })
        }
    }
    

Observer类实现

需要实现的功能

  • 负责把data选项的属性转换成响应式数据
  • data中的某个属性也是对象,把该属性也要转化成响应式数据
  • 数据变化发送通知
    /*
        结构
        observe
        walk(data) //把data中的属性转化成getter和setter
        defineReactive(data,key,value)
    */
     class Oberver {
         constructor (data) {
             this.walk(data)
         }
         walk (data) {
             // 1. 判断data是否是对象且不为空 如果为空什么都不做
             if(!data || typeof data !== 'object') {
                 return
             }
             // 2. 遍历data对象中的属性
             Object.keys(data).forEach(key=>{
                 //提问  这个地方为什么要传第三值val 
                 this.defineReactive(data,key,data[key])
             })
         }
         defineReactive (obj,key,val) {
             // 如果val里面是一个对象,同时把对象的属性转成响应式数据
             this.walk(val)
             //为每个属性 创建dep 对象
             const dep = new Dep()
             const self = this
             Object.defineProperty(obj,key,{
                 configurable:true,
                 enumerable:true,
                 get () {
                     Dep.target && dep.addSub(Dep.target)
                     return val
                 },
                 set (newValue) {
                     if(newValue === val){
                         return
                     }
                     val = newValue
                     // 当重新复制的值是对象的时候需要把它转成响应式的数据
                     self.walk(newValue)
                     //数据发生变化发送通知
                     dep.notify()
                 }
             })
         }
     }
    

compiler类的实现

功能

  • 负责编译模板,解析指令、差值表达式
  • 负责页面的首次渲染
  • 当数据发生改变后重新渲染视图
    /*
        结构
        名称 compiler  
        属性 el vm
        方法 compile(el)
        compileElement(node)
        compileText(node)
        isDirective(attrName)
        isTextNode(node)
        isElementNode(node)
    */
    class Compiler {
        constructor (vm) {
            this.vm = vm
            this.$el = vm.$el
            this.compile(this.$el)
        }
        // 编译模板 处理文本节点和元素节点
        compile (el) {
            // 遍历元素下的所有节点
            const 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)
                }
            })
        }
        // 编译元素节点 处理指令
        compileElement (node) {
            // 遍历所有属性
             Array.from(node.attributes).forEach(attr=>{
                 let attrName = attr.name
                 //判断属性书否是指令
                 if(this.isDirective(attrName)){
                     const key = attr.value
                     // 处理指令
                     this.update(node,key,attrName)
                 }
             })
        }
        // 处理指令
        update (node,key,attrName) {
             const updateFn = this[attrName + 'Updater']
             updateFn && updateFn.call(this,node,this.vm[key],key)
        }
        // 处理v-text指令
        textUpdater (node,value,key) {
            node.textContent = value
            //创建watcher对象 当数据改变更新视图
            new Watcher(this.vm,key,(newValue)=>{
                node.textContent = newValue
            })
        }
        // 处理 v-model 指令
        modleUpdater (node,value,key) {
            node.value = value
            //创建watcher对象 当数据改变更新视图
            new Watcher(this.vm,key,(newValue)=>{
                node.value = newValue
            })
            //实现双向绑定
            node.addEventlistener('input',()=>{
                this.vm[key] = node.value
            })
        }
        // 编译文本节点 处理差值表达式
        compileText (node) {
            const reg = /\{\{(.+?) \}\}/
            const value = node.textContent
            if(reg.test(value)) {
                const key = RegExp.$1.trim()
                node.textContent = value.replace(reg,this.vm[key])
                //创建watcher对象 当数据改变更新视图
                new Watcher (this.vm,key,(newValue)=>{
                    node.textContent = newValue
                })
            }
        }
        // 判断属性是否是指令
        isDirective (attrName) {
            return attrName.startsWith('v-')
        }
        // 判断节点是否是文本节点
        isTextNode (node) {
            return node.nodeType === 3
        }
        // 判断节点是都是元素节点
        isEelmentNode (node) {
            return node.nodeType === 1
        }
    }

Dep (dependency)类的实现

功能

  • 收集依赖,添加观察者(watcher)
  • 通知所有观察者
    /*
        结构
        名称 Dep
        属性 subs //存储观察者
        方法 addSub(sub) //添加观察者
        notify() // 发布通知
    */
    class Dep {
        constructor () {
            // 存放所有观察者
            this.subs = []
        }
        //添加观察者
        addSub (sub) {
            if(sub && sub.update) {
                this.subs.push(sub)
            }
        }
        // 发送通知
        notify () {
            this.subs.forEach(sub=>{
                sub.update()
            })
        }
    }
    

watcher类的实现

image.png 功能

  • 当数据变化触发依赖,dep通知所有的watcher实例更新视图
  • 自身实例的时候往dep对象中添加自己
    /*
        结构
        名称 watcher
        属性 vm key  cb oldValue
        方法 update()
    */
    class  Watcher () {
      constructor (vm,key,cb) {
          this.vm = vm
          this.key = key
          // 当数据改变的时候更新视图
          this.cb = cb
          // 把watcher对象添加到Dep.target实例上
          Dep.target = this
          // 触发get方法,在get中调用addSub 把watcher实例添加到subs中
         this.oldValue = vm[key]
         // 防止重复添加watcher实例
         Dep.target = null
      }
      // 数据发生改变更新视图
      update () {
          const newValue = this.vm[this.key]
          if(newValue === this.oldValue) {
              return
          }
          //更改视图
          this.cb(newValue) 
      }
    }