自己写代码理解Vue的数据双向绑定

重要代码的作用都写在代码注释中了。

核心点:
1、Object.defineProperty(),相当于给相关属性在取值和赋值时增加proxy,可以执行proxy中的功能,这里就是在get是进行双向绑定
2、Compiler的本质就是遍历html文档,把按照vue规范定的字段找出来做处理。比如{{deep.a}}取出来之后,从vm实例中拿出真实的值把{{deep.a}}替换掉。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div id="app">
    <div>{{deep.a}}</div>
    <input v-model="deep.a">
  </div>

  <script>

    class Dep {

      static target = null
      static targetStack = []
      static pushTarget(_target) {
        if (Dep.target) {
          Dep.targetStack.push(Dep.target)
        }
        Dep.target = _target
      }

      static popTarget() {
        Dep.target = Dep.targetStack.pop()
      }

      constructor() {
        this.subArr = []
      }

      addDepend() {
        Dep.target.addDep(this) //this.addSub(watcher),此时的watcher就在target里
      }

      addSub(sub) {
        this.subArr.push(sub)
      }

      notify() {
        for (let sub of this.subArr) {
          sub.update()
        }
      }
    }

    class Watcher {
      //以expression为deep.a为例
      constructor(vm, expression, cb) {
        this.vm = vm
        this.expression = expression
        this.cb = cb
        this.value = this.getVal()
      }

      getVal () {
        Dep.pushTarget(this)
        let val = this.vm
        this.expression.split('.').forEach((key) => {
          val = val[key]//这里会触发get方法,把这个watcher塞到Dep的sub里
        })
        Dep.popTarget()
        return val //根据compiler里的分析这里val=1,也就是这个watcher的value=1
      }

      addDep(dep) {
        dep.addSub(this)
      }

      update() {
        let val = this.vm
        this.expression.split('.').forEach((key) => {
          val = val[key]
        })
        this.cb.call(this.vm, val, this.value)
      }
    }

    class Observer {
      constructor(obj) {
        this.walk(obj)
      }

      walk(obj) {
        Object.keys(obj).forEach(key=> {
          if (typeof obj[key] === 'object') {
            this.walk(obj[key])
          }

          this.defineReactive(obj, key, obj[key])
        })
      }

      defineReactive(obj, key, value) {

        let dep = new Dep()

        Object.defineProperty(obj, key, {

          set(newVal) {
            console.log('set')
            console.log(key + ': ' + value)
            if (newVal === value) {
              return
            }

            value = newVal
            dep.notify()
          },

          get() {
            if (Dep.target) {
              dep.addDepend()
            }
            return value
          },
        })
      }

    }

    class Compiler {
      constructor (el, vm) {

        vm.$el = document.querySelector(el)
        let fragment = document.createDocumentFragment()
        this.replace(vm.$el, vm)
      }

      replace (frag, vm) {
        Array.from(frag.childNodes).forEach(node => {
          let txt = node.textContent
          let reg = /\{\{(.*?)\}\}/g // 正则匹配{{}}

          if (node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}}

            //比如这里RegExp.$1为deep.a,reg.test(txt)去掉了大括号
            let arr = RegExp.$1.split('.')//['deep', 'a']
            let val = vm
            arr.forEach(key => {
              val = val[key]//循环两次,第一次val[deep] = {a:1, b:2},第二次val[a] = 1,最终val = 1
            })
            // 用trim方法去除一下首尾空格,这里{{deep.a}}就变成了1
            node.textContent = txt.replace(reg, val).trim()
            //传入'deep.a'做双向绑定
            vm.$watch(RegExp.$1, function (newVal) {
              node.textContent = txt.replace(reg, newVal).trim()
            })//new Watcher(vm, 'deep.a', cb) cb就是上面第二个function参数
          }

          if (node.nodeType === 1) {  // 元素节点
            let nodeAttr = node.attributes // 获取dom上的所有属性,是个类数组
            Array.from(nodeAttr).forEach(attr => {
              let name = attr.name
              let exp = attr.value
              if (name.includes('v-')){
                let val = vm
                let arr = exp.split('.')
                arr.forEach(key=> {
                  val = val[key]
                })
                node.value = val
                // node.value = vm[exp]
              }
              // 监听变化
              vm.$watch(exp, function(newVal) {
                node.value = newVal
              })

              node.addEventListener('input', e => {
                let newVal = e.target.value
                let arr = exp.split('.')
                let val = vm
                arr.forEach((key, i)=> {
                  if (i === arr.length - 1) {
                    val[key] = newVal
                    return
                  }
                  val = val[key]
                })
              })
            })
          }

          // 如果还有子节点,继续递归replace
          if (node.childNodes && node.childNodes.length) {
            this.replace(node, vm)
          }
        })
      }
    }

    const LIFECYCLE_HOOKS = [
      'created',
      'mounted'
    ]

    function callHook (vm, hook) {
      const handlers = vm.$options[hook]
      if (handlers) {
        handlers.call(vm)
      }
    }

    class Due {
      constructor(options) {
        let vm = this
        vm.$options = options
        vm.$watch = function (key, cb) {
          new Watcher(vm, key, cb)
        }
        vm._data = vm.$options.data
        this.observe(vm)

        LIFECYCLE_HOOKS.forEach(hook => {
          vm.$options[hook] = vm.$options[hook] || function () {}
        })

        for (let key in vm._data) {
          this.proxy(vm, '_data', key)
        }

        callHook(vm, 'created')
        new Compiler(vm.$options.el, vm)
        callHook(vm, 'mounted')
      }

      proxy (target, sourceKey, key) {
        Object.defineProperty(target, key, {
          configurable: true,
          get: function proxyGetter() {
            return target[sourceKey][key]
          },
          set: function proxySetter(newVal) {
            target[sourceKey][key] = newVal
          }
        })
      }

      observe(obj) {
        if (!obj || typeof obj !== 'object') {
          return
        }

        return new Observer(obj)
      }
    }

    let app = new Due({
      el: '#app',
      data: {
        msg: 'hello wue',
        deep: {
          a: 1,
          b: 2
        }
      },
      created() {
        console.log('created')
      },
      mounted () {
        console.log('mounted')
        this.deep.a = 111
      }
    })


  </script>
</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值