简易版 vue 指令解析

2、在模拟 Vue.js 响应式源码的基础上实现 v-html 指令,以及 v-on 指令

  • 答:指令解析的代码如下(完整代码见 https://github.com/smallSix6/fed-e-task-liuhuijun/tree/master/fed-e-task-03-01/code/liuzi-minVue ):
    • vue/vue.js
        class Vue {
          constructor(options) {
              // 1、通过属性保存选项的数据
              this.$options = options || {}
              this.$data = options.data || {}
              this.$methods = options.methods || {}
              this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
                  // 2、把 data 中的成员转换成 getter 和 setter,并注入到 vue 实例中
              this._proxyData(this.$data)
                  // 把 methods 中的成员注入到 vue 实例中 
              this._proxyMethods(this.$methods)
                  // 3、调用 observer 对象,监听数据的变化
              new Observer(this.$data)
                  // 4、调用 compiler 对象,解析指令和插值表达式
              new Compiler(this)
          }
          _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]) {
                              data[key] = newValue
                          }
                      }
                  })
              })
          }
          _proxyMethods(methods) {
              Object.keys(methods).forEach(key => {
                  // 把 methods 的成员注入到 vue 实例中
                  this[key] = methods[key]
              })
          }
      }
      
    • vue/compiler.js
      class Compiler {
          constructor(vm) {
                  this.el = vm.$el
                  this.vm = vm
                  this.compile(this.el)
              }
              // 编译模板,处理文本节点和元素节点
          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)
                      }
                      // 判断 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)) {
                          // v-text --> text
                          attrName = attrName.substr(2)
                          let key = attr.value
                          if (attrName.startsWith('on')) {
                              const event = attrName.replace('on:', '') // 获取事件名
                                  // 事件更新
                              return this.eventUpdate(node, key, event)
                          }
                          this.update(node, key, attrName)
                      }
                  })
              }
              // 编译文本节点,处理插值表达式
          compileText(node) {
              let reg = /\{\{(.+?)\}\}/
              let value = node.textContent
              if (reg.test(value)) {
                  let key = RegExp.$1.trim()
                  node.textContent = value.replace(reg, this.vm[key])
                      // 创建 watcher 对象,当数据改变时更新视图
                  new Watcher(this.vm, key, (newValue) => {
                      node.textContent = newValue
                  })
              }
      
          }
          update(node, key, attrName) {
              let updateFn = this[attrName + 'Updater']
              updateFn && updateFn.call(this, node, this.vm[key], key)
          }
          eventUpdate(node, key, event) {
              this.onUpdater(node, key, event)
          }
      
      
          // 处理 v-text 指令
          textUpdater(node, value, key) {
                  node.textContent = value
                  new Watcher(this.vm, key, (newValue) => {
                      node.textContent = newValue
                  })
              }
              // 处理 v-html 指令
          htmlUpdater(node, value, key) {
                  node.innerHTML = value
                  new Watcher(this.vm, key, (newValue) => {
                      node.innerHTML = newValue
                  })
              }
              // 处理 v-model 指令
          modelUpdater(node, value, key) {
                  node.value = value
                  new Watcher(this.vm, key, (newValue) => {
                          node.value = newValue
                      })
                      // 双向绑定
                  node.addEventListener('input', () => {
                      this.vm[key] = node.value
                  })
              }
              // 处理 v-on 指令
          onUpdater(node, key, event) {
              node.addEventListener(event, (e) => this.vm[key](e))
          }
      
      
      
          // 判断元素属性是否是指令
          isDirective(attrName) {
                  return attrName.startsWith('v-')
              }
              // 判断节点是否是文本节点
          isTextNode(node) {
                  return node.nodeType === 3
              }
              // 判断节点是否是元素节点
          isElementNode(node) {
              return node.nodeType === 1
          }
      }
      
    • vue/index.html
      <!DOCTYPE html>
      <html lang="cn">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>Mini Vue</title>
          <script src="./vue/dep.js"></script>
          <script src="./vue/watcher.js"></script>
          <script src="./vue/compiler.js"></script>
          <script src="./vue/observer.js"></script>
          <script src="./vue/vue.js"></script>
      </head>
      
      <body>
          <div id="app">
              <h1 v-on:click="myClick">点击事件</h1>
              <h3>{{ msg }}</h3>
              <h1>v-text</h1>
              <div v-text="msg"></div>
              <h1 v-html='html'></h1>
              <input type="text" v-model="msg">
              <input type="text" v-model="count">
              <h1>{{dog.name}}</h1>
          </div>
      </body>
      <script>
          let vm = new Vue({
              el: '#app',
              data: {
                  msg: 'object1111',
                  dog: {
                      name: ''
                  },
                  html: '<button style="color: pink; font-size: 26px">v-html</button>'
              },
              methods: {
                  myClick() {
                      alert('点击事件')
                  },
                  clickHandler() {
                      this.dog
                          // 该 name 属性是否是响应式的
                      this.$set(dog, name, 'Trump')
                  }
              }
          })
      </script>
      
      </html>
      
    • v-html 的实现思路:
      • 在 vue 实例初始化的时候,调用 new Compiler(this) 解析指令和插值表达式
      • 在 Compiler 类中编译模板,处理文本节点和元素节点,过程见方法 this.compile(this.el)
      • 处理元素节点,方法见 this.compileElement(node)
      • compileElement(node) 方法中会遍历这个节点下的属性,如果是指令的话则调用想用的指令处理函数
        • textUpdater:处理 v-text 指令
        • htmlUpdater:处理 v-html 指令
        • modelUpdater:处理 v-model 指令
        • onUpdater:处理 v-on 指令
    • v-on 的实现思路:
      • 在 vue 实例初始化的时候,调用 new Compiler(this) 解析指令和插值表达式
      • 把 methods 中的成员注入到 vue 实例中,this._proxyMethods(this.$methods)
      • 在 Compiler 类中编译模板,处理文本节点和元素节点,过程见方法 this.compile(this.el)
      • 处理元素节点,方法见 this.compileElement(node)
      • 如果找到属性名是以 on 开头的,则 调用 this.eventUpdate(node, key, event)
      • eventUpdate 调用 onUpdater 来处理 v-on 指令
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值