vue那些指令实现你还不会嘛?

简介: 上篇文章数据搞到页面上展示已经成功将文本节点的{{ 值 }}用DVue内部的变量提换。这篇文章将接着上文内容,将实现一些基本指令,以及简单的更新操作。

网络异常,图片无法展示
|


上篇文章数据搞到页面上展示已经成功将文本节点的{{ 值 }}用DVue内部的变量提换。这篇文章将接着上文内容,将实现一些基本指令,以及简单的更新操作。


初始化就可以看出来的指令


就拿vue来说,在编译过程中的元素节点上,可能会存在一些特殊的指令符号。


网络异常,图片无法展示
|


在此,以v-text、v-html、@click、v-model指令为例。


想要实现指令的编译,我们就需要在编译子节点时,对拿到的字节属性进行处理。通过attributes获取好当前节点的属性,将其变成数组,遍历其各自的属性值和属性名。


// Compile类中
if (isNode(child)) {
  // 元素
  // 解析动态指令 属性绑定、事件监听
  const childAttrs = child.attributes
  Array.from(childAttrs).forEach((attr) => {
    const attrName = attr.name
    const exp = attr.value
    if (this.isDir(attrName)) {
      console.log(exp, attrName) //  name d-model   age d-text   html d-html 
      const dir = attrName.slice(2)
      this[dir] && this[dir](child, exp)
    }
  })
  if (child.childNodes.length > 0) this.compiler(child)
}
function isDir(dir) {
  return dir.startsWith('d-')
}


判断指令是不是以d-开头的(isDir函数),将它的属性名从第二位开始截取,获得属性名的函数(比如d-text获得text),如果存在就执行该函数。


d-text、d-html 实现


实现


编写html函数和text函数。text函数就是将其内部的文本变量替换,可直接使用节点的textContent属性,用DVue中的对应变量替换旧的文本内容。

html(node, exp) {
  node.innerHTML = this.$vm[exp]
  console.log(node, exp)
  node.removeAttribute('d-html')
}
text(node, exp) {
  node.textContent = this.$vm[exp]
}


对于html它是将变量值以html节点添加到当前节点的内部节点,在使用

removeAttribute删除节点对应属性。


进一步优化


对于公共的处理指令函数,我们可以提取共同的初始化函数(update:用于初始化和更新)。


update(node, exp, dir) {
  //初始化
    const fn = this[dir + 'Updater']
    fn && fn(node, this.$vm[exp])
    // 更新
  }
  html(node, exp) {
    // node.innerHTML = this.$vm[exp]
    this.update(node, exp, 'html')
    node.removeAttribute('d-html')
  }
  htmlUpdater(node, val) {
    node.innerHTML = val
  }


将其拆分成三个方法,以便于后期更新操作。到此,我们的d-text、d-html就已完成。


网络异常,图片无法展示
|


更新操作


vue的更新,上上一次的经典图:


网络异常,图片无法展示
|

这一次,我们需要去完成Watcher这个功能。它负责具体的节点更新。初始化Watcher。


watcher


采用全量更新,先不使用dep管理。


定义一个watchers用来存放watcher的数组,在编译时,创建一个个的watcher实例,把他们都放到一个watchers中。 在相关变量改变时,遍历触发更新。


const watchers = []
// 负责具体节点更新
class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    this.updater = updater
    watchers.push(this)
  }
  update() { // 更新对应相关的key
    this.updater.call(this.vm, this.vm[this.key])
  }
}
function defineReactive(obj, key, val) {
  observe(val)
  Object.defineProperty(obj, key, {
   ......
    set(newVal) {
      if (newVal !== val) val = newVal
      observe(newVal)
      watchers.forEach((w) => w.update())  // 遍历更新
    },
  })
}

网络异常,图片无法展示
|


可以看到图中的name在定时器的作用下,展示的值得到了改变。


Dep


Dep和响应式的属性key之间一一对应关系。使用Dep对watcher进行管理。

初始化dep,Dep中应该存在一个数组用于管理收集的watcher。并且存在收集watcher和触发更新的方法。


class Dep {
  constructor() {
    this.deps = []
  }
  addDep(dep) {
    this.deps.push(dep)
  }
  notify() {
    this.deps.forEach((w) => w.update())
  }
}


  • 在拦截每个变量时,创建相对应的Dep。
  • 在编译创建watcher实例时,将watcher用Dep的某个变量存放起来(target),读取变量时,用dep收集watcher。存放完成后,将target属性删除。
  • 在相关变量发生变化时,触发dep的更新操作(更新watcher)。


function defineReactive(obj, key, val) {
  observe(val)
  const dep = new Dep()  // 1、 创建实例
  Object.defineProperty(obj, key, {
    get() {
      // console.log('get', key)
      Dep.target && dep.addDep(Dep.target)  // 2.2、收集
      return val
    },
    set(newVal) {
      if (newVal !== val) val = newVal
      observe(newVal)
      // watchers.forEach((w) => w.update())
      dep.notify()  // 3、触发依赖
    },
  })
}
class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    this.updater = updater
    // watchers.push(this)
    Dep.target = this  // 2、保存watcher,读取变量收集依赖
    this.vm[this.key]
    Dep.target = null
  }
  update() {
    this.updater.call(this.vm, this.vm[this.key])
  }
}
class Dep {
  constructor() {
    this.deps = []
  }
  addDep(dep) {
    this.deps.push(dep)
  }
  notify() {
    this.deps.forEach((w) => w.update())
  }
}


更新后方便查看的指令


在更新之后更容易查看的指令比如:事件的监听@click、v-model。


@click 实现


在动态编译指令时,我们还需要考虑当前的属性是否存在事件监听。

如果当前的属性为事件监听,那么我们就需要在当前的节点上添加事件监听(addEventListener)。


compiler(el) {
    const childNodes = el.childNodes
    childNodes.forEach((child) => {
      if (isNode(child)) { // 元素
        // 解析动态指令 属性绑定、事件监听
        const childAttrs = child.attributes
        Array.from(childAttrs).forEach((attr) => {
          const attrName = attr.name
          const exp = attr.value
         ...
          // 事件
          if (this.isEvent(attrName)) {
            const dir = attrName.slice(1)
            this.eventHandler(child, exp, dir) // <div d-text="age" @click='add'></div>  "add" "click"
          }
        })
        ...
      } 
      ...
    })
}
isEvent(name) {
    return name.indexOf('@') == 0
  }
eventHandler(node, exp, dir) {
  const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
  node.addEventListener(dir, fn.bind(this.$vm))
}


特别现需要注意的是在添加事件监听时,可能需要使用到当前的实例内的变量或方法,需要改变当前的this指向,把实例传入。


成果展示:

网络异常,图片无法展示
|


d-model 实现


model指令是一个双向绑定的指令,需要实现将其值展示到页面,在input内部修改时,其相关展示的变量改变。可以转化成value值的设定和事件监听两个功能。


model和html、text指令相似,将其拆分成三个方法,共用一个update方法。


model(node, exp) {
    this.update(node, exp, 'model')
  }
  modelUpdater(node, val) {
  // 表单元素赋值
    node.value = val
  }


初始化完成:

网络异常,图片无法展示
|


在input中,需要对当前进行一个事件监听。


model(node, exp) {
  this.update(node, exp, 'model')
  // 事件监听
  node.addEventListener('input', (e) => (this.$vm[exp] = e.target.value))
}


在输入框输入时,将输入的内容重新赋值给这个变量。就可以看到输入框绑定的值与相关变量联动。


网络异常,图片无法展示
|


感兴趣的朋友可以关注 Vue源码初识专栏,会持续输出vue相关知识哦(●'◡'●)。 如果不足,请多指教。

目录
相关文章
|
8天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
vue学习第四章
|
8天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
vue学习第九章(v-model)
|
7天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
vue学习第十章(组件开发)
|
13天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
13天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
13天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
13天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
13天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
14天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
13天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉