博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Vue 模板解释
阅读量:5289 次
发布时间:2019-06-14

本文共 4193 字,大约阅读时间需要 13 分钟。

Vue 模板解释

  如今的前端三大框架都有它们独特的模板,模板的作用就是让开发编码变得更加简单,然而我觉得 Vue 在这一点上是做得近乎完美的(当然,只是个人观点~~),Vue 模板解释的核心不外乎就是两个玩意儿,一个是双大括号表示式,另一个是模板指令,这两东西也是我们在 Vue 项目中都肯定会用到的,下面就来详细介绍他们是如何实现的。

(一)创建模板解释对象

function Vue(options) {    // 将配置对象保存在实例对象上    this.$options = options    // 将配置对象里面的data属性保存在实例对象上    let data = this._data = options.data    // 保存实例对象,其实也可以用箭头函数~~    let me = this    // 遍历data中的属性,逐一实现数据劫持    Object.keys(data).forEach(function (key) {        me._proxy(key)    })    // 模板解释    this.$compile = new Compile(options.el || document.body,this)}

可见,模板解释是在数据劫持之后实现的,在实现完数据劫持后,创建模板解释对象,并且保存到实例对象中,这里面有两个参数,第一个就是配置对象中的 el ,也就是挂载的 DOM ,第二个就是 vm 。

(二)通过 Fragment 容器实现初始化

function Compile(el, vm) {    // 保存vm    this.$vm = vm    // 保存el,判断是否是元素节点,如果不是则尝试通过选择器字符来解释    this.$el = this.isElementNode(el) ? el : document.querySelector(el)    // 确保$el存在    if(this.$el){        // 1. 取出el中所有子节点, 封装在一个fragment对象中        this.$fragment = this.node2Fragment(this.$el)        // 2. 编译fragment中所有层次子节点,这个就是模板编译的核心方法~~~        this.init()        // 3. 将fragment添加到el中        this.$el.appendChild(this.$fragment)    }}

初始化的过程也是很容易理解,分三步,先将所有的元素转移到 fragment 容器中,然后在 fragment 容器中进行初始化,最后将这个 fragment 容器塞回原处。其实 fragment 容器并不进入页面,这里塞回去的仅仅是那些给初始化的节点而已。上面用到的三个定义在原型上的函数,isElementNode 用于判断是否是元素节点;node2Fragment 用于将节点中的所有子节点转移到 fragment 容器中,init 是初始化的核心函数,用于初始化模板数据:

Compile.prototype = {    // 将节点中的所有子节点转移到fragment容器中    node2Fragment:function(node){        // 创建一个fragment对象        let fragment = document.createDocumentFragment()        // 循环将元素节点中的所有子节点塞入fragment容器中,最终返回塞满子节点的fragment对象        let child        while(child = node.firstChild){            fragment.appendChild(child)        }        return fragment    },    // 判断是否是元素节点    isElementNode:function (node) {        return node.nodeType === 1    }}

(三)初始化,详解 init 方法

Compile.prototype = {    init:function(){        // 编译函数        this.compileElement(this.$fragment)    },    compileElement:function(el){        // 获取所有子节点        const childNodes = el.childNodes        // 保存compile对象        const me = this        // 将类数组转化为真数组,遍历所有子节点        Array.prototype.slice.call(childNodes).forEach(function (node) {            // 得到节点的文本内容            const text = node.textContent            // 定义正则表达式,用于匹配大括号表达式            const reg = /\{\{(.*?)\}\}/            // 元素节点            if(me.isElementNode(node)){                // 编译元素节点的指令属性                me.compile(node)            }else if(me.isTextNode(node) && reg.test(text)){ // 如果是一个大括号表达式的文本节点                me.compileText(node,RegExp.$1)            }            // 如果子节点还有子节点,递归调用            if(node.childNodes && node.childNodes.length){                me.compileElement(node)            }        })    },}

首先,init 方法去调用了compileElement 方法,该方法的主要作用就是处理之前准备好的 fragment 容器,将容器中所有子节点取出,然后进行分类处理,如果是一个元素节点,就去编译元素节点中的指令,如果是一个大括号表达式的文本节点,就去编译大括号表达式;如果节点里面还有子节点,则递归调用。顺着这个思路,先来研究比较简单的大括号表达式的情况(就是compileText这个方法):

Compile.prototype = {    // 编译大括号表达式,参数node代表节点,exp代表表达式(就是正则匹配到的那个东西)    compileText:function(node,exp){        compileUtil.text(node, this.$vm, exp)    }}const compileUtil = {    // 解释 v-text 和 双大括号表达式,由此也可以看出其实双大括号表达式跟v-text指令的实现原理是一致的!    text:function (node, vm, exp) {        this.bind(node,vm,exp,'text')    },    // 真正用于解释指令的函数    bind:function (node, vm, exp, dir) {        // 获取更新函数        const updaterFn = updater[dir + 'Updater']        updaterFn && updaterFn(node,this._getVMVal(vm,exp))    },    // 得到表达式对应的value    _getVMVal:function (vm, exp) {        let val = vm._data        exp = exp.split('.')        exp.forEach(function (key) {            val = val[key]        })        return val    }}// 更新器const updater = {    // 更新节点的textContent    textUpdater:function (node, value) {        node.textContent = typeof value === 'undefined' ? '' : value    }}

从代码和注释上已经很好的说明了整个流程了,这里再简单的啰嗦一下吧,其实我们用的双大括号表达式也是一种指令,因为它跟v-text的处理是完全一致的,都是在操作节点的textContent属性。可能会让人迷糊的是 _getVMVal函数吧,这个函数的作用就是处理多层次对象的,因为表达式不会仅仅是一层的,也可能是两层或者多层次的,比如,data里面保存了一个person对象,里面还有name等其他属性,然而我们很可能会在表达式里面写person.name这样类似的多层次的属性(说句题外话,vue 不会监听到对象内部属性的变化,如果是简单的通过对象.属性名的方式去改变对象,那么vue是不知道的~~),这个函数也正是用于处理这种结构的。因为双大括号跟其他指令都很是类似的思想,都是在操作 DOM 的某个属性,具体的过程就不再细说了。

转载于:https://www.cnblogs.com/jonas-von/p/9974107.html

你可能感兴趣的文章
socket计划编制的原则
查看>>
sqlite3经常使用命令&语法
查看>>
[leetcode] 309. Best Time to Buy and Sell Stock with Cooldown(medium)
查看>>
解决微信授权回调页面域名只能设置一个的问题 [php]
查看>>
数组去重一步到位
查看>>
HDU 4671 Backup Plan 构造
查看>>
linux下编译openjdk8
查看>>
【python】--迭代器生成器装饰器
查看>>
Pow(x, n)
查看>>
安卓当中的线程和每秒刷一次
查看>>
MySQL Proxy
查看>>
关于Vue的组件的通用性问题
查看>>
随机颜色值
查看>>
每日一库:Modernizr.js,es5-shim.js,es5-safe.js
查看>>
目录相关的操作
查看>>
解决虚拟机vmware安装64位系统“此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态”的问题...
查看>>
C++----练习--引用头文件
查看>>
11.基本包装类型
查看>>
ajax连接服务器框架
查看>>
wpf样式绑定 行为绑定 事件关联 路由事件实例
查看>>