vue解析指令compile源码层面使用解析

目录
  • 概述
  • compile
  • 测试代码
  • 结果
  • 延伸及重点讲解
    • 1. 类数组对象
    • 2. RegExp.$1
    • 3. nodeType

概述

上篇文章我们已经介绍了Vue的响应式原理,并实现了对数据的监听,监听的目的是为了及时更新视图,所以这篇文章就来介绍下vue解析指令并初始化视图部分。

compile

在Vue的构造函数中对根元素进行编译

class MVue {
  constructor (options) {
    // 保存options
    this.$options = options
    this.$data = options.data
    // 将data进行响应式处理
    observe(this.$data)
    // 代理
    proxy(this)
    // 编译
    /**核心部分**/
    new Compile(options.el, this)
  }
}
class Compile {
  constructor (el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    if (this.$el) {
      this.compile(this.$el)
    }
  }
  compile (node) {
    // 找到该元素的子节点
    const childNodes = node.childNodes
    // childNodes是类数组对象
    Array.from(childNodes).forEach(n => {
      // 判断类型
      if (this.isElment(n)) {
        this.compileElement(n)
        // 递归
        if (n.childNodes.length > 0) {
          this.compile(n)
        }
      } else if (this.isInter(n)) {
        // 动态插值表达式  编译文本
        this.compileText(n)
      }
    })
  }
  isElment (node) {
    return node.nodeType === 1
  }
  isInter (n) {
    return n.nodeType === 3 && /\{\{(.*)\}\}/.test(n.textContent)
  }
  isDir (attrName) {
    return attrName.startsWith('m-')
  }
  isEvent (attrName) {
    return attrName.startsWith('@')
  }
  // 编译元素:遍历它的所有属性,看是否m-开头指令,或者@事件
  compileElement (node) {
    const attrs = node.attributes
    Array.from(attrs).forEach(attr => {
      // m-text="XXX"
      // name = m-text, value=xxx
      const attrName = attr.name
      const exp = attr.value
      // 是指令
      if (this.isDir(attrName)) {
        // 执行特定指令处理函数
        const dir = attrName.substring(2)
        this[dir] && this[dir](node, exp)
      } else if (this.isEvent(attrName)) {
      	//是事件
      	const event = attrName.substring(1)
        // 通过bind改变this指向为vm实例,以便方法内部访问vm.data中的数据
        node.addEventListener(event, this.$vm.$methods[exp].bind(this.$vm))
      }
    })
  }
  compileText (n) {
    // 获取表达式
    // n.textContent = this.$vm[RegExp.$1]
    n.textContent = this.$vm[RegExp.$1.trim()]
  }
  text (node, exp) {
    node.textContent = this.$vm[exp] || exp
  }
  html (node, exp) {
    node.innerHTML = this.$vm[exp]
  }
  model (node, exp) {
    node.value = this.$vm[exp]
  }
}

测试代码

<!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">
        <p>{{ counter }}</p>
        <p m-text="测试"></p>
        <p m-text="counter"></p>
        <p m-html="desc"></p>
        <input m-model="name" />
    </div>
</body>
<script src="./index.js"></script>
<script>
    const app = new MVue({
            el: '#app',
            data: {
                counter: 1,
                desc: '<span style = "color: red">super wanan</span>',
                name: 'wanan'
            }
        })
        // setInterval(() => {
        //     app.counter++
        // }, 1000)
</script>
</html>

结果

延伸及重点讲解

1. 类数组对象

node.childNodes和node.attributes都是一种类数组对象,NodeList用于保存一组有序的节点。可以通过方括号语法来访问NodeList的值,有item方法与length属性。它并不是Array的实例,没有数组对象的方法。

比如我们再compile方法中打印childNodes,会得到这样的结果:

因此需要使用Array.from()方法将类数组对象转换为真正数组。

2. RegExp.$1

RegExp 是javascript中的一个内置对象。为正则表达式。

RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串,以此类推,RegExp.$2,RegExp.$3,…RegExp.$99总共可以有99个匹配

其实RegExp这个对象会在我们调用了正则表达式的方法后, 自动将最近一次的结果保存在里面, 所以如果我们在使用正则表达式时, 有用到分组, 那么就可以直接在调用完以后直接使用RegExp.$xx来使用捕获到的分组内容。

所以我们在compileText时可以这样使用:

  compileText (n) {
    // 获取表达式
    // n.textContent = this.$vm[RegExp.$1]
    n.textContent = this.$vm[RegExp.$1.trim()]
  }

需要注意的是,上述代码中还使用了trim方法去除前后空格,这是为了将{{ counter }}前后的空格去掉,正确匹配到this.vm[‘counter’],否则会读取this.vm[’ counter ']导致读取不到。

3. nodeType

DOM(文档对象模型)可以将任何HTML和XML文档描绘成一个由多层node(节点)构成的结构。

DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中作为Node类型实现的。除了IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享相同的基本属性和方法。每个节点都有一个nodeType属性,用于表明节点的类型。

nodeType的取值如下:

类型 nodeType常数值 描述
Node.ELEMENT_NODE 1 元素节点
Node.ATTRIBUTE_NODE 2 属性节点
Node.TEXT_NODE 3 文本节点
Node.CDATA_SECTION_NODE 4 字符数据节点(文本不会被解析器解析)
Node.ENTITY_REFERENCE_NODE 5 实体引用节点
Node.ENTITY_NODE 6 实体节点
Node.PROCESSING_INSTRUCTION_NODE 7 处理指令节点
Node.COMMENT_NODE 8 注释节点
Node.DOCUMENT_NODE 9 文档节点(DOM树的根节点)
Node.DOCUMENT_TYPE_NODE 10 向为文档定义的实体提供接口
Node.DOCUMENT_FRAGMENT_NODE 11 表示邻接节点和它们的子树
Node.NOTATION_NODE 12 代表一个符号在DTD中的声明

到此这篇关于vue解析指令compile源码层面使用解析的文章就介绍到这了,更多相关vue compile内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue 中的compile操作方法

    在 Vue 里,模板编译也是非常重要的一部分,里面也非常复杂,这次探究不会深入探究每一个细节,而是走一个全景概要,来吧,大家和我一起去一探究竟. 初体验 我们看了 Vue 的初始化函数就会知道,在最后一步,它进行了 vm.$mount(el) 的操作,而这个 $mount 在两个地方定义过,分别是在 entry-runtime-with-compiler.js(简称:eMount) 和 runtime/index.js(简称:rMount) 这两个文件里,那么这两个有什么区别呢? // entr

  • Vue编译器解析compile源码解析

    目录 引言 解析 compile compile 源码 配置选项 属性分别解析 finalOptions添加warn 方法 两个特殊的属性处理 引言 在上篇文章 Vue编译器源码分析compileToFunctions作用中我们介绍到了,在 compileToFunctions 方法中: // compile var compiled = compile(template, options); 而真正的编译工作是依托于 compile 函数,接下来我们详细解析 compile . 解析 comp

  • vue原理Compile从新建实例到结束流程源码

    目录 引言 1 获取 template 模板 2 生成 render 3 保存 render createCompiler baseCompile createCompilerCreator 生成一个函数 compile 返回 compileToFunctions 和 compile 但是为什么分出一个 compileToFunctions 呢? 因为要做模板缓存!! createCompileToFunctionFn 他的缓存是怎么做的 理清思路 内部函数的作用是 引言 Compile 的内容

  • Vue编译器源码分析compileToFunctions作用详解

    目录 引言 Vue.prototype.$mount函数体 源码出处 options.delimiters & options.comments compileToFunctions函数逐行分析 createFunction 函数源码 引言 Vue编译器源码分析 接上篇文章我们来分析:compileToFunctions的作用. 经过前面的讲解,我们已经知道了 compileToFunctions 的真正来源你可能会问为什么要弄的这么复杂?为了搞清楚这个问题,我们还需要继续接触完整的代码. 下面

  • vue原理Compile之optimize标记静态节点源码示例

    目录 引言 而 optimize 的作用是什么呢? 1 是否存在 v-pre 2 不能存在 node.hasBindings 3 不能存在 node.if 和 node.for 4 节点名称不能是slot 或者 component 5 isPlatformReservedTag(node.tag) 6 isDirectChildOfTemplateFor(node) 7 Object.keys(node).every(isStaticKey) 标记静态节点 1 isStatic 这个方法对 as

  • 详解如何解决Vue和vue-template-compiler版本之间的问题

    今天把远程仓库拉下项目,运行'npm run dev'时,报错 Module build failed: Error: Cannot find module 'vue-template-compiler' 报错原因:通常出现于一些依赖库的更新或者安装新的依赖库之后(可以认为npm update已经成为一种习惯),导致了vue和vue-template-compiler的版本不一致. 解决方案:统一vue和vue-template-compiler的版本 "vue": "2.3

  • 解决vue安装less报错Failed to compile with 1 errors的问题

    1.创建vue项目后安装less,执行 npm install less less-loader --save-dev 下载版本为:less-loader@6.1.0 , less@3.11.3,重启服务报错,报错信息如下: 2.报错原因 less 本版太高需要降低版本,执行代码 先移除之前版本: npm uninstall less-loader 下载指定版本: npm install less-loader@5.0.0 -D 3.重启代码就可以了,若还是报错可移除文件node_modules

  • vue解析指令compile源码层面使用解析

    目录 概述 compile 测试代码 结果 延伸及重点讲解 1. 类数组对象 2. RegExp.$1 3. nodeType 概述 上篇文章我们已经介绍了Vue的响应式原理,并实现了对数据的监听,监听的目的是为了及时更新视图,所以这篇文章就来介绍下vue解析指令并初始化视图部分. compile 在Vue的构造函数中对根元素进行编译 class MVue { constructor (options) { // 保存options this.$options = options this.$d

  • android异步消息机制 源码层面彻底解析(1)

    Handler.Message.Loopler.MessageQueen 首先看一下我们平常使用Handler的一个最常见用法. Handler handler =new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //这里进行一些UI操作等处理 } new Thread(new Runnable() { @Override public void run() {

  • Vue中$nextTick实现源码解析

    目录 正文 先看一个简单的问题 内部实现 先看第一块: 再看第二块: 然后是第三块: 最后是第四块: 正文 先看一个简单的问题 <template> <div @click="handleClick" ref="div">{{ text }}</div </template> <script> export default { data() { return { text: 'old' } }, methods:

  • vue从使用到源码实现教程详解

    搭建环境 项目github地址 项目中涉及了json-server模拟get请求,用了vue-router: 关于Vue生命周期以及vue-router钩子函数详解 生命周期 1.0版本 1.哪些生命周期接口 init Created beforeCompile Compiled Ready Attatched Detached beforeDestory destoryed 2.执行顺序 1. 不具有keep-alive 进入: init->create->beforeCompile->

  • 如何获取vue单文件自身源码路径

    这个问题要从一个想法说起. D2Admin是一个开源的,前端中后台集成方案,原先是基于 vue-cli2,大概是向 vue-cli3 过渡时, 作者老李,想在页面右下角加个 Toggle 点击,跳到当前页面源码对应的 github 页面. 确实很实用的功能,D2Admin 的 Demo 页面太多了,想看某个页面的源码,对于不熟悉项目目录结构的新手很不友好. 这些页面统一为 .vue 组件,那么转换一下:如何获取 vue 单文件自身源码路径? 目前经历了三个方案,最终目标是把自身路径赋值到 thi

  • 详解Element 指令clickoutside源码分析

    clickoutside是Element-ui实现的一个自定义指令,顾名思义,该指令用来处理目标节点之外的点击事件,常用来处理下拉菜单等展开内容的关闭,在Element-ui的Select选择器.Dropdown下拉菜单.Popover 弹出框等组件中都用到了该指令,所以这个指令在实现一些自定义组件的时候非常有用. 要分析该源码,首先要了解一下Vue的自定义指令.自定义指令的定义方式如下: // 注册一个全局自定义指令 Vue.directive('directiveName', { bind:

  • 高分面试从Hotspot源码层面剖析java多态实现原理

    目录 C++是如何实现多态的 JVM中的虚表 Java是如何实现虚表分发 本篇文章是接上篇文章[JVM的多态是如何实现的]写的,如果你还没看过,墙裂都建议你看一下. 传送门  高分面试分析jvm如何实现多态 上篇文章我给出了这道面试题的及格分的回答及七八十分的回答,今天我就告诉大家如果想回答得接近满分,应该怎么回答.因为会设计到C++的虚表及C++的多态实现,如何这块你不理解或不熟,面试中建议别拿出来说,免得碰到懂C++给你来个连环call把你问懵了. 这边给大家补一个知识点.我在昨天的文章里说

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • vue 2.5.1 源码学习 之Vue.extend 和 data的合并策略

    1. 子类父类 2.Vue.extend()      //创建vue的子类 组件的语法器 Vue.extend(options) Profile().$mount('#app') // 挂在app上,并替换app 新建 initExend ==> Vue.extend 3. strat.data ==> if(!vm){子组件中data的值是一个方法function ==> mergeDataorFn()} // 数据的合并 ==> else {} //通过实例绑定的data 实

随机推荐