vue源码探究(第四弹)

结束了上一part的数据代理,这一部分主要讲讲vue的模板解析,感觉这个有点难理解,而且内容有点多,hhh。

模板解析

废话不多说,先从简单的入手。

按照之前的套路,先举一个例子 :

<div id="test"><p>{{name}}</p>
</div>
<script type="text/javascript" src="js/mvvm/compile.js"></script>
<script type="text/javascript" src="js/mvvm/mvvm.js"></script>
<script type="text/javascript" src="js/mvvm/observer.js"></script>
<script type="text/javascript" src="js/mvvm/watcher.js"></script>
<script type="text/javascript">new MVVM({el: '#test',data: {name: '喵喵喵'}})// 这时候,我们的页面还是渲染出 喵喵喵
</script>

接下来讲讲内部的相关实现:

我们的MVVM中的构造函数中有什么东西,可以解析我们的模板呢?

// 创建一个用来编译模板的compile对象
this.$compile = new Compile(options.el || document.body, this)

什么是Compile?

一行一行注释着解读

function Compile(el, vm) {// 保存vmthis.$vm = vm;// 保存el元素this.$el = this.isElementNode(el) ? el : document.querySelector(el);// 如果el元素存在if (this.$el) {// 1. 取出el中所有子节点, 封装在一个framgment对象中// 这里的node2Fragment 就是将node -> 放入 Fragment中,documentFragment将node进行批量处理this.$fragment = this.node2Fragment(this.$el);// 2. 编译fragment中所有层次子节点this.init();// 3. 将fragment添加到el中this.$el.appendChild(this.$fragment);}
}Compile.prototype = {node2Fragment: function (el) {var fragment = document.createDocumentFragment(),child;// 将原生节点拷贝到fragmentwhile (child = el.firstChild) {fragment.appendChild(child);}return fragment;},init: function () {// 编译fragmentthis.compileElement(this.$fragment);},compileElement: function (el) {// 得到所有子节点var childNodes = el.childNodes,// 保存compile对象me = this;// 遍历所有子节点[].slice.call(childNodes).forEach(function (node) {// 得到节点的文本内容var text = node.textContent;// 正则对象(匹配大括号表达式)var reg = /{{(.*)}}/;  // {{name}}// 这里提出一个问题,为什么这里的正则匹配要用/{{(.*)}}/,而不是/{{.*}}/呢?// 其实/{{.*}}/就可以匹配到{{xxx}},这里加一个()的意义是,用于.$1,来取得{{}}中的值,eg:name// 如果是元素节点if (me.isElementNode(node)) {// 编译元素节点的指令属性me.compile(node);// 如果是一个大括号表达式格式的文本节点} else if (me.isTextNode(node) && reg.test(text)) {// 编译大括号表达式格式的文本节点me.compileText(node, RegExp.$1); // RegExp.$1: 表达式   name}// 如果子节点还有子节点if (node.childNodes && node.childNodes.length) {// 递归调用实现所有层次节点的编译me.compileElement(node);}});},compile: function (node) {// 得到所有标签属性节点var nodeAttrs = node.attributes,me = this;// 遍历所有属性[].slice.call(nodeAttrs).forEach(function (attr) {// 得到属性名: v-on:clickvar attrName = attr.name;// 判断是否是指令属性if (me.isDirective(attrName)) {// 得到表达式(属性值): testvar exp = attr.value;// 得到指令名: on:clickvar dir = attrName.substring(2);// 事件指令if (me.isEventDirective(dir)) {// 解析事件指令compileUtil.eventHandler(node, me.$vm, exp, dir);// 普通指令} else {// 解析普通指令compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);}// 移除指令属性node.removeAttribute(attrName);}});},compileText: function (node, exp) {// 调用编译工具对象解析compileUtil.text(node, this.$vm, exp);},isDirective: function (attr) {return attr.indexOf('v-') == 0;},isEventDirective: function (dir) {return dir.indexOf('on') === 0;},isElementNode: function (node) {return node.nodeType == 1;},isTextNode: function (node) {return node.nodeType == 3;}
};// 指令处理集合
var compileUtil = {// 解析: v-text/{{}}text: function (node, vm, exp) {this.bind(node, vm, exp, 'text');},// 解析: v-htmlhtml: function (node, vm, exp) {this.bind(node, vm, exp, 'html');},// 解析: v-modelmodel: function (node, vm, exp) {this.bind(node, vm, exp, 'model');var me = this,val = this._getVMVal(vm, exp);node.addEventListener('input', function (e) {var newValue = e.target.value;if (val === newValue) {return;}me._setVMVal(vm, exp, newValue);val = newValue;});},// 解析: v-classclass: function (node, vm, exp) {this.bind(node, vm, exp, 'class');},// 真正用于解析指令的方法bind: function (node, vm, exp, dir) {/*实现初始化显示*/// 根据指令名(text)得到对应的更新节点函数// 取到一个object的属性,有2个方法,一个是obj. 一个是obj[]// 当我们要取得属性是一个变量的时候,使用obj[]var updaterFn = updater[dir + 'Updater'];// 如果存在调用来更新节点updaterFn && updaterFn(node, this._getVMVal(vm, exp));// 创建表达式对应的watcher对象new Watcher(vm, exp, function (value, oldValue) {/*更新界面*/// 当对应的属性值发生了变化时, 自动调用, 更新对应的节点updaterFn && updaterFn(node, value, oldValue);});},// 事件处理eventHandler: function (node, vm, exp, dir) {// 得到事件名/类型: clickvar eventType = dir.split(':')[1],// 根据表达式得到事件处理函数(从methods中): test(){}fn = vm.$options.methods && vm.$options.methods[exp];// 如果都存在if (eventType && fn) {// 绑定指定事件名和回调函数的DOM事件监听, 将回调函数中的this强制绑定为vmnode.addEventListener(eventType, fn.bind(vm), false);}},// 得到表达式对应的value_getVMVal: function (vm, exp) {// 这里为什么要forEach呢?// 如果你的exp是a.b.c.c.d呢 就需要forEach 如果只是一层 当然不需要遍历啦var val = vm._data;exp = exp.split('.');exp.forEach(function (k) {val = val[k];});return val;},_setVMVal: function (vm, exp, value) {var val = vm._data;exp = exp.split('.');exp.forEach(function (k, i) {// 非最后一个key,更新val的值if (i < exp.length - 1) {val = val[k];} else {val[k] = value;}});}
};// 包含多个用于更新节点方法的对象
var updater = {// 更新节点的textContenttextUpdater: function (node, value) {node.textContent = typeof value == 'undefined' ? '' : value;},// 更新节点的innerHTMLhtmlUpdater: function (node, value) {node.innerHTML = typeof value == 'undefined' ? '' : value;},// 更新节点的classNameclassUpdater: function (node, value, oldValue) {var className = node.className;className = className.replace(oldValue, '').replace(/s$/, '');var space = className && String(value) ? ' ' : '';node.className = className + space + value;},// 更新节点的valuemodelUpdater: function (node, value, oldValue) {node.value = typeof value == 'undefined' ? '' : value;}
};

最后

未完待续...
接下来,还有一个更有趣的东西

下一章继续~

vue大括号里接受一个函数_vue源码探究(第四弹)相关推荐

  1. vue大括号里接受一个函数_vue双花括号的使用方法 附练习题

    本文实例为大家分享了vue双花括号的具体代码,供大家参考,具体内容如下 {{}}的使用 {{msg}} {{cart.brand}} 3 + 5 = {{ 3 + 5 }} new Vue({ el: ...

  2. vue foreach用法_vue 源码探究(第二弹)

    vue 源码探究(第二弹) 接着上一篇,继续来讲一个非常有意思的东西documentFragment 解析 六.DocumentFragment: 文档碎片(高效批量更新多个节点) 这里先甩出 2 个 ...

  3. vue从哪看组件版本_VUE源码解析之路

    Vue 是一个 MVVM 框架,一个数据响应式的组件系统,通过把页面抽象成一个个组件来增加复用性,降低复杂性,提高维护便利性.所以重要的事情说三遍: 页面一个视图区域抽象成组件,通用型工具抽出公共组件 ...

  4. vue 存储对象 不要监听_Vue源码解析----响应式原理

    从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新 ...

  5. tcplayer 源码改造第三弹 - 防盗录

    文章目录 前序 简介 人群 git地址 源码改造tcplayer.js(各位客官请自行格式化代码) 修改思路 添加配置参数 添加防盗录的节点 添加防盗录的节点样式 使用说明 参数说明 使用示例 相关推 ...

  6. vue 初始化方法_Vue源码解读(一)引入Vue做了什么

    本系列文章都是vue版本2.6.11,也就是vuecli3的对应的vue的版本 学习一门技术的源码首先当然是从它的入口开始学习,在这里我先扩展一下从引入vue到入口都讲一下 首先看下node_modu ...

  7. Vue源码探究-全局API

    Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...

  8. Vue源码探究-事件系统

    Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...

  9. 实现根据条件删除_Vue源码解析,keep-alive是如何实现缓存的?

    作者:WahFung 转发链接:https://juejin.im/post/6862206197877964807 前言 在性能优化上,最常见的手段就是缓存.对需要经常访问的资源进行缓存,减少请求或 ...

最新文章

  1. ansys的kbc_ansys求解过程
  2. Android中事件分发机制的总结
  3. 旅行场景下的个性化营销平台揭秘
  4. mysql+ubunt+绿色安装_Mysql在ubuntu18上的安装及简单使用
  5. MySQL源码解读之数据结构-LF_DYNARRAY
  6. font-familly:' 阿里巴巴-普惠体 '【永久免费 】 - 下载与使用
  7. 2017CCPC哈尔滨 H:A Simple Stone Game
  8. Android Binder Driver流程分析
  9. QoS专题-第4期-QoS实现之限速
  10. 孙鑫VC学习笔记:第十三讲 (五) 保存可串行化的类对象 如何获取文档与视类指针
  11. elk日志分析系统_ELK 日志分析系统
  12. 将自己的主页地址设置为OpenID
  13. 服务器声卡如何虚拟,服务器没声卡远程桌面连接怎么实现听到服务器的声音
  14. 【C++】完成一个消消乐
  15. Android仿淘宝详情页面viewPager滑动到最后一张图片跳转的功能
  16. 禁止多人使用同一账号在系统上进行操作[踢人操作]
  17. 人人都能成为闪电网络节点:第7章管理lnd
  18. 获取ALM中步骤数据
  19. 组合、聚合、继承详解
  20. 程设大作业之魔兽世界

热门文章

  1. 分析一段H264视频数据
  2. 《 追风筝的人 》:“ 为你,千千万万遍 ” ...
  3. Spring 之注解事务 @Transactional
  4. Servlet全面讲解
  5. logback 配置
  6. Request的getHeader()和getParameter()的区别
  7. 405 Method Not Allowed
  8. LAMP 系统性能调优,第 3 部分: MySQL 服务器调优(转)
  9. STL之string类型
  10. 李洋疯狂C语言之关于自增自减遇到的一些问题