MVVM 第三步发布和订阅

简述

这的发布订阅流程:
1.当我们转换mastach语法的时候 有两个步骤 查找和替换
查找到节点内容中带有{{}}的字符串 解析成数组 然后再vm[xxx]找到对应的值
查找完毕开始替换内容 这是一个查找替换过程
2.单单只有查找替换还不够 因为要双向绑定 数据变化视图变化
此时就需要一个监听 也就是watcher
那么这里就有几个问题 1.什么时候添加订阅 2.什么时候发布 3.怎么添加订阅
添加订阅的目的就是将mastach转换为数据那一步先订阅进dep中然后进行监听统一管理
所以添加订阅要在compile的字符串转化为data后
发布的话肯定是要当我们改变了data中的数据时触发 当我们改变data中的数据时
肯定就会触发set函数 然后此时发布订阅 通知watcher进行更新 看修改了那个值然后进行一个
函数回调 将新值进行替换 这么一个流程
所以显而易见 watcher里的回调肯定就是一个替换新值的过程了
最麻烦的就是新值我们要怎么获取
这里的思路就是:
a.当我们new Watcher时候会触发构造函数 在compile中当我们遍历节点的时候 一次又一次
new Watcher 就会触发构造函数 并且传入vm 以及匹配到的mastach内的变量字符串
b.此时我们就可以通过vm和变量字符串访问data中的属性 访问属性的同时会触发get
然后我们就可以在这里添加订阅了 添加完后当我们修改属性时候就会触发set 此时就可以 触发notify
触发notify就会调用watcher的update 在updata里面我们就可以将之前添加的节点全部进行一次重更新
一次次访问data属性并将新值传入到回调函数中 在回调函数中就可以将新的值进行替换
此时就完成了数据到视图的一个更新

html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <p>
        这个人的年龄{{aaa.age}}

        <span>
          性别是{{aaa.sex}}

          <p>年龄是{{aaa.age}}</p>
        </span>
      </p>
      <h1>这个人的年龄{{aaa.sex}}</h1>
      <div>这个人的年龄{{aaa.children.name}}</div>
      <i>这个人的年龄{{aaa.children.age}}</i>
      这个人的性别{{aaa.sex}}
      <div>{{aaa.hobby}}</div>
    </div>
    <script src="vue.js"></script>
    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          aaa: {
            age: 14,
            sex: 'aa',
            children: {
              name: 'son',
              age: 18
            },
            hobby: '123546'
          }
        }
      })
      // var obj = {
      //   age: 14,
      //   sex: 'aa',
      //   children: {
      //     name: 'son',
      //     age: 18
      //   },
      //   hobby: '111'
      // }

      // var app = document.querySelector('#app')
      // var fragment = document.createDocumentFragment()
      // var children = app.childNodes
      // console.log(children)
      // var reg = /\{\{(.*)\}\}/
      // var arrChildren = Array.from(children)

      // function findMastach(childrenNodes, obj) {
      //   childrenNodes.forEach(val => {
      //     var text = val.textContent
      //     // 判断节点类型 是否符合{{}}
      //     if (val.nodeType === 3 && reg.test(text)) {
      //       var newObj = { obj }
      //       RegExp.$1.split('.').forEach(val => {
      //         // newObj[val]
      //         newObj = newObj[val]
      //       })

      //       text = text.replace(reg, newObj)
      //       val.textContent = text
      //     }
      //     // 判断是否有子节点 有递归筛选
      //     if (val.childNodes) {
      //       findMastach(val.childNodes, obj)
      //     }
      //   })
      // }
      // findMastach(children, obj)
    </script>
  </body>
</html>

vue.js

function Vue(options) {
  // 数据初始化
  this.$data = options.data
  this.$el = options.el
  console.log(this.$el)

  // 数据劫持
  new Observe(this.$data)
  // 数据代理
  dataAgancy.call(this, this.$data)
  // 编译模板
  new Compile(this.$el, this)
}

// 数据劫持
function Observe(data) {
  walk(data)
}
// 将对象的属性改为get和set的访问属性
function walk(obj) {
  for (const k in obj) {
    let value = obj[k]
    // 进来的data第一层属性全部替换为get和set的访问属性
    let dep = new Dep()
    console.log(dep.subs)

    Object.defineProperty(obj, k, {
      enumerable: true,
      configurable: true,
      get() {
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      set(newValue) {
        console.log('set')
        // 监听新值为对象进行递归
        walk(newValue)
        console.log('修改')

        value = newValue
        dep.notify()
      }
    })
    console.log(dep)

    // 判断第二层的属性是否为对象
    if (value instanceof Object) {
      // 为对象进行深度递归
      walk(value)
    }
  }
}

// 数据代理 让vm直接通过定义的属性名 vm.name 访问到值
//  不用vm.$data.name访问
function dataAgancy() {
  // 遍历劫持完成后的对象 当前实例的对象为name对象
  for (const k in this.$data) {
    // this指向的是vm实例 像vm实例中直接添加name属性
    Object.defineProperty(this, k, {
      enumerable: true,
      configurable: true,
      get() {
        return this.$data[k]
      },
      set(newValue) {
        console.log('代理')

        this.$data[k] = newValue
      }
    })
  }
}
// 模板编译
function Compile(el, vm) {
  console.log(vm.aaa)

  const app = document.querySelector(el)
  // 创建一个文档
  let fragment = document.createDocumentFragment()

  // 将所有子节点放入内存中
  while ((child = app.firstChild)) {
    fragment.appendChild(child)
  }
  // 正则检测mastach语法

  console.log(fragment)
  // 转换为数组
  findMastach(fragment.childNodes, vm)

  app.appendChild(fragment)
}
// 将dom节点中的{{}}语法挑选出来改换成实际数据
function findMastach(childeNodes, vm) {
  const reg = /\{\{(.*)\}\}/
  Array.from(childeNodes).forEach(val => {
    let text = val.textContent
    // 判断节点类型 并 检测是否含有mastach语法
    if (val.nodeType === 3 && reg.test(text)) {
      // 有
      let value = vm

      RegExp.$1.split('.').forEach(val => {
        value = value[val]
      })

      new Watcher(vm, RegExp.$1, function(newValue) {
        val.textContent = text.replace(reg, newValue)
      })
      val.textContent = text.replace(reg, value)
    }
    // 无
    // 判断子节点 子节点类型可能为3也可能为1
    // 如果为3 继续上述检测mastach语法
    // 如果为1 继续递归判断
    if (val.childNodes) {
      findMastach(val.childNodes, vm)
    }
  })
}
// 发布订阅模式
function Dep() {
  this.subs = []
}

Dep.prototype.addSub = function(sub) {
  this.subs.push(sub)
}

Dep.prototype.notify = function() {
  this.subs.forEach(val => {
    val.update()
  })
}

function Watcher(vm, exp, fn) {
  this.fn = fn
  this.vm = vm
  this.exp = exp

  Dep.target = this
  let arr = exp.split('.')
  let value = vm
  arr.forEach(val => {
    value = value[val]
  })

  Dep.target = null
}
Watcher.prototype.update = function() {
  arr = this.exp.split('.')
  console.log(arr)
  let value = this.vm
  arr.forEach(val => {
    value = value[val]
  })
  console.log(value)

  this.fn(value)
}
// 这的发布订阅流程:
// 1.当我们转换mastach语法的时候 有两个步骤  查找和替换
// 查找到节点内容中带有{{}}的字符串 解析成数组 然后再vm[xxx]找到对应的值
// 查找完毕开始替换内容  这是一个查找替换过程
// 2.单单只有查找替换还不够 因为要双向绑定 数据变化视图变化
// 此时就需要一个监听 也就是watcher
// 那么这里就有几个问题 1.什么时候添加订阅 2.什么时候发布 3.怎么添加订阅
// 添加订阅的目的就是将mastach转换为数据那一步先订阅进dep中然后进行监听统一管理
//  所以添加订阅要在compile的字符串转化为data后
// 发布的话肯定是要当我们改变了data中的数据时触发 当我们改变data中的数据时
// 肯定就会触发set函数 然后此时发布订阅 通知watcher进行更新 看修改了那个值然后进行一个
// 函数回调 将新值进行替换 这么一个流程 
// 所以显而易见 watcher里的回调肯定就是一个替换新值的过程了
// 最麻烦的就是新值我们要怎么获取
// 这里的思路就是:
// a.当我们new Watcher时候会触发构造函数 在compile中当我们遍历节点的时候 一次又一次
// new Watcher 就会触发构造函数 并且传入vm 以及匹配到的mastach内的变量字符串
// b.此时我们就可以通过vm和变量字符串访问data中的属性 访问属性的同时会触发get
// 然后我们就可以在这里添加订阅了 添加完后当我们修改属性时候就会触发set 此时就可以 触发notify
// 触发notify就会调用watcher的update 在updata里面我们就可以将之前添加的节点全部进行一次重更新
// 一次次访问data属性并将新值传入到回调函数中  在回调函数中就可以将新的值进行替换
// 此时就完成了数据到视图的一个更新


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值