封装--小型Vue

本文详细介绍了MVVM模式在JavaScript中的实现,包括Vue构造器、观察仓库Dep、观察者Watcher以及模板编译过程。通过数据劫持和响应式原理,实现数据与视图的双向绑定,以`v-model`为例展示了在input元素中的应用。此外,还涵盖了模板编译的逻辑,用于处理DOM元素中的指令并更新视图。

=> mvvm(第三版)

class Vue {
  constructor(options) {
    // 挂载点的dom
    this.$el = document.querySelector(options.el)
    // 数据
    this.$data = options.data

    // 数据劫持
    observe(this.$data)
    //模板编译
    compileTemplate(this.$el, this)
  }
}

// 观察仓库
class Dep {
  constructor() {
    // 观察者队列
    this.subscribes = []
  }
  // 添加观察者
  addSub(watcher) {
    this.subscribes.push(watcher)
  }

  // 通知更新
  notify() {
    this.subscribes.forEach(watcher => watcher.update())
  }
}

// 观察者
class Watcher {
  constructor(vm, key, node) {
    // 仓库对象的静态属性来保存当前watcher实例对象
    Dep.target = this;
    this.$vm = vm;
    this.$key = key;
    this.$node = node;
    // 触发数据劫持中的get方法,此时Dep.target的值为当有对象
    this.getValue();
    // 设置为null,为下次new Watcher准备
    Dep.target = null;
  }
  getValue() {
    // 触发get
    this.$value = this.$vm.$data[this.$key]
  }

  update() {
    // 获取一下最新数据
    this.getValue();
    if (this.$node.nodeType === 1) {
      if (this.$node.nodeName == 'INPUT') {
        this.$node.value = this.$value
      } else {
        this.$node.innerHTML = this.$value
      }
    } else if (this.$node.nodeType === 3) {
      this.$node.textContent = this.$value
    }
  }
}


// 模板编译
function compileTemplate(el, vm) {
  // dom操作在内存中完成
  // 文档碎片 --> 在内存在存储,不会在界面中渲染,通过appendChild渲染到视图中
  let fragment = document.createDocumentFragment()
  let childNode;
  while (childNode = el.firstChild) {
    // 渲染模板
    compileRender(childNode, vm)
    // 把得到的dom对象放到fragment中,此时dom会删除
    fragment.appendChild(childNode)
  }
  // 处理完成后,放到视图中
  el.appendChild(fragment)
}
// 编译视图显示
function compileRender(node, vm) {
  // 判断当前节点的类型 1元素,3文本
  if (node.nodeType === 1) {
    // 得到元素所有的属性集合
    // [...node.attributes].forEach(attrObj => {
    // console.log(attrObj.name, attrObj.value);
    [...node.attributes].forEach(({ name, value }) => {
      // 只关心v-开头属性
      if (/^v-/.test(name)) {
        if (name === 'v-model') { // 针对于input输入框
          node.value = vm.$data[value]
          // 观察者
          new Watcher(vm, value, node)

          // 绑定事件
          node.addEventListener('input', function () {
            vm.$data[value] = this.value.trim()
          });
        } else {
          // 观察者
          new Watcher(vm, value, node)

          node.innerHTML = vm.$data[value]
        }
      }
    });
    // 问一下有没有子元素了
    node.childNodes.forEach(child => compileRender(child, vm))
  } else if (node.nodeType === 3) { // 文本节点
    // 内容
    let cnt = node.textContent
    // 匹配只有{{}}才进行处理
    let preg = /\{\{\s*(\w+)\s*\}\}/
    // 替换
    cnt = cnt.replace(preg, (a0, a1) => {
      // 观察者
      new Watcher(vm, a1, node)

      return vm.$data[a1]
    })
    node.textContent = cnt
  }


}

// ------------------- 数据劫持
// 监听数据源
function observe(target) {
  // 只劫持json对象
  if (Object.prototype.toString.call(target) != '[object Object]') return;
  // 遍历
  for (let key in target) {
    defineRactive(target, key, target[key])
  }
}
// 实现劫持
function defineRactive(target, key, value) {
  observe(value)

  let dep = new Dep()

  Object.defineProperty(target, key, {
    // 获取器
    get() {
      if (Dep.target) {
        // 添加了观察者到通知队列中
        dep.addSub(Dep.target)
      }
      return value
    },
    // 修改器
    set(newV) {
      if (newV != value) {
        value = newV
        dep.notify()
      }
    }
  });
}

调用

<script src="./js/myvue.js"></script>

<div id="app">
  <h3>{{ title }}</h3>
  <input type="text" v-model='title'>
</div>

<script>
  const vm = new Vue({
    el: '#app',
    data: {
      title: '你好MVVM'
    }
  })
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值