一个简易的 vue 框架 —— MyVue

MyVue

一个简易的 Vue 框架:

1、用来解释 vue 的响应式原理
2、实现简单的模版指令{{}}、v-model 指令和事件指令

项目地址

https://github.com/userkang/MyVue

用法

1、 安装依赖

npm install

2、 启动本地服务

npm run dev

部分代码

index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>myVue</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="text">
    <h1>{{text}}</h1>
    <h2>{{text}}</h2>
    <button v-on:click="clickToChange">reset</button>
  </div>
</body>
<script src="./dist/myvue.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    new MyVue({
      el: '#app',
      data: {
        text: '',
      },
      methods: {
        clickToChange() {
          this.text = 'myVue'
        }
      },
      mounted() {
        this.text = 'myVue'
      }
    })
  })
</script>

</html>

index.js:

import { observe } from './observer.js'
import { Compile } from './compile.js'

class MyVue {
  constructor(options) {
    this.data = options.data
    this.methods = options.methods

    // 添加属性代理
    Object.keys(this.data).forEach(key => {
      this.proxyKeys(key)
    })

    // 注册监听
    observe(this.data)

    // 解析和初始化模版,并注册订阅者
    new Compile(options.el, this)

    // 执行 mounted 函数
    options.mounted.call(this)
  }

  proxyKeys(key) {
    Object.defineProperty(this, key, {
      enumerabel: true,
      configurable: true,
      get() {
        return this.data[key]
      },
      set(newVal) {
        this.data[key] = newVal
      }
    })
  }
}

window.MyVue = MyVue

observer.js:

/**
 * 监听者
 */
class Observer {
  constructor(data) {
    this.data = data
    this.walk(data)
  }

  walk(data) {
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }

  defineReactive(data, key, val) {
    const dep = new Dep()
    observe(val)
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 如果当前有缓存订阅者,就添加一个新订阅者
        if (Dep.target) {
          // 添加一个订阅者
          dep.addSub(Dep.target)
        }
        return val
      },
      set(newVal) {
        if (newVal === val) {
          return
        }
        val = newVal
        // 如果数据有变化,通知所有订阅者
        dep.notify()
      }
    })
  }
}

// 消息订阅器
export class Dep {
  constructor() {
    this.subs = []
  }

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

  notify() {
    this.subs.forEach(sub => {
      // 调用订阅者的 update 方法
      sub.update()
    })
  }
}
Dep.target = null

export const observe = value => {
  if (!value || typeof value !== 'object') {
    return
  }
  return new Observer(value)
}

watcher.js:

import { Dep } from './observer.js'

// 订阅者
export class Watcher {
  constructor(vm, exp, cb) {
    this.cb = cb
    this.vm = vm
    this.exp = exp
    this.value = this.get()
  }

  update() {
    this.run()
  }

  run() {
    let value = this.vm.data[this.exp]
    let oldVal = this.value
    if (value !== oldVal) {
      this.value = value
      this.cb.call(this.vm, value, oldVal)
    }
  }

  get() {
    // 缓存订阅者自己
    Dep.target = this
    // 强制执行监听器里的 get 函数,这里的执行顺序不能变
    let value = this.vm.data[this.exp]
    // 清除自己
    Dep.target = null
    return value
  }
}

compile.js:

import { Watcher } from './watcher.js'

/**
 * 解析器
 * 1、解析模版指令,并替换模版数据,初始化视图
 * 2、将模版指令对应的节点绑定对应的更新函数,初始化相应的订阅器
 */
export class Compile {
  constructor(el, vm) {
    this.vm = vm
    this.el = document.querySelector(el)
    this.fragment = null
    this.init()
  }

  init() {
    if (this.el) {
      this.fragment = this.nodeToFragment(this.el)
      this.compileElement(this.fragment)
      this.el.appendChild(this.fragment)
    } else {
      console.log('Dom 元素不存在')
    }
  }

  // 将节点转为文档片段
  nodeToFragment(el) {
    let fragment = document.createDocumentFragment()
    let child = el.firstChild
    while (child) {
      fragment.appendChild(child)
      child = el.firstChild
    }
    return fragment
  }

  // 编译节点
  compileElement(el) {
    const childNodes = el.childNodes

    Array.prototype.slice.call(childNodes).forEach(node => {
      const reg = /\{\{(.*)\}\}/
      const text = node.textContent

      if (this.isElementNode(node)) {
        this.compile(node)
      } else if (this.isTextNode(node) && reg.test(text)) {
        this.compileText(node, reg.exec(text)[1])
      }

      // 如果有子节点,递归编译
      if (node.childNodes && node.childNodes.length) {
        this.compileElement(node)
      }
    })
  }

  // 编译指令
  compile(node) {
    const nodeAttrs = node.attributes

    Array.prototype.forEach.call(nodeAttrs, attr => {
      let attrName = attr.name

      // 是否是指令属性
      if (this.isDirective(attrName)) {
        const exp = attr.value
        const dir = attrName.substring(2)

        // 是否是事件指令属性
        if (this.isEventDirective(dir)) {
          this.compileEvent(node, exp, dir)
        } else {
          // v-model 指令
          this.compileModel(node, exp)
        }
        node.removeAttribute(attrName)
      }
    })
  }

  // 编译 {{}} 文本模版语法
  compileText(node, exp) {
    const initText = this.vm[exp]
    this.updateText(node, initText)
    new Watcher(this.vm, exp, value => {
      this.updateText(node, value)
    })
  }

  // 编译 v-on 事件指令
  compileEvent(node, exp, dir) {
    const eventType = dir.split(':')[1]
    const cb = this.vm.methods && this.vm.methods[exp]
    if (eventType && cb) {
      node.addEventListener(eventType, cb.bind(this.vm))
    }
  }

  // 编译 v-model 指令
  compileModel(node, exp) {
    let val = this.vm[exp]
    this.modelUpdater(node, val)
    new Watcher(this.vm, exp, value => {
      this.modelUpdater(node, value)
    })

    node.addEventListener('input', e => {
      const newValue = e.target.value
      if (val === newValue) {
        return
      }
      this.vm[exp] = newValue
      val = newValue
    })
  }

  // 更新文本数据
  updateText(node, value) {
    node.textContent = typeof value === 'undefined' ? '' : value
  }

  // 更新 model 数据
  modelUpdater(node, value) {
    node.value = typeof value === 'undefined' ? '' : value
  }

  // 判断是否是指令
  isDirective(attr) {
    return attr.indexOf('v-') === 0
  }

  // 判断是否是事件指令
  isEventDirective(dir) {
    return dir.indexOf('on:') === 0
  }

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

  // 判断是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值