javascript --- Vue初始化 模板渲染

不带响应式的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的运行机制大致就是这样

img

实现

  • 在这里实现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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/250269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#RANK_1 极其简单的递归——骑士与金币

2000:金币 总时间限制: 1000ms内存限制: 65536kB描述国王将金币作为工资&#xff0c;发放给忠诚的骑士。第一天&#xff0c;骑士收到一枚金币&#xff1b;之后两天&#xff08;第二天和第三天&#xff09;里&#xff0c;每天收到两枚金币&#xff1b;之后三天&#xff08;第四、…

javascript --- vue2.x中原型的使用(拦截数组方法) 响应式原理(部分)

说明 在Vue2.x中,利用了对原型链的理解,巧妙的利用JavaScript中的原型链,实现了数组的pop、push、shift、unshift、reverse、sort、splice等的拦截. 你可能需要的知识 参考 - MDN 原型链 JavaScript常被描述为一种基于原型的语言(prototype-based language),每个对象拥有一…

dubbo-admin构建报错

dubbo-admin构建报错 意思是maven库里没有dubbo2.5.4-SNAPSHOT.jar这个版本的dubbo的jar包&#xff0c;把dubbo-admin项目的pom.xml的   <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${proje…

PyCharm 通过Github和Git上管理代码

1.Pycharm中设置如图: 2.配置Git,通过网页 https://www.git-scm.com/download/win 下载 3. 转载于:https://www.cnblogs.com/0909/p/9956406.html

享元模式-Flyweight(Java实现)

享元模式-Flyweight 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。 本文中的例子如下: 使用享元模式: 小明想看编程技术的书, 就到家里的书架上拿, 如果有就直接看, 没有就去买一本, 回家看. 看完了就放到家里…

013.Zabbix的Items(监控项)

一 Items简介 Items是从主机里面获取的所有数据&#xff0c;可以配置获取监控数据的方式、取值的数据类型、获取数值的间隔、历史数据保存时间、趋势数据保存时间、监控key的分组等。通常情况下item由key参数组成&#xff0c;如监控项中需要获取cpu信息&#xff0c;则需要一个对…

算法 --- 记一道面试dp算法题

题目: 给定一个数组(长度大于1),如下 let a [1,4,3,4,5] // 长度不确定,数值为整数要求写一个函数,返回该数组中,除本身数字之外其他元素的成积.即返回如下: // 过程[4*3*4*5, 1*3*4*5, 1*4*4*5, 1*4*3*5, 1*4*3*4] // 结果[240, 60, 80, 60, 48]题目要求不使用除法,且时间…

ASP.NET MVC 实现页落网资源分享网站+充值管理+后台管理(10)之素材管理

源码下载地址&#xff1a;http://www.yealuo.com/Sccnn/Detail?KeyValuec891ffae-7441-4afb-9a75-c5fe000e3d1c 素材管理模块也是我们这个项目的核心模块&#xff0c;里面的增删查改都跟文章管理模块相同或者相似&#xff0c;唯一不同点可能是对附件的上传处理&#xff0c;但…

Git很简单--图解攻略

Git Git 是目前世界上最先进的分布式版本控制系统&#xff08;没有之一&#xff09;作用 源代码管理为什么要进行源代码管理? 方便多人协同开发方便版本控制Git管理源代码特点 1.Git是分布式管理.服务器和客户端都有版本控制能力,都能进行代码的提交、合并、. 2.Git会在根…

vc/vs开发的应用程序添加dump崩溃日志转

原贴地址&#xff1a;https://blog.csdn.net/wangkui1331/article/details/78029940 vc/vs开发的应用程序出现崩溃的时候&#xff0c;由于没有任何记录&#xff0c;导致开发人员很难追踪&#xff0c;但是添加dump文件后&#xff0c;就可以免除这些烦恼 1.添加方法 &#xff08;…

51 nod 1127最短的包含字符串(尺取法)

1127 最短的包含字符串 收藏关注给出一个字符串&#xff0c;求该字符串的一个子串S&#xff0c;S包含A-Z中的全部字母&#xff0c;并且S是所有符合条件的子串中最短的&#xff0c;输出S的长度。如果给出的字符串中并不包括A-Z中的全部字母&#xff0c;则输出No Solution。Input…

JSON 数据重复 出现$ref

JSONArray 类型 如果我们往里面add数据的时候 如果数据相同&#xff0c;那么就会被替换成 $ref: 也就是被简化了 因为数据一样所直接 指向上一条数据 循环引用&#xff1a;当一个对象包含另一个对象时&#xff0c;fastjson就会把该对象解析成引用。引用是通过$ref标示的&am…

Linux初学时的一些常用命令(4)

1. 磁盘 查看当前磁盘使用情况 df -h查看某个文件大小 du -sh 文件名 如果不输入文件名&#xff0c;默认是当前目录的所有文件之和&#xff0c;即当前目录大小 2. 系统内存 free参数详解&#xff1a;https://blog.csdn.net/loongshawn/article/details/51758116 3. CPU CPU 使用…

爬虫之拉勾网职位获取

重点在于演示urllib.request.Request()请求中各项参数的 书写格式 譬如&#xff1a; url data headers...Demo演示&#xff08;POST请求&#xff09;:import urllib.requestimport urllib.parseimport json, jsonpath, csvurl "https://www.lagou.com/jobs/positionAjax.…

小程序 --- 点击放大功能、获取位置信息、文字样式省略、页面跳转(navigateTo)

1. 点击放大功能的实现 需求: 点击轮播图中的图片会实现放大预览的功能。首先有轮播图的样式如下 <!-- pages/goods_detail/index.wxml --> <!-- 轮播图 --> <view class"detail_swiper"><swiperautoplaycircularindicator-dots><swip…

Axure实现多用户注册验证

*****多用户登录验证***** 一、&#xff08;常规想法&#xff09;方法&#xff1a;工作量较大&#xff0c;做起来繁琐 1、当用户名和密码相同时怎么区分两者&#xff0c;使用冒号和括号来区分&#xff1a; eg. (admin:123456)(123456:demo)(zhang:san);由此得出前面是括号后面是…

Maximum Xor Secondary(单调栈好题)

Maximum Xor Secondary CodeForces - 280B Bike loves looking for the second maximum element in the sequence. The second maximum element in the sequence of distinct numbers x1, x2, ..., xk (k > 1) is such maximum element xj, that the following inequa…

杂项-公司:唯品会

ylbtech-杂项-公司&#xff1a;唯品会唯品会公司成立于2008年08月&#xff0c;2012年3月23日登陆美国纽约证券交易所上市&#xff08;股票代码&#xff1a;VIPS&#xff09;。成为华南第一家在美国纽交所上市的电子商务企业。主营B2C商城唯品会名牌折扣网站是一家致力于打造中高…

Linux基本的操作

一、为什么我们要学习Linux 相信大部分人的PC端都是用Windows系统的&#xff0c;那我们为什么要学习Linux这个操作系统呢&#xff1f;&#xff1f;&#xff1f;Windows图形化界面做得这么好&#xff0c;日常基本使用的话&#xff0c;学习成本几乎为零。 而Linux不一样&#xff…

汇编语言 实验4

实验4 实验内容1&#xff1a;综合使用 loop,[bx]&#xff0c;编写完整汇编程序&#xff0c;实现向内存 b800:07b8 开始的连续 16 个 字单元重复填充字数据 0403H&#xff1b;修改0403H为0441H&#xff0c;再次运行 步骤1&#xff1a;在记事本中编写好temp.asm文件 步骤2&#x…