Vue 源码阅读学习(三)
第三节:函数柯里化与渲染模型
嘿,朋友们,本节是
Vue
源码阅读的第三讲。Vue
源码阅读系列得到了赞赏,我很高兴,同时希望大家可以给予反馈!我虚心接纳您的意见!
如果没有看之前的第一讲和第二讲的内容可以先去看看哦,好啦,让我们开始吧。
目录
- 第三节:函数柯里化与渲染模型
- 概念
- Ⅰ. 判断元素
- Ⅱ. 虚拟 `DOM` 的 `render` 方法
- 重点
本节内容开始之前,我们先了解一些概念。
函数式编程(学有余力可以看看)这里不做展开。
概念
① 柯里化
一个函数原本有多个参数,只传入一个参数,生成一个函数,由新函数来接受剩余参数来运行得到结果。
② 偏函数
一个函数原本有多个参数,只传入一部分参数,生成一个函数,由新函数来接受剩余参数来运行得到结果。
③ 高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
Q1
: 谈谈为什么要使用柯里化?
A1
: 为了提高性能,使用柯里化可以缓存一部分能力。
使用两个案例来说明:
Ⅰ. 判断元素
Vue
本质上是使用 HTML
的字符串作为模板的,将字符串的模板转换为抽象语法树(AST),再转换为虚拟DOM。
抽象语法树后面简写成 AST
。
下图为表示式 a+a*(b-c)+(b-c)*d
的AST
图示,当然 Vue
中 AST
不一定长这样。
过程:
模板 --> AST
AST
--> VNode
VNode
--> DOM
Q2: 哪个阶段最消耗性能?
A2: 模板 --> AST
,其中涉及对字符串进行解析。
例如,字符串拼接
。运行下面代码,在浏览器中检查
都打不开,所以说拼接字符串是非常消耗性能的,
let str = '';
for(let i = 0; i < 100000; i++){str += i;console.log(str);
}
再来个例子 : 有let s = "1 + 2 * ( 3 + 4 )"
,现在写一个程序,解析这个表达式,要求得到运算结果。
解
:一般会将这个表达式转换为“逆波兰式”,然后使用栈结构来运算。
这里就不细说了,涉及到编译原理中的语义分析…和数据结构中的表达式求值…
Q3
: 在 Vue
中每一个标签可以是真正的 HTML
标签 ,也可以是自定义组件
,那怎么区分?
A3
: 在 Vue
源码中将所有可用
的 HTML
标签已经存起来了。
Vue
源码具体实现部分代码:
var isHTMLTag = makeMap('html,body,base,head,link,meta,style,title,' + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + '非常多此处省略10000字...');
先不管 makeMap
函数是用来干嘛的。
假设这里只考虑几个标签。
let tags = 'div,p,a,img,ul,li'.split(',');
//["div","p","img","ul","li"]
现需要一个函数,判断一个标签名是否为内置
的标签?
具体实现代码:
function isHtmlTag(tagName){tagName = tagName.toLowerCase();//需要判断的标签for( let i = 0; i < tags.length; i++ ){/*使用indexOf可以if( tags[i].indexOf(tagName) > -1){return true;}*/if(tagName === tags[i]){return true;}}return false;
}
现有 1 个标签需要判断,如果有 6 个内置标签,最多要判断 6 次;现模板中有 10 个标签需要判断,那么最多需要执行 60 次循环,随着内置标签和模板中判断的标签增多,循环次数也是指数倍增长,所以说非常消耗性能!
优化: Vue
中使用 makeMap
函数,意思是创造映射(键值对),来进行优化。
let tags = 'div,p,a,img,ul,li'.split(',');
//["div","p","img","ul","li"]
function makeMap(keys) {//keys数组let set = {};//集合 键值对tags.forEach(key => {set[key] = true;});return function (tagName) {//注意:! 取反 !! 强制转换 Boolean//这里如果你不是内置标签 而是自定义组件 就是undefinedreturn !!set[tagName.toLowerCase()];}
}
let isHtmlTag = makeMap(tags);//返回函数
//时间复杂度 O(n) ==> O(1)
这里我觉得老师讲的例子好像跟柯里化没有具体联系,该例子是用空间换时间
,进行代价转移,或许是我理解有误,欢迎讨论!
Ⅱ. 虚拟 DOM
的 render
方法
思考: vue
项目模板
转换为 AST
需要执行几次?
页面一开始加载需要渲染
每一个属性(响应式)数据在发生变化的时候要渲染
watch computed
等等函数要渲染
…
之前写的代码 render
每次渲染的时候,模板就会被解析一次(实际上我们简化了解析方法),我们是没有经过 AST
环节,而 Vue
是用字符串解析抽象语法树
。
render
函数作用是将虚拟 DOM
转换为真正的 DOM
加入到页面中,我们现在将虚拟 DOM
“降级”理解为 AST
,(这话说的还是有点毛病的,简化操作)。我们知道一个项目运行时,模板是不会变的,表明 AST
不会变的。所以我们可以将代码进行优化,将虚拟 DOM
缓存起来,生成一个函数,函数只需要传入数据,就可以得到真正的 DOM
。
思路有了,现在来重构代码!
function JGVue(options) {this._data = options.data;this._el = options.el;//提供一个新的方法 挂载this.mount();
}//mount
JGVue.prototype.mount = function () {//调用 mountComponent()之前需要提供一个render方法this.render = this.createRenderFn();//render 作用: 生成虚拟 DOM (与之前不一样了)//todo render...this.mountComponent();
}
mountComponent
方法的编写
解释:这么写在后面几期涉及到发布订阅模式
, mount
实际上是传给了 watcher
来进行调用,但是还没讲到,了解一下。
JGVue.prototype.mountComponent = function (){//执行mountComponent()函数let mount = () => {//一个函数 函数的this 默认全局对象 “函数调用模式”this.update(this.render());//render 用于生成虚拟 DOM update 负责渲染到页面上}//改变上下文mount.call(this);//本质上应该交给 watcher 来调用//箭头函数调用 call ? 有待思考
}
提供 render
方法
JGVue.prototype.mount = function () {//调用 mountComponent()之前需要提供一个render方法//作用: 生成虚拟 DOM//需要提供一个 renderthis.render = this.createRenderFn();//创建 render 函数//带有缓存(Vue 本身是可以带有 render 成员)this.mountComponent();
}
Q4
: 为什么需要这种方式创建?
A4
: 为了缓存虚拟 DOM
编写 createRenderFn
和 update
方法
//生成 render 函数 目的是为了缓存AST (使用虚拟 DOM 来模拟)
JGVue.prototype.createRenderFn = function() {//todo...
}//在真正的 Vue 中使用了二次提交的设计结构//将虚拟 DOM 渲染到页面中,diff算法在这里
JGVue.prototype.update = function() {// 简化, 直接生成 HTML DOM replaceChild 到页面中//todo...
}
Vue
中使用了二次提交
的设计结构,类似数据库中事务
操作。比如 A 给 B 转账 1000 元,需要做两件事情,很简单,A 扣款 1000 元,B 收款 1000 元;如果只是做了其中一件事情,突然有内鬼中止交易,这样交易是错误的。
图解:
重点
❶ 页面中的 DOM
和 虚拟 DOM
是一一对应的关系
图解说明
:页面中 HTML
就是真正的 DOM
, 而就 ❶ 所说,页面中的 DOM
和 虚拟 DOM
是一一对应的关系,Vue
每一次改变数据的时候,都会生成一个含有新数据的 VNode
。数据发生变化,生成新的 VNode
, 这个新的VNode
是含有新数据(缓存了AST
),上节课,我们所写的 VNode
是使用模板来做的,模板与数据结合是更新的时候做的。现在新的VNode
和页面中的VNode
相比较,哪里不同就更新哪里(目的就是更新),实际上更新 DOM
。(diff
算法),更新到 VNode
上也就是更新 HTML
。
图解:
这个算法挺绕的,主要是搞懂几个函数之间的“职责”。
❷ render
函数所做的: AST
和数据生成 VNode
(新的)
❸ diff
算法所作的:将 oldVNode
和 NewVNode
比较
❹ update
:更新
现在所做的事情尽量的去模拟 “二次提交” 的设计结构。
回到 createRenderFn
,其作用:生成 render
函数 目的是为了缓存AST
(这里我们使用虚拟 DOM
来模拟)
//生成 render 函数 目的是为了缓存AST (使用虚拟 DOM 来模拟)
JGVue.prototype.createRenderFn = function() {let ast = getVNode(this._template);//ast 用虚拟 DOM 模拟return function render () {// 将 AST 和 data 结合 生成 VNode// 现简化:待有“坑”的 VNode + data ==> 含有数据的VNode//todo... combinelet _tmp = combine(ast,this._data);return _tmp;}
}
编写 combine
// Vue : 将 AST 和 data 结合 生成 VNode
// 现简化:待有“坑”的 VNode + data ==> 含有数据的VNode
// 模拟 AST --> VNode 行为
function combine(vnode, data) {let _type = vnode.type;let _data = vnode.data;let _value = vnode.value;let _tag = vnode.tag;let _children = vnode.children;let _vnode = null;if (_type === 3) {//文本结点//填“坑”_value = _value.replace(r,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;
}
Q5
: 那在mount
为什么写了个render
?
A5
: Vue
本身是可以带有 render
成员
举个例子:
/*<div id="root"><p>{{name}}</p>
</div>*///`Vue`中有一个元素 `elm` 是真正的 `DOM `, 其 `children`同样也是真正的`DOM`
let app = new Vue({el: '#root',data: {name: 'xxx'},render: (createElement) => {//自定义如何生成虚拟 DOMreturn createElement('h1');//返回的就是虚拟DOM//没有使用模板 而是替换模板//页面:<h1></h1>}
})
之前 mount
方法中需要提供一个 render
方法,其作用就是生成新的生成虚拟 DOM
,继续编写,
//在 JGVue 构造函数提供保存 options
//...
JGVue.prototype.mount = function () {/*if(typeof this._options.render !== 'function'){//todo...}*///调用 mountComponent()之前需要提供一个render方法//作用: 生成虚拟 DOM//需要提供一个 renderthis.render = this.createRenderFn();//创建 render 函数//带有缓存(Vue 本身是可以带有 render 成员)this.mountComponent();
}JGVue.prototype.mountComponent = function (){//执行mountComponent()函数let mount = () => {//一个函数 函数的this 默认全局对象 “函数调用模式”this.update(this.render());//render 用于生成虚拟 DOM update 负责渲染到页面上}//改变上下文mount.call(this);//本质上应该交给 watcher 来调用//为什么//this.update(this.render());//使用发布订阅模式,渲染和计算的行为 应该交给 watcher
}
下节课:编写 update
方法,响应式原理
//将虚拟 DOM 渲染到页面中,diff算法在这里
JGVue.prototype.update = function() {// 简化, 直接生成 HTML DOM replaceChild 到页面中// 父元素.replaceChild(newElement,oldElement)// 需要拿到父元素
}
随着
Vue
源码深入学习,同时难度也在增大,所以说要求我们javascript
打下良好的基础。
为此,后面开一专题你应该掌握的 javascript 高阶技能
,为大家提供更多的优质学习内容,希望大家多多支持!
Vue 源码阅读学习(三)相关推荐
- Vue 源码阅读(三)Special Attributes
Special Attributes 包括以下:key ref slot v-* key https://vuejs.org/v2/api/#key The key special attribute ...
- Soul 网关源码阅读(三)请求处理概览
Soul 源码阅读(三)请求处理概览 简介 基于上篇:Soul 源码阅读(二)代码初步运行的配置,这次debug下请求处理的大致路径,验证网关模型的路径 详细流程记录 查看运行日志,寻找切入点 ...
- 【Vue原理】Vue源码阅读总结大会 - 序
[Vue原理]Vue源码阅读总结大会 - 序 阅读源码准备了什么 1.掌握 Vue 所有API 2.JavaScript 扎实基础 3.看完 JavaScript 设计模式 4.学会调试 Vue 源码 ...
- Java源码阅读学习后的浅析和感悟(JDK篇)(持续更新)
目录 Java源码阅读学习后的浅析和感悟(JKD篇) - 为什么阅读源码 集合框架类 - 为什么会要引入集合 - 集合结构图(部分) ArrayList集合源码分析 - 扩容机制 - 关键方法解释(D ...
- Go-Excelize API源码阅读(三十一)——ProtectSheet(sheet string, settings *SheetProtectionOptions)
Go-Excelize API源码阅读(三十一)-- ProtectSheet(sheet string, settings *SheetProtectionOptions) 开源摘星计划(WeOpe ...
- Alibaba Druid 源码阅读(三) 数据库连接池初始化探索
Alibaba Druid 源码阅读(三) 数据库连接池初始化探索 简介 上文中探索了Alibaba Druid的连接池初始化和获取连接的关键代码,接下来详细看看初始化部分 数据库连接池初始化 对整个 ...
- Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射
*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...
- 【Vue原理】Vue源码阅读总结大会
专注 Vue 源码分享,为了方便大家理解,分为了白话版和 源码版,白话版可以轻松理解工作原理和设计思想,源码版可以更清楚内部操作和 Vue的美,喜欢我就关注我的公众号,好吧兄弟,不会让你失望的 阅读源 ...
- Vue源码分析——第三章
Vue源码分析--第一章 Vue源码分析--第二章 // only used in dev mode//检测 val必需是数字function checkDuration(val, name, vno ...
最新文章
- esxi 6.x 密码复杂度要求_还在为账号密码多而烦恼?
- linux 下C调用Python 模块
- layui表格使用复选框批量删除_word表格技巧:如何对表格进行样式批处理
- 算法解读--递归(二)
- 3d点击_gooood合辑:3D打印 | 精选全部3D打印案例
- 线程间同步的几种方法--互斥锁,条件变量,信号量,读写锁
- BrainFuck——C实现BrainFuck解释器
- 西北农林科技大学计算机组成原理脱机实验,西北农林科技大学_计算机组成原理XP实验系统要素.ppt...
- 用Nginx分流绕开Github反爬机制
- 如何处理服务器SSL收到了一个弱临时Diffie-Hellman 密钥?
- Anguar 使用interceptor拦截器设置请求头传入jwt token
- 详解循环神经网络RNN(理论篇)
- 仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)...
- 2021-06-15
- H5 Laya 字体
- [apk破解]AirPin,无告用户书,无升级提示
- 计算机ip地址无法更改,win7电脑无法更改ip地址静态ip不能保存怎么办
- QT FOR 安卓动态旋转屏幕
- linux桌面记事本,推荐6款简单实用的手机记事本APP,总有一款适合你的‖APP展览馆...
- 语法糖(Syntactic sugar)/ 语法盐(syntactic salt)
热门文章
- 微信银行卡如何解除绑定?图文教程,快速解除
- 中职学校计算机应用基础试卷,(完整版)中职计算机应用基础试卷.(答案)doc
- sql服务器显示error,如图,一直显示error: 40 - 无法打开到 SQL Server 的连接,我用的是sql server 2012,使用c#制作网站,...
- 深度学习论文阅读目标检测篇(五)中英对照版:YOLOv2《 YOLO9000: Better, Faster, Stronger》
- 英语钻石法则(四)-----深入学习
- 北京/上海内推 | 小红书社区搜索组招聘多模态/NLP算法实习生
- BIM与GIS 融合技术方案 从此公路施工实现信息化
- 从零开始Desire HD刷机指南——第十四章:如何制作金卡
- 哪个网盘比较好,想找一个好用又安全的网盘
- 【ARTS】01_02_左耳听风-20181119~1125