Vue.js - 模拟Vue.js响应式原理(2/2)

项目仓库:https://gitee.com/big-right-right/vue-responsive/tree/master/L8

一. 类的说明

模块说明

Vue类 :保存传入的选项数据,把选项data中的成员注入vue实例(可用this.msg访问),调用observer对象监听数据变化,调用compiler对象解析指令和差值表达式。

Observer类:把data选项中属性转换成响应式数据(若data某个属性是对象,该对象的属性也具有响应式),数据变化发送通知。

Compiler类:负责编译模板并解析指令和差值表达式,负责页面视图首次渲染,以及数据变化后的重新渲染。

Dep类:观察者模式中的发布者,用于记录所有观察者,当收到数据变化时发送通知给所有观察者。

Watcher类:观察者模式中的观察者,当收到Dep发送的通知时,执行自己的update方法来更新视图。

 

二. 项目代码

1. index.html

<html>
<head>
</head>
<body>
  <h1>Vue响应式原理-分析</h1>

  <div id="app">
    <h2>差值表达式</h2>
    <h3>{{ msg }}</h3>
    <h3>{{ count }}</h3>

    <h2>v-text</h2>
    <div v-text="msg"></div>

    <h2>v-model</h2>
    <input type="text" v-model="msg">
    <input type="text" v-model="count">
  </div>

  <script src="./dep.js"></script>
  <script src="./watcher.js"></script>
  <script src="./compiler.js"></script>
  <script src='./observer.js'></script>
  <script src='./vue.js'></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello vue',
        count: 22,
        person: {name: 'zhangsan'}
      }
    })
    console.log(vm)
    // vm.person = { abc: 'abc' }
    // console.log(vm)
  </script>
</body>
</html>

2. vue.js

/* 实现最小版本的 Vue */
/*
Vue
 $optinos
 $el
 $data
 _proxyData()
*/
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) {
    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
        }
      })
    })
  }
}

3. observer.js

/*
Observer
 把data选项中的属性转换成响应式数据
 若data中的某个属性也是对象,把该属性转换成响应式数据
 数据变化发送通知
*/
class Observer {
  constructor(data) {
    this.walk(data)
  }
  walk(data) {
    // 1. 判断data是否是对象
    if(!data || typeof data !== 'object') {
      return
    }
    // 2. 遍历data对象的所有属性
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }
  defineReactive(obj, key, val) {
    const that = this
    // 负责收集依赖 并发送通知
    let dep = new Dep()

    // 如果data对象的某个属性也是对象 则该子对象的每个属性也需要响应式
    this.walk(obj[key])
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
      },
      set(newVal) {
        if(newVal === val) {
          return
        }
        val = newVal
        // 如果data对象的某个属性被重新赋值 则该属性需要重新设置响应式
        that.walk(newVal)
        // 发送通知
        dep.notify()
      }
    }) 
  }
}

4. 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 => {
      // 处理文本节点
      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)
      }
    })
  }
  // 编译元素节点 处理指令
  compileElement(node) {
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是指令
      let attrName = attr.name
      if(this.isDirective(attrName)) { // v-text v-model
        attrName = attrName.substr(2)  // text model
        let key = attr.value           // msg count
        this.update(node, key, attrName)
      }
    })
  }
  update(node, key, attrName) {
    let 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, (newValue) => {
      node.textContent = newValue
    })
  }
  // 处理 v-model 指令
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }
  // 编译文本节点,处理差值表达式
  compileText(node) {
    let reg = /\{\{(.+?)\}\}/
    let value = node.textContent
    if(reg.test(value)) {
      let key = RegExp.$1.trim()
      console.log('key: ', key)
      node.textContent = value.replace(reg, this.vm[key])
    
      // 创建watcher对象,当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断元素是否是指令
  isDirective(attrName) {
    return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }
  // 判断节点是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1
  }
}

5. dep.js

class Dep {
  constructor () {
    // 存储所有的观察者
    this.subs = []
  }
  // 添加观察者
  addSub (sub) {
    if(sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 发起通知
  notify () {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

6. watcher.js

class Watcher {
  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]
    Dep.target = null
  }
  // 当数据发生变化时更新视图
  update () {
    let newValue = this.vm[this.key]
    if(this.oldValue === newValue) {
      return
    }
    this.cb(newValue)
  }
}

本文 完。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js是一款流行的JavaScript框架,用于构建现代化的用户界面。它提供了响应数据绑定和组件化的开发模,使开发者可以更轻松地构建交互丰富的应用程序。 而xterm.js是一个基于Web的终端模拟器,它可以在浏览器中模拟一个命令行界面,支持输入和输出文本,以及一些常见的终端命令。Vue.js和xterm.js可以很好地结合在一起,以实现在浏览器中模拟终端的功能。 Spring Boot是一个用于快速构建Java应用程序的框架,它提供了自动化配置和约定优于配置的原则,使开发者可以更快地搭建Java Web应用。Spring Boot支持各种后端技术,包括Spring MVC、Spring Data、Spring Security等。 结合Vue.js、xterm.js和Spring Boot,我们可以实现一个完整的Web应用程序,具备交互终端的功能。用户可以在浏览器中输入命令,并由xterm.js模拟终端效果,然后通过Vue.js将输入的命令发送到后端的Spring Boot应用处理。后端应用可以执行相应的逻辑,并将结果返回给前端显示。 这样的应用程序可以用于各种场景,比如远程服务器管理、日志查看、文件操作等。使用Vue.js和xterm.js可以使用户体验更加友好和直观,而Spring Boot则提供了强大的后端支持,使开发过程更加高效。 总之,将Vue.js、xterm.js和Spring Boot结合起来,可以构建出一个功能强大且用户友好的Web应用程序,实现在浏览器中模拟终端的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值