极简的MVVM实现


function Vue(options = {}) {
  this.$options = options;
  this._data = this.$options.data;

  // /观测数据
  new Observer(this._data);

  this._data = this.$options.data;
  console.log('data => :', this._data);


  for (const key in this._data) {
    Object.defineProperty(this, key, {
      enumerable: true,
      get() {
        return this._data[key]
      },
      set(newVal) {
        this._data[key] = newVal
      }
    })
  }

  new Compile(this.$options.el, this)
}


// 模板解析
function Compile(el, vm) {
  vm.$el = document.querySelector(el)
  const fragment = document.createDocumentFragment()
  let child = null;
  // 通过while循环遍历el下所有的节点  移入到 空白的fragment节点
  // 然后 el节点就变成空的了
  while (child = vm.$el.firstChild) {
    console.log(child, '移动的节点有');
    fragment.append(child)
  }
  /*
   *捕获DOM中所有 {{  }}中的字符串 
   *对_data中的 键名 key进行匹配
   *匹配成功的就进行替换 
   */
  replace(fragment);

  // .然后把创建的节点插进根节点 就OK 了   模板编译完成 
  /*
    虽然此时模板编译完成了;我们想要的界面都渲染出来了
    但是页面却不是响应式的
  */
  vm.$el.append(fragment)

  // 
  function replace(fragment) {
    // 正则匹配形如{{str}}的字符串
    const pattern = /\{\{(.*)\}\}/

    //遍历fragment所有的子节点  
    Array.from(fragment.childNodes).forEach(node => {
      let text = node.textContent; //获取子节点的文本内容

      //当子节点的nodeType为 3 (文本节点)  && 正则匹配成功
      if (node.nodeType === 3 && pattern.test(text)) {
        // 正则匹配到的内容用对应的vm[key]替换
        console.log(RegExp.$1, '匹配到的字符串为');
        const key = RegExp.$1.trim()


        //实例化一个订阅者 用来实现 {{ }} 的响应式  callback用来更新视图
        new Watcher(vm, key, (newVal) => {
          node.textContent = text.replace(pattern, newVal)
        })

        // 替换文本节点的内容为) _data中对应的字符串
        node.textContent = text.replace(pattern, vm[key])
      }

      // 如果当前的子节点不是文本节点就进行递归处理 遍历所以的后代节点进行处理
      if (node.childNodes && node.childNodes.length) {
        replace(node)
      }

    })
  }

}


/**
 * 实现响应式   需要三个东西 Observer(观察者) Watcher(订阅者) Dep(充当eventHub的角色)
 * Observer负责观察数据,当数据发生变化时,
 * 通过Dep通知Watcher,Watcher收到数据变动的通知时,
 * 能够拿到数据的最新值并执行相应回调更新视图。
 */

/** 
 * Dep具有一个属性连个方法  subs=[]  addSub() 和 notify()
 * subs 可当成一个存储器  存储着  sub (Wathcer的实例)
 * addSub方法将sub添加到subs数组,
 * notify方法遍历 subs 数组并调用每个子元素sub的update方法(此方法将更新视图)
 */
function Dep() {
  this.subs = []
}

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

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


/**
 * Watcher的实现  
 * 
 * Compile函数中当子节点为文本节点且正则匹配成功时,除了替换文本内容外,
 * 还必须调用Watcher,传递三个参数,vm, key和fn,
 * vm为当前Vue实例,key为此节点中正则匹配到的key,fn为更新此节点文本内容的回调(需传参)。
 * Watcher函数的最后,将实例this赋值给Dep.target,
 * 这个实例将被添加到 Dep实例的subs数组中,这一步是在Observer中进行的。
 */


function Watcher(vm, key, fn) {
  this.vm = vm
  this.key = key
  this.fn = fn
  Dep.target = this
}
// 更新视图
Watcher.prototype.update = function () {
  this.fn(this.vm[this.key])
}


// 数据劫持
function Observer(data) {
  const dep = new Dep()


  // 循环 _data 中所有的响应式变量 
  for (const key in data) {
    let val = data[key]
    /**
     * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
     */
    Object.defineProperty(data, key, {
      enumerable: true,
      get() {
        // 把 Watcher的实例添加进 Dep 的 subs 中
        Dep.target && dep.addSub(Dep.target)
        Dep.target = null
        // 读取数据的值
        return val
      },
      set(newVal) {
        // 当属性值呗修改的时候就调用这个函数 
        // 执行 当前属性值对应的  Wathcer 实例的 update 方法 => 执行 new Watcher 的时候的 callback() 传入最新的当前值
        if (newVal === val) {
          return
        }
        val = newVal
        dep.notify()
      }
    })

  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值