vue核心效果的实现

vue核心效果的实现

1.此次实现了vue的模板语法的编译({{}}),包括v-text, v-html, v-bind, v-model, v-on, 以及绑定时间的简写(@)和v-bind的简写(:);

2.实现了数据的响应式更新,observer数据劫持,watcher依赖收集 Dep 类管理wather

vue.js 主要实现编译效果 observer.js 实现数据的劫持及响应式更新;代码如下:

// Vue.js  2020.12.29 wangchuxian


class Vue {

  constructor(options){
    this.$el = options.el
    this.$data = options.data
    this.$options = options
    this.$methods = options.methods
    if(this.$el){
      new Observer(this.$data)   //数据劫持
      new Compile(this.$el, this)  //模板编译
    }

    this.proxyData(this.$data)  // 代理this.$data数据 可以直接通过 this.obj 直接访问(this.$data.obj)
  }

  proxyData(data){

    for(let key in data){
      Object.defineProperty(this, key, {  //this指向vue实例,即当访问vue下的数据时,可以从$data数据中查找
        set:(newVal)=>{
          data[key] = newVal
        },
        get(){
          return data[key]
        }
      })
    }
  }
}

class Compile {

  constructor(el, vm){
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm

    // 为了减少页面的回流和重绘,创建文档碎片。

    let fragment = this.nodeToFragment(this.el)

    this.compile(fragment)

    // 在插入节点前,先要对节点进行处理,解析v-命令及{{}}语法
    this.el.appendChild(fragment)


  }

  compile(node){

    let childNodes = [...node.childNodes]
    childNodes.forEach(child=>{

      if(this.isElementNode(child)){
        
        this.compileElement(child)
      }else{

        this.compileText(child, this.vm)
      }

      if(child.childNodes && child.childNodes.length) { //如果子节点里还有元素节点, 再次对子节点进行编译

        this.compile(child)
      }
    })
  }

  compileElement(el){

    let attributes = [ ...el.attributes ]

    attributes.forEach(attr =>{
      let {name, value} = attr
      // 判断是否是vue的指令
      if(this.isRedirect(name)){

        let [, directive] = name.split('-') //获取指令 如v-text 中的text  v-html 中的html v-on:click 中的on:click
        let [dirName,eventName] = directive.split(':')  //获取事件类型 如 click
        compileUtil[dirName](el, value, this.vm, eventName)
        el.removeAttribute('v-' + directive) // 删除标签中的v-属性

      }else if(this.isEvent(name)){   //兼容@方法绑定事件

        let [, eventName] = name.split('@')
        compileUtil['on'](el, value, this.vm, eventName)
        el.removeAttribute(name) // 删除标签中的v-属性

      }else if(this.isBindInfo(name)){ //兼容v-bind的 :简写

        let [, eventName] = name.split(':')
        compileUtil['bind'](el, value, this.vm, eventName)
        el.removeAttribute(name) // 删除标签中的v-属性
      }
    })
  }

  compileText(textNode,vm){ //解析{{}}语法
    let reg = /\{\{(.+?)\}\}/g
    let textContent = textNode.textContent

    function getContentValue(expr, vm){

      return expr.replace(reg, (...args)=>{

        return compileUtil.getValue(vm, args[1])
      })
    }

    if(reg.test(textContent)){

      let value = textContent.replace(reg, (...args) => {

        new Watcher(vm, args[1], (newValue)=>{ //创建watcher

          console.log(getContentValue(args[1], vm) )
          textNode.textContent = getContentValue(textContent, vm)       //更新视图 newValue变化后的值
        })
        return compileUtil.getValue(vm, args[1])
      })

      textNode.textContent = value
    }
  }

  isBindInfo(name){
    return name.startsWith(':')
  }

  isEvent(name){

    return name.startsWith('@')
  }

  isRedirect(name){

    return name.startsWith('v-')
  }

  isElementNode(node){

    return node.nodeType === 1
  }

  nodeToFragment(el){

    let fragment = document.createDocumentFragment()
    while( el.firstChild ){
      fragment.appendChild(el.firstChild )
    }
    return fragment
  }
}

const compileUtil = {

  // 处理v-text 命令
  text(node, val, vm){

    let value = this.getValue(vm, val)
    node.textContent = value
    new Watcher(vm, val, (newValue)=>{ //创建watcher  在new watcher() 的同时 将watcher 放入 dep  watcher管理中
      node.textContent = newValue        //更新视图 newValue变化后的值
    })
  },

  // 处理v-html 命令
  html(node, val, vm){
    let value = this.getValue(vm, val)
    node.innerHTML = value  //value 初始化视图的数据
    new Watcher(vm, val, (newValue)=>{ //创建watcher
      node.innerHTML = newValue        //更新视图 newValue变化后的值
    })
    
  },

  // 处理v-model 命令
  model(node, val, vm){
    let value = this.getValue(vm, val)
    node.value = value
    new Watcher(vm, val, (newValue)=>{ //创建watcher
      node.value = newValue        //更新视图 newValue变化后的值
    })

    node.addEventListener('input', (e) =>{  //处理双向绑定 所以说v-model 是v-bind和input事件的语法糖

      let newVal = e.target.value
      this.setValue(vm, val, newVal)
    })
  },

  bind(node, val, vm, eventName){  //处理v-bind 命令

    let value = this.getValue(vm, val)
    node.setAttribute(eventName, value)
    new Watcher(vm, val, (newValue)=>{ //创建watcher
      node.setAttribute(eventName, newValue)        //更新视图 newValue变化后的值
    })
  },

  // 处理v-on 事件处理
  on(node, val, vm, eventName){

    node.addEventListener(eventName, vm.$methods[val])
  },


  getValue(vm,val){

    return val.split('.').reduce((data, current) =>{  //循环获得深层级的数据 如data.obj.val.key

      return data[current]
    }, vm.$data)
  },

  setValue(vm, attr, val){

    attr.split('.').reduce((data, current) =>{

      data[current] = val
    }, vm.$data)
  }
}

observer.js 如下


class Observer{

  constructor(data){

    this.observer(data)
  }

  observer(data){

    if(data && typeof data === 'object'){

        Object.keys(data).forEach(key =>{

          this.defineReactive(data, key, data[key])
        })
    }
  }

  defineReactive(obj, key, val){

    this.observer(val)  // val 可能还是对象,递归劫持
    //在数据劫持的时候创建dep
    // let dep = new Dep()
    Object.defineProperty(obj, key, {
      enumerable:true,
      configurable: false,
      get(){

        return val
      },
      set: (newValue) =>{
        this.observer(newValue)
        if(newValue !== val){
          val = newValue //更改数据
          dep.notify()   //通知更改视图
        }
      }
    })
  }
}

class Dep {

  constructor(){

    this.subs = []  //管理watcher 的数组
  }

  addSub(watcher){

    this.subs.push(watcher)  //添加watcher
  }

  notify(){  //通知观察者更新

    this.subs.forEach(w => w.update())
  }
}

let dep = new Dep()

class Watcher {
  constructor(vm, expr, cb){
    this.vm = vm
    this.expr = expr
    this.cb = cb
    this.oldValue = this.getOldValue()
    dep.addSub(this)
  }

  update(){

    let newValue = compileUtil.getValue(this.vm, this.expr) //vue.js 中申明的工具函数得到对应的值  在调用update函数时(更新视图),数据已经更改,所以得到的是新的值
    if(newValue !== this.oldValue){
      this.cb(newValue)  //调用更新视图函数
    }
  }

  getOldValue(){

    return compileUtil.getValue(this.vm, this.expr) //vue.js 中申明的工具函数得到对应的值
  }
}

效果查看,和vue的使用方法一致,如:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h1>{{title}}------{{html_txt}}</h1>
    <input type="text" v-model="input_text">
    <ul v-bind:hello="bind">
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    <div v-html="html_txt"></div>
    <div v-text="text_txt"></div>
    <button v-on:click="handleClick">点击</button>
    <button @click="handleClick">点击</button>
  </div>
</body>
</html>
<script src="vue.js"></script>
<script src="observer.js"></script>
<script>

  let VM = new Vue({
    el:"#app",
    data:{
      title: '这是一个标题',
      input_text:'input 输入框',
      html_txt: '这是v-html的内容',
      text_txt: '这是v-text指令的内容',
      bind: 'bind 属性'
    },

    methods:{

      handleClick(){

        console.log(999)
      }
    }
  })
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值