第三节:函数柯里化与渲染模型

嘿,朋友们,本节是 Vue 源码阅读的第三讲。Vue 源码阅读系列得到了赞赏,我很高兴,同时希望大家可以给予反馈!我虚心接纳您的意见!
如果没有看之前的第一讲和第二讲的内容可以先去看看哦,好啦,让我们开始吧。

目录

  • 第三节:函数柯里化与渲染模型
  • 概念
    • Ⅰ. 判断元素
    • Ⅱ. 虚拟 `DOM` 的 `render` 方法
    • 重点

本节内容开始之前,我们先了解一些概念。

函数式编程(学有余力可以看看)这里不做展开。


概念

柯里化

​ 一个函数原本有多个参数,只传入一个参数,生成一个函数,由新函数来接受剩余参数来运行得到结果。

偏函数

​ 一个函数原本有多个参数,只传入一部分参数,生成一个函数,由新函数来接受剩余参数来运行得到结果。

高阶函数

​ 变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。


Q1: 谈谈为什么要使用柯里化?

A1: 为了提高性能,使用柯里化可以缓存一部分能力。

使用两个案例来说明:

Ⅰ. 判断元素

Vue 本质上是使用 HTML 的字符串作为模板的,将字符串的模板转换为抽象语法树(AST),再转换为虚拟DOM。

​ 抽象语法树后面简写成 AST

​ 下图为表示式 a+a*(b-c)+(b-c)*dAST图示,当然 VueAST不一定长这样。


​ 过程:

​ 模板 --> 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)

​ 这里我觉得老师讲的例子好像跟柯里化没有具体联系,该例子是用空间换时间,进行代价转移,或许是我理解有误,欢迎讨论!


Ⅱ. 虚拟 DOMrender 方法

思考: 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


编写 createRenderFnupdate 方法

//生成 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 算法所作的:将 oldVNodeNewVNode比较

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 源码阅读学习(三)相关推荐

  1. Vue 源码阅读(三)Special Attributes

    Special Attributes 包括以下:key ref slot v-* key https://vuejs.org/v2/api/#key The key special attribute ...

  2. Soul 网关源码阅读(三)请求处理概览

    Soul 源码阅读(三)请求处理概览 简介     基于上篇:Soul 源码阅读(二)代码初步运行的配置,这次debug下请求处理的大致路径,验证网关模型的路径 详细流程记录 查看运行日志,寻找切入点 ...

  3. 【Vue原理】Vue源码阅读总结大会 - 序

    [Vue原理]Vue源码阅读总结大会 - 序 阅读源码准备了什么 1.掌握 Vue 所有API 2.JavaScript 扎实基础 3.看完 JavaScript 设计模式 4.学会调试 Vue 源码 ...

  4. Java源码阅读学习后的浅析和感悟(JDK篇)(持续更新)

    目录 Java源码阅读学习后的浅析和感悟(JKD篇) - 为什么阅读源码 集合框架类 - 为什么会要引入集合 - 集合结构图(部分) ArrayList集合源码分析 - 扩容机制 - 关键方法解释(D ...

  5. Go-Excelize API源码阅读(三十一)——ProtectSheet(sheet string, settings *SheetProtectionOptions)

    Go-Excelize API源码阅读(三十一)-- ProtectSheet(sheet string, settings *SheetProtectionOptions) 开源摘星计划(WeOpe ...

  6. Alibaba Druid 源码阅读(三) 数据库连接池初始化探索

    Alibaba Druid 源码阅读(三) 数据库连接池初始化探索 简介 上文中探索了Alibaba Druid的连接池初始化和获取连接的关键代码,接下来详细看看初始化部分 数据库连接池初始化 对整个 ...

  7. Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  8. 【Vue原理】Vue源码阅读总结大会

    专注 Vue 源码分享,为了方便大家理解,分为了白话版和 源码版,白话版可以轻松理解工作原理和设计思想,源码版可以更清楚内部操作和 Vue的美,喜欢我就关注我的公众号,好吧兄弟,不会让你失望的 阅读源 ...

  9. Vue源码分析——第三章

    Vue源码分析--第一章 Vue源码分析--第二章 // only used in dev mode//检测 val必需是数字function checkDuration(val, name, vno ...

最新文章

  1. esxi 6.x 密码复杂度要求_还在为账号密码多而烦恼?
  2. linux 下C调用Python 模块
  3. layui表格使用复选框批量删除_word表格技巧:如何对表格进行样式批处理
  4. 算法解读--递归(二)
  5. 3d点击_gooood合辑:3D打印 | 精选全部3D打印案例
  6. 线程间同步的几种方法--互斥锁,条件变量,信号量,读写锁
  7. BrainFuck——C实现BrainFuck解释器
  8. 西北农林科技大学计算机组成原理脱机实验,西北农林科技大学_计算机组成原理XP实验系统要素.ppt...
  9. 用Nginx分流绕开Github反爬机制
  10. 如何处理服务器SSL收到了一个弱临时Diffie-Hellman 密钥?
  11. Anguar 使用interceptor拦截器设置请求头传入jwt token
  12. 详解循环神经网络RNN(理论篇)
  13. 仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)...
  14. 2021-06-15
  15. H5 Laya 字体
  16. [apk破解]AirPin,无告用户书,无升级提示
  17. 计算机ip地址无法更改,win7电脑无法更改ip地址静态ip不能保存怎么办
  18. QT FOR 安卓动态旋转屏幕
  19. linux桌面记事本,推荐6款简单实用的手机记事本APP,总有一款适合你的‖APP展览馆...
  20. 语法糖(Syntactic sugar)/ 语法盐(syntactic salt)

热门文章

  1. 微信银行卡如何解除绑定?图文教程,快速解除
  2. 中职学校计算机应用基础试卷,(完整版)中职计算机应用基础试卷.(答案)doc
  3. sql服务器显示error,如图,一直显示error: 40 - 无法打开到 SQL Server 的连接,我用的是sql server 2012,使用c#制作网站,...
  4. 深度学习论文阅读目标检测篇(五)中英对照版:YOLOv2《 YOLO9000: Better, Faster, Stronger》
  5. 英语钻石法则(四)-----深入学习
  6. 北京/上海内推 | 小红书社区搜索组招聘多模态/NLP算法实习生
  7. BIM与GIS 融合技术方案 从此公路施工实现信息化
  8. 从零开始Desire HD刷机指南——第十四章:如何制作金卡
  9. 哪个网盘比较好,想找一个好用又安全的网盘
  10. 【ARTS】01_02_左耳听风-20181119~1125