miniVue手写实现

// 完整代码案例详见https://gitee.com/CrystalAngelLee/handwritting/tree/master/minivue-handlewritting

分析Vue使用结构

<!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>Vue使用结构</title>
</head>
<body>
  <div id="app">
    <h1>插值表达式</h1>
    <h3>{{ msg }}</h3>
    <h3>{{ count }}</h3>
    <h1>v-text</h1>
    <div v-text="msg"></div>
    <h1>v-model</h1>
    <input type="text" v-model="msg">
    <input type="text" v-model="count">
  </div>

  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue',
        count: 20,
        items: ['a', 'b', 'c']
      }
    })
  </script>
</body>
</html>

Vue响应式原理整体结构分析

在这里插入图片描述
Vue

负责把data中的成员注入到Vue实例,并且转换成getter 和setter
会调用Observer 和 compiler
在这里插入图片描述

功能:

  • 负责接收初始化的参数(选项) - constructor
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变化
  • 负责调用 compiler 解析指令/插值表达式

observer

数据劫持:能够监听data中的数据,如果数据更新,获取值并通知dep
在这里插入图片描述

功能:

  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变化发送通知

Compiler

解析每个元素中的指令/插值表达式, 并替换成相应的数据
在这里插入图片描述

功能:

  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染
  • 当数据变化后重新渲染视图

dep(发布者-Dependency)

添加观察者,当数据发生变化的时候通知所有的观察者
在这里插入图片描述

在这里插入图片描述
功能:

  • 收集依赖,添加观察者(watcher)
  • 通知所有观察者

compiler中收集依赖,发送通知

watcher(观察者)

update方法负责更新视图
在这里插入图片描述

在这里插入图片描述
功能:

  • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
  • 自身实例化的时候往 dep 对象中添加自己

相关实现

Vue

class Vue {
  constructor(options) {
    // 1 通过属性保存选项的数据
    this.$options = options;
    this.$data = options.data || {};
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
    // 2 把data中的成员转换成getter/setter
    this._proxyData(this.$data)
    // 3 调用observer对象,监听数据的变化
    new Observer(this.$data)
    // 4 调用compiler对象,解析指令和差值表达式
    new Compiler(this)
  }

  _proxyData(data) {
    // Object.defineProperty
    Object.keys(data).forEach(d => {
      Object.defineProperty(this, d, {
        enumerable: true,
        configurable: true,
        get() {
          return data[d]
        },
        set(val) {
          if (data[d] === val) return
          data[d] = val
        }
      })
    })
  }
}

observer

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

  // 遍历data对象
  walk(data) {
    if (!data || typeof data !== 'object') return;
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }

  // 定义响应式数据
  defineReactive(obj, key, value) {
    const that = this
    // 负责收集依赖,并发送通知
    const dep = new Dep();
    // 如果value是对象,把value内部的属性转换成响应式数据
    this.walk(value)
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target);
        return value
      },
      set(val) {
        if (val === value) return
        value = val;
        // 如果val是对象,把val内部的属性转换成响应式数据
        that.walk(val)
        // 发送通知
        dep.notify();
      }
    })
  }
}

compiler

class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el)
  }
  // 编译模板,处理节点
  compile(el) {
    const 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)
      }
    })
  }

  // 编译文本节点: 处理插值表达式
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/
    const val = node.textContent
    if (!reg.test(val)) return;
    const key = RegExp.$1.trim()
    node.textContent = val.replace(reg, this.vm[key])
    new Watcher(this.vm, key, (val) => {
      node.textContent = val
    })
  }

  // 编译元素节点: 处理指令
  compileElement(node) {
    Array.from(node.attributes).forEach(attr => {
      let attrName = attr.name;
      console.log(attrName)
      if(this.isDirective(attrName)) {
        attrName = attrName.substr(2)
        const key = attr.value
        this.update(node, key, attrName)
      }
    })
  }

  update (node, key, attrName) {
    const updateFn = this[attrName + 'Updater']
    updateFn && updateFn.call(this, node, this.vm[key] ,key)
  }

  // v-text
  textUpdater(node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (val) => {
      node.textContent = val
    })
  }

  // v-model
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (val) => {
      node.value = val
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // 判断是否是指令
  isDirective(name) {
    return name.startsWith('v-')
  }

  // 判断节点是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }

  // 判断节点是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
}

dep

class Dep {
  constructor() {
    this.subs = []
  }

  // 添加观察者
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }

  // 发送通知
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

watcher

class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    this.key = key; // data中的属性名称
    this.cb = cb; // 回调函数负责更新视图

    // 把watcher对象记录到Dep类的静态属性target
    Dep.target = this;
    // 触发get方法,在get方法中会调用addSub
    this.oldValue = vm[key]
    Dep.target = null
  }

  // 当数据发生变化的时候更新视图
  update() {
    const value = this.vm[this.key]
    if (value === this.oldValue) return;
    this.cb(value)
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值