npm: vue实现原理(最小版本的vue)

vue模板用法

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</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/dep.js"></script>
  <!-- watch中引入的dep -->
  <script src="./js/watch.js"></script> 
  <script src="./js/compiler.js"></script>
  <script src="./js/observer.js"></script>
  <script src="./js/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'hello world',
        count: 100,
        person: {
          name: 'zhangsan' 
        }
      }
    })
    console.log(vm)
    // vm.msg = {
    //   test: 'hello'
    // }

    // --测试,看数据变化视图有没有变化 (控制台 输入vm.msg = 456 )

    // --测试,修改表单内容,看数据有没有变化

  </script>

</body>

</html>

流程图

在这里插入图片描述

最小版本vue的实现代码

/js/dep.js
在这里插入图片描述

// dep对象用于收集依赖,将来数据变化的时候去发布通知
class Dep {
  constructor() {
    // 存储所有的观察者
    this.subs = []
  }
  addSub(sub) {
    // 判断是不是观察者
    if(sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 发布通知
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

/js/watch.js
在这里插入图片描述

// watch用于收集依赖,当依赖发生调用回调函数
class Watch {
  constructor(vm, key, cb) {
    this.vm = vm
    // data中属性名称
    this.key = key
    // 回调函数负责更新视图
    this.cb = cb


    // 把watcher对象记录到Dep的target静态属性中
    Dep.target = this
    // 触发get方法,在get方法中调用addSub
    this.oldValue = vm[key]
    // target设置为空,防止重复添加
    Dep.target = null 
  }
  // 当数据发生变化的时候更新视图
  update() {
    let newValue = this.vm[this.key]
    if(this.oldValue === newValue){
      return
    }
    this.cb(newValue)
  }
}

/js/compiler.js
在这里插入图片描述

// compiler用来解析指令和插值表达式
class Compiler {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    // 希望立即编译模板
    this.compile(this.el)
  }
  // 编译模板,处理文本节点和元素节点
  compile (el) {
    let childNodes = el.childNodes // 是伪数组
    Array.from(childNodes).forEach(node => {
      console.log(':::compile.start')
      console.log('node.nodeType', node.nodeType)
      console.dir(node)
      console.log(':::compile.end')
      if (this.isTextNode(node)) {
        console.log('node.textContent', node.textContent)
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        console.log('compileElement', node.attributes)
        this.compileElement(node)
      }

      // 判断node是否有子节点,如果有需要递归调用遍历子节点
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }
  // 编译元素节点,处理指令
  compileElement (node) {
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否有指令
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // v-text ==> text
        attrName = attrName.substr(2)
        console.log('attrName', attrName)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }
  update (node, key, attrName) {
    let updateFn = this[attrName + 'Update']
    updateFn && updateFn.call(this, node, key, this.vm[key]) // 调用.call改成我们需要指向的实例
  }
  // 处理v-text指令
  textUpdate (node, key, value) {
    node.textContent = value

    // 创建watcher对象(数据改变更新视图)
    new Watch(this.vm, key, (newValue) => {
      console.log('watcher callback textUpdate', newValue)
      node.textContent = newValue
    })
  }
  // 处理v-model指令
  modelUpdate (node, key, value) {
    node.value = value

    // 创建watcher对象(数据改变更新视图)
    new Watch(this.vm, key, (newValue) => {
      console.log('watcher callback modelUpdate', newValue)
      node.value = newValue
    })

    // 实现双向绑定(页面发生该改变 对应的数据也应该发生变化)
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
  // 编译文本节点,处理插值表达式
  compileText (node) {
    // console.dir(node)
    // {{ msg }}
    let reg = /\{\{(.+?)\}\}/ //匹配插值表达式
    let value = node.textContent
    // console.log('node.textContent', node.textContent)
    // console.log('reg.test(value)', reg.test(value))
    if (reg.test(value)) {
      let key = RegExp.$1.trim() //获取第一个匹配的项
      node.textContent = value.replace(reg, this.vm[key])

      // 创建watcher对象(数据改变更新视图)
      new Watch(this.vm, key, (newValue) => {
        console.log('watcher callback compileText', newValue)
        node.textContent = newValue
      })

    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {
    return attrName.startsWith('v-')
  }
  // 判断元素是不是文本节点
  isTextNode (node) {
    return node.nodeType === 3
  }
  // 判断元素是不是元素节点
  isElementNode (node) {
    return node.nodeType === 1
  }
}

/js/observer.js
在这里插入图片描述

// 对数据进行劫持,给属性添加setter和getter方法并发布通知
class Observer {
  constructor(data) {
    this.walk(data)
  }
  // walk用来遍历,如果是对象接着遍历
  walk(data) {
    // 判断data对象是否是对象
    if(!data || typeof data !== 'object') {
      return
    }
    // 遍历data对象所有的属性
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })

  }
  defineReactive(obj, key, val){
    let self = this
    let dep = new Dep() // 负责收集依赖并发送通知
    // 如果val是对象,也会将对象内的数据转换成响应式
    this.walk(val)
    Object.defineProperty(obj, key, {
      enumerable: true, // 可枚举
      configurable: true, // 可遍历
      get() {
        // 在get收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val // 不是obj[key], 因为会发生死递归而报错
      },
      set(newValue) {
        if(newValue === val) {
          return
        }
        val = newValue
        self.walk(newValue) // 把data中重新赋值的对象变成响应式
        // 在set发送通知
        dep.notify()
      }
    })
  }
}

/js/vue.js
在这里插入图片描述

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,注入到vue实例中
    this._proxyData(this.$data)
    // 3.调用observer对象,监听数据变化
    new Observer(this.$data)
    // 4.调用compiler对象,解析指令和插值表达式
    new Compiler(this)
  }
  _proxyData(data) {
    // 遍历data中的所有属性
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true, // 可枚举
        configurable: true, // 可遍历
        get() {
          return data[key]
        },
        set(newValue) {
          if(newValue === data[key]) {
            return
          }
          data[key] = newValue
        }
      })
    })

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值