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相关知识哦(●'◡'●)。 如果不足,请多指教。

目录
相关文章
|
3天前
|
JavaScript
vue页面加载时同时请求两个接口
vue页面加载时同时请求两个接口
|
3天前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式
12 0
|
3天前
|
移动开发 JavaScript 应用服务中间件
vue打包部署问题
Vue项目`vue.config.js`中,`publicPath`设定为&quot;/h5/party/pc/&quot;,在线环境基于打包后的`dist`目录,而非Linux的`/root`。Nginx代理配置位于`/usr/local/nginx/nginx-1.13.7/conf`,包含两个相关配置图。
vue打包部署问题
|
3天前
|
JavaScript 前端开发
iconfont 图标在vue里的使用
iconfont 图标在vue里的使用
15 0
|
2天前
|
JavaScript
vue打印v-model 的值
vue打印v-model 的值
|
3天前
|
JavaScript
Vue实战-组件通信
Vue实战-组件通信
5 0
|
3天前
|
JavaScript
Vue实战-将通用组件注册为全局组件
Vue实战-将通用组件注册为全局组件
5 0
|
3天前
|
JavaScript 前端开发
vue的论坛管理模块-文章评论02
vue的论坛管理模块-文章评论02
|
3天前
|
JavaScript Java
vue的论坛管理模块-文章查看-01
vue的论坛管理模块-文章查看-01
|
3天前
|
资源调度 JavaScript 前端开发
vue 项目运行过程中出现错误的问题解决
vue 项目运行过程中出现错误的问题解决