不带响应式的Vue缩减实现

模板

现有模板如下:

<div id ="app"><div class="c1"><div title='tt1' id="id">{{ name }}</div><div title='tt2' >{{age}}</div><div>hello3</div></div><ul><li>1</li><li>2</li><li>3</li></ul>
</div>
<script>let app = new Vue({el: '#app',data:{name: '张三',age: 19}})
</script>

Vue初始化流程

Vue的初始化,是从new Vue开始的,以下的图中可以知道在new Vue后,会执行init,再$mount实现挂载,再到编译compile,生成render函数,接下来是响应式依赖收集,通过pach实现异步更新。render function会被转化为Vnode节点,Virtual DOM是一棵以JavaScript对象(Vnode节点)为基础的树。是对真实DOM的描述。通过patch()转化为真实DOM。在数据有变化时,会通过setter -> Watcher -> update来更新视图。整个Vue的运行机制大致就是这样

实现

  • 在这里实现new Vue -> $mount -> compile -> render function -> Virtual DOM Tree -> patch() -> DOM,即除了响应式的部分.

  • 简略版

【流程梳理】:

  • 首先要明确目的,我们需要将现有的HTML模板与数据结合,生成一个新的HTML结构,并渲染到页面上.考虑到性能问题,我们首先将模板读取到内存中(源代码是进行HTML解析,生成一棵抽象AST).在这里使用带mustcache语法的HTML模板代替.

  • 首先是执行new Vue,在Vue函数中会将传入的数据和模板保存起来,为了后续的方便,会将模板及其父元素也保存起来,然后执行mount

function Vue(options){let elm = document.querySelector(options.el)this._data = options.datathis._template = elmthis._parent = elm.parentNodethis.mount()
}
  • 然后是mount函数,在里面做了2件事:

    • 第一件事是将HTML读取为AST保存在内存中,并返回一个根据AST 和 data 生成 虚拟DOM的render函数
    • 第二件事是调用mountComponent: 将render函数生成的VNode(虚拟DOM)转换成真实的HTML节点渲染到页面上

【先看第一件事】

Vue.prototype.mount = function(){this.render = this.createRenderFn()
}
Vue.prototype.createRenderFn = function(){let AST = getVNode(this._template)return function render(){let _tmp = combine(AST, this._data)return _tmp}
}

上面在mount中调用了createRenderFn,生成了一个render函数(AST + DATA -> VNode). 之所以写出那种形式,

是因为AST仅在一开始读取DOM结构时候就固定不变了,采用上面的写法可以提高性能.

getVNode函数根据模板,返回带mustache语法的虚拟DOM.更多参考

class VNode {constructor(tag ,data, value, type){this.tag = tag && tag.toLowerCase()this.data = datathis.value = valuethis.type = typethis.children = []}appendChild(vnode){this.children.push(vnode)}
}
function getVNode(node){let nodeType = node.nodeTypelet _vnode = nullif(nodeType == 1){// 元素节点let tag = node.nodeName,attrs = node.attributes,_data = {}for(let i = 0, len = attrs.length; i < len; i++){_data[attrs[i].nodeName] = attrs[i].nodeValue}_vnode = new VNode(tag, _data, undefined, nodeType)// 考虑子元素let childNodes = node.childNodes;for(let i = 0, len = childNodes.length; i< len; i++){_vnode.appendChild(getVNode(childNodes[i]))}} else if(nodeType == 3){// 文本节点_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)}return _vnode
}

此时得到的是一个对象,这个对象中的值类似{{name}}(模拟了AST),下面使用combine将该对象模板与数据结合生成一个新的对象(在Vue中是虚拟的DOM)。即将mustache语法用真实的数据替换

function combine(vnode ,data){let _type = vnode.type, _data = vnode.data, _tag = vnode.tag, _value = vnode.value, _children = vnode.children, _vnode = nullif(_type == 3){// 文本节点_value = _value.replace(/\{\{(.+?)\}\}/g, function(_, g){return getValueByPath(data, g.trim())})_vnode = new VNode(_tag, _data, _value, _type)} else if(_type == 1){// 元素节点_vnode = new VNode(_tag, _data, _value, _type)_children.forEach(_subVNode => _vnode.appendChild(combine(_subVNode, data)))}return _vnode
}
// getValueByPath,深层次获取对象的数据. 栗子: 获取 a.name.age.salary
function getValueByPath(obj, path){let res=obj, currProp, props = path.join('.')while(currProp = props.shift()){res = res[props]}return res
}

【再看第二件事】

mountComponent中会使用第一件事中的render函数将AST和Data结合起来生成虚拟DOM,然后调用this.update方法将虚拟DOM渲染到页面上

Vue.prototype.mountComponent = function(){let mount = () => {this.update(this.render())}mount.call(this)
}
// 之所以采用this.update,是因为update后面会交付给watcher来调用的
Vue.prototype.update = function (vnode){let realDOM = parseVNode(vnode)this._parent.replaceChild(realDOM, this._template)
}
function parseVNode(vnode){let type = vnode.type, _node = nullif(type ==3){return document.createTextNode(vnode.value)} else if (type == 1){_node = document.createElement(vnode.tag)let data = vnode.dataObject.keys(data).forEach(key => {_node.setAttribute(key, data[key])})let children = vnode.childrenchildren.forEach(subvnode =>{_node.appendChild(parseNode(subvnode))})}return _node
}

整体代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"><div class="c1"><div title="tt1" id="id">{{ name }}</div><div title="tt2">{{age}}</div><div>hello3</div><ul><li>1</li><li>2</li><li>3</li></ul></div></div><script>/* 虚拟DOM 构造函数 */class VNode {constructor(tag, data, value, type) {this.tag = tag && tag.toLowerCase()this.data = datathis.value = valuethis.type = typethis.children = []}appendChild(vnode) {this.children.push(vnode)}}/* HTML DOM -> VNode(带坑的Vnode): 将这个函数当做 compiler 函数  *//*Vue中会将真实的DOM结构当作字符串去解析得到一棵 AST此处使用带有mustache语法的虚拟DOM来代替 AST*/function getVNode(node) {let nodeType = node.nodeTypelet _vnode = nullif (nodeType == 1) {// 元素let nodeName = node.nodeNamelet attrs = node.attributeslet _attrObj = {}for (let i = 0; i < attrs.length; i++) {_attrObj[attrs[i].nodeName] = attrs[i].nodeValue}_vnode = new VNode(nodeName, _attrObj, undefined, nodeType)// 考虑node的子元素let childNodes = node.childNodesfor (let i = 0; i < childNodes.length; i++) {_vnode.appendChild(getVNode(childNodes[i]))}} else if (nodeType == 3) {_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)}return _vnode}/* 将虚拟DOM转换成真正的DOM */function parseVNode(vnode){// 创建真实的DOMlet type = vnode.type;let _node = null;if( type == 3){return document.createTextNode(vnode.value)} else if(type == 1){_node = document.createElement(vnode.tag)// 属性let data = vnode.data  // 现在这个data是键值对Object.keys(data).forEach((key)=>{let attrName = keylet attrValue = data[key]_node.setAttribute(attrName, attrValue)})// 子元素let children = vnode.children;children.forEach(subvnode =>{_node.appendChild(parseVNode(subvnode))})return _node}}const mustache = /\{\{(.+?)\}\}/g // 匹配{{}}的正则表达式// 根据路径访问对象成员function getValueByPath(obj, path) {let res = obj,currProp,props = path.split('.')while ((currProp = props.shift())) {res = res[currProp]}return res}/*模拟 AST -> VNode 的过程将带有坑(mustache语法)的VNode与数据data结合,得到填充数据的VNode:*/function combine(vnode, data) {let _type = vnode.typelet _data = vnode.datalet _tag = vnode.taglet _value = vnode.valuelet _children = vnode.childrenlet _vnode = nullif (_type == 3) {// 文本节点// 对文本处理_value = _value.replace(mustache, function(_, g) {return getValueByPath(data, g.trim())})_vnode = new VNode(_tag, _data, _value, _type)} else if (_type == 1) {// 元素节点_vnode = new VNode(_tag, _data, _value, _type)_children.forEach(_subVNode => _vnode.appendChild(combine(_subVNode, data)))}return _vnode}function JGVue(options) {// this._options = options;this._data = options.datalet elm = document.querySelector(options.el)this._template = elmthis._parent = elm.parentNodethis.mount() // 挂载}JGVue.prototype.mount = function() {// 需要提供一个render方法: 生成虚拟DOM// if(typeof this._options.render !== 'function'){// }this.render = this.createRenderFn() // 带有缓存this.mountComponent()}JGVue.prototype.mountComponent = function() {// 执行mountComponent()let mount = () => {// update将虚拟DOM渲染到页面上this.update(this.render())}mount.call(this) // 本质上应该交给 watcher 来调用// 为什么// this.update(this.render())  // 使用发布订阅模式,渲染和计算的行为应该交给watcher来完成}/*在真正的Vue中,使用了二次提交的设计结构第一次提交是在内存中,在内存中确定没有问题了在修改硬盘中的数据1. 在页面中的DOM和虚拟DOM是一一对应的关系*/// 这里是生成render函数,目的是缓存抽象语法树(我们使用虚拟DOM来模拟)JGVue.prototype.createRenderFn = function() {let AST = getVNode(this._template)// 将 AST + data => VNode// 我们: 带坑的VNode + data => 含有数据的 VNodereturn function render() {// 将带坑的VNode转换为真正带数据的VNodelet _tmp = combine(AST, this._data)return _tmp}}// 将虚拟DOM熏染到页面中: diff算法就在这里JGVue.prototype.update = function(vnode) {// 简化,直接生成HTML DOM replaceChild 到页面中// 父元素.replaceChild(新元素,旧元素)let realDOM = parseVNode(vnode)// debuggerthis._parent.replaceChild(realDOM, document.querySelector('#app'))// 这个算法是不负责任的// 每次都会将页面中的DOM全部替换}let app = new  ({el: '#app',data: {name: '张三',age: 19}})</script></body>
</html>

javascript --- Vue初始化 模板渲染相关推荐

  1. 基于vue通用模板渲染Table

    文章目录 前言 一.需求分析 二.实施方案 1.方案解析 2.实例 总结 前言 日常业务中我们偶尔会用到使用通用模板渲染不同样式表格的需求,已避免重复开发的工作量:本文基于此场景谈谈如何解决此类问题 ...

  2. Vue模板字符串的使用,【html标签、Element-uivue template模板渲染】

    文章目录 技术 背景 关于在vue 中 html标签的渲染与使用[v-html] 关于在vue 中element组件标签模板渲染与使用 技术 Vue父子组件传值 Vue render函数的引用 Vue ...

  3. Vue实例的属性及模板渲染

    Vue实例的属性及模板渲染 1 概述 2 el:与DOM元素绑定 3 data:定义双向绑定的数据 4 computed:计算属性 5 methods:定义Vue实例的方法 6 Vue中的三种模板 6 ...

  4. egg html模板,egg+vue服务端渲染模板项目介绍

    egg-vue-webpack-boilerplate 基于 Egg + Vue + Webpack SSR 服务端渲染和 CSR 前端渲染工程骨架项目,包括前台系统(SSR MPA)和后台管理系统( ...

  5. Vue 服务端渲染原理 拆分成三步个步骤简单的实现一个案例

    前言 可能我们平常接触比较多的是使用 vue + vue全家桶来搭建起一个单页(SPA)应用.用 服务端渲染 搭建项目比较少,本文是记录我在学习 服务端渲染 过程中的一些见解,如有出错或疏漏,麻烦帮忙 ...

  6. vue服务端渲染之nuxtjs

    前言 本篇主要针对nuxtjs中的一些重要概念整理和代码实现! 在学习vue服务端渲染之前,先搞清楚几个概念: 什么是客户端渲染(CSR) 什么是服务端渲染(SSR) CSR和SSR有什么异同 客户端 ...

  7. Vue.js 模板语法

    模板语法 Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据.所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 H ...

  8. [vue] 你知道vue的模板语法用的是哪个web模板引擎的吗?说说你对这模板引擎的理解

    [vue] 你知道vue的模板语法用的是哪个web模板引擎的吗?说说你对这模板引擎的理解 模板引擎: 负责组装数据,以另外一种形式或外观展现数据. 优点: 可维护性(后期改起来方便): 可扩展性(想要 ...

  9. png文件头_Golang GinWeb框架7静态文件/模板渲染

    简介 本文接着上文(Golang GinWeb框架6-绑定请求字符串/URI/请求头/复选框/表单类型)继续探索GinWeb框架 静态文件服务 package mainimport ( "g ...

最新文章

  1. 在线作图|如何绘制一张好看的点棒图
  2. “二子乘舟”的故事很难讲
  3. python爬虫能干啥-Python爬虫还能干什么?
  4. Spring boot的Bean使用JSR 303校验
  5. 解决hao123胁持chrome等浏览器主页问题
  6. [网络开发]服务器开发
  7. SVN在vs2013中使用
  8. Filecoin网络存储容量已达3 EB
  9. python下载教程-Python 如何入门?附Python教程下载
  10. Javascript特效:左侧二维码的显示和隐藏
  11. LeetCode 49 - Group Anagram 归类同构字
  12. 华为机试真题 C++ 实现【数字涂色】
  13. Fern wifi cracker 无线破解工具——图解
  14. 51单片机汇编编程--16位拉幕灯
  15. 魂斗罗进化革命+塞班JAVA版_魂斗罗进化革命电脑版
  16. 计算机中利用的物理原理,现代电脑技术中物理原理.doc
  17. python解包wxapkg_小程序反编译之获取wxapkg包
  18. 我们的时间去了哪里?
  19. AMA专题: 深入解读Read2N三大创新, 全面启动市场引擎
  20. Hbuilder-应用程序打包

热门文章

  1. 计算机控制系统为什么会受到干扰,浅谈计算机控制系统中的干扰及其抑制措施...
  2. java 四种内存_不可访问内存 Java四种引用包括强引用,软引用,弱引用,虚引用...
  3. 如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的?
  4. Unity HDRP中的光照烘焙测试(Mixed Lighing )和间接光
  5. tensorboard的可视化及模型可视化
  6. Imbalanced data – Finding Waldo
  7. 递归锁、信号量、GIL锁、基于多线程的socket通信和进程池线程池
  8. JAVA EE 基本了解
  9. Python:eval的妙用和滥用
  10. Linux命令:mkdir