前言
源码阅读可能会迟到,但是一定不会缺席!
众所周知,以下代码就是 vue 的一种直接上手方式。通过 cdn 可以在线打开 vue.js。一个文件,一万行源码,是万千开发者赖以生存的利器,它究竟做了什么?让人品味。
<html> <head></head> <body> <div id="app"> {{ message }} </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { message: 'See Vue again!' }, }) </script> </html>
源码cdn地址:cdn.jsdelivr.net/npm/vue/dis…,当下版本:v2.6.11。
本瓜选择生啃的原因是,可以更自主地选择代码段分轻重来阅读,一方面测试自己的掌握程度,一方面追求更直观的源码阅读。
当然你也可以选择在 github.com/vuejs/vue/t… 分模块的阅读,也可以看各路大神的归类整理。
其实由于本次任务量并不算小,为了能坚持下来,本瓜将源码尽量按 500 行作为一个模块来形成一个 md 文件记录(分解版本共 24 篇感兴趣可移步),结合注释、自己的理解、以及附上对应查询链接来逐行细读源码,此篇为合并版本。
目的:自我梳理,分享交流。
最佳阅读方式推荐:先点赞👍再阅读📖,靴靴靴靴😁
正文
第 1 行至第 10 行
// init
( function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Vue = factory()); }( this, function () { 'use strict'; //...核心代码... } ) ); // 变形 if (typeof exports === 'object' && typeof module !== 'undefined') { // 检查 CommonJS module.exports = factory() } else { if (typeof define === 'function' && define.amd) { // AMD 异步模块定义 检查JavaScript依赖管理库 require.js 的存在 [link](https://stackoverflow.com/questions/30953589/what-is-typeof-define-function-defineamd-used-for) define(factory) } else { (global = global || self, global.Vue = factory()); } } // 等价于 window.Vue=factory() // factory 是个匿名函数,该匿名函数并没自执行 设计参数 window,并传入window对象。不污染全局变量,也不会被别的代码污染
第 11 行至第 111 行
// 工具代码
var emptyObject = Object.freeze({});// 冻结的对象无法再更改 [link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
// 接下来是一些封装用来判断基本类型、引用类型、类型转换的方法
- isUndef//判断未定义
- isDef// 判断已定义
- isTrue// 判断为 true
- isFalse// 判断为 false
- isPrimitive// 判断为原始类型
- isObject// 判断为 obj
- toRawType // 切割引用类型得到后面的基本类型,例如:[object RegExp] 得到的就是 RegExp
- isPlainObject// 判断纯粹的对象:"纯粹的对象",就是通过 { }、new Object()、Object.create(null) 创建的对象
- isRegExp// 判断原生引用类型
- isValidArrayIndex// 检查val是否是一个有效的数组索引,其实就是验证是否是一个非无穷大的正整数
- isPromise// 判断是否是 Promise
- toString// 类型转成 String
- toNumber// 类型转成 Number
第 113 行至第 354 行
- makeMap// makeMap 方法将字符串切割,放到map中,用于校验其中的某个字符串是否存在(区分大小写)于map中 e.g.
var isBuiltInTag = makeMap('slot,component', true);// 是否为内置标签 isBuiltInTag('slot'); //true isBuiltInTag('slot1'); //undefined var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');// 是否为保留属性
- remove// 数组移除元素方法
- hasOwn// 判断对象是否含有某个属性
- cached// ※高级函数 cached函数,输入参数为函数,返回值为函数。同时使用了闭包,其会将该传入的函数的运行结果缓存,创建一个cache对象用于缓存运行fn的运行结果。link
function cached(fn) { var cache = Object.create(null);// 创建一个空对象 return (function cachedFn(str) {// 获取缓存对象str属性的值,如果该值存在,直接返回,不存在调用一次fn,然后将结果存放到缓存对象中 var hit = cache[str]; return hit || (cache[str] = fn(str)) }) }
- camelize// 驼峰化一个连字符连接的字符串
- capitalize// 对一个字符串首字母大写
- hyphenateRE// 用字符号连接一个驼峰的字符串
- polyfillBind// ※高级函数 参考link
- Function.prototype.bind() // link1link2
- toArray// 将像数组的转为真数组
- extend// 将多个属性插入目标的对象
- toObject// 将对象数组合并为单个对象。
e.g.
console.log(toObject(["bilibli"])) //{0: "b", 1: "i", 2: "l", 3: "i", 4: "b", 5: "l", 6: "i", encodeHTML: ƒ}
- no// 任何情况都返回false
- identity // 返回自身
- genStaticKeys// 从编译器模块生成包含静态键的字符串。TODO:demo
- looseEqual//※高级函数 对对象的浅相等进行判断
//有赞、头条面试题
function looseEqual(a, b) { if (a === b) return true const isObjectA = isObject(a) const isObjectB = isObject(b) if(isObjectA && isObjectB) { try { const isArrayA = Array.isArray(a) const isArrayB = Array.isArray(b) if(isArrayA && isArrayB) { return a.length === b.length && a.every((e, i) => { return looseEqual(e, b[i]) }) }else if(!isArrayA && !isArrayB) { const keysA = Object.keys(a) const keysB = Object.keys(b) return keysA.length === keysB.length && keysA.every(function (key) { return looseEqual(a[key], b[key]) }) }else { return false } } catch(e) { return false } }else if(!isObjectA && !isObjectB) { return String(a) === String(b) }else { return false } }
- looseIndexOf// 返回索引,如果没找到返回-1,否则执行looseEqual()
- once// 确保函数只被调用一次,用到闭包
阶段小结
- cached
- polyfillBind
- looseEqual
这三个函数要重点细品!主要的点是:闭包、类型判断,函数之间的互相调用。也即是这部分工具函数的精华!
第 356 行 至 第 612 行
// 定义常量和配置
- SSR_ATTR// 服务端渲染
- ASSET_TYPES// 全局函数 component、directive、filter
- LIFECYCLE_HOOKS// 生命周期,无需多言
- config // 全局配置 link
- unicodeRegExp//用于解析html标记、组件名称和属性pat的unicode字母
- isReserved// 检查变量的开头是 $ 或 _
- def// 在一个对象上定义一个属性的构造函数,其中 !!enumerable 强制转换 boolean
- parsePath// 解析一个简单路径 TODO:
- userAgent// 浏览器识别
- inBrowser
- _isServer//检测 vue的服务器渲染是否存在, 而且避免webpack去填充process
- isNative //这里判断 函数是否是系统函数, 比如 Function Object ExpReg window document 等等, 这些函数应该使用c/c++实现的。这样可以区分 Symbol是系统函数, 还是用户自定义了一个Symbol
- hasSymbol//这里使用了ES6的Reflect方法, 使用这个对象的目的是, 为了保证访问的是系统的原型方法, ownKeys 保证key的输出顺序, 先数组 后字符串
- _Set// 设置一个Set
第 616 行至第 706 行
//设置warn,tip等全局变量 TODO:
- warn
- tip
- generateComponentTrace// 生成组件跟踪路径(组件数规则)
- formatComponentName// 格式化组件名
第 710 行至第 763 行
Vue核心:数据监听最重要之一的 Dep
// Dep是订阅者Watcher对应的数据依赖 var Dep = function Dep () { //每个Dep都有唯一的ID this.id = uid++; //subs用于存放依赖 this.subs = []; }; //向subs数组添加依赖 Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub); }; //移除依赖 Dep.prototype.removeSub = function removeSub (sub) { remove(this.subs, sub); }; //设置某个Watcher的依赖 //这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用 //也就是说判断他是Watcher的this.get调用的,而不是普通调用 Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify () { var subs = this.subs.slice(); //通知所有绑定 Watcher。调用watcher的update() for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
强烈推荐阅读:link
Dep 相当于把 Observe 监听到的信号做一个收集(collect dependencies),然后通过dep.notify()再通知到对应 Watcher ,从而进行视图更新。
第 767 行至第 900 行
Vue核心:视图更新最重要的 VNode( Virtual DOM)
- VNode
- createEmptyVNode
- createTextVNode
- cloneVNode
把你的 template 模板 描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载
更新的时候对比旧的VNode和新的VNode,只更新有变化的那一部分,提高视图更新速度。
e.g.
<div class="parent" style="height:0" href="2222"> 111111 </div> //转成Vnode { tag: 'div', data: { attrs:{href:"2222"} staticClass: "parent", staticStyle: { height: "0" } }, children: [{ tag: undefined, text: "111111" }] }
强烈推荐阅读:link
- methodsToPatch
将数组的基本操作方法拓展,实现响应式,视图更新。
因为:对于对象的修改是可以直接触发响应式的,但是对数组直接赋值,是无法触发的,但是用到这里经过改造的方法。我们可以明显的看到 ob.dep.notify() 这一核心。
阶段小结
这一 part 最重要的,毋庸置疑是:Dep 和 VNode,需重点突破!!!
第 904 行至第 1073 行
Vue核心:数据监听最重要之一的 Observer
- 核心的核心!Observer(发布者) => Dep(订阅器) => Watcher(订阅者)
类比一个生活场景:报社将各种时下热点的新闻收集,然后制成各类报刊,发送到每家门口的邮箱里,订阅报刊人们看到了新闻,对新闻作出评论。
在这个场景里,报社==发布者,新闻==数据,邮箱==订阅器,订阅报刊的人==订阅者,对新闻评论==视图更新
- Observer//Observer的调用过程:initState()-->observe(data)-->new Observer()
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { this.walk(value); } };
- ※※ defineReactive 函数,定义一个响应式对象,给对象动态添加 getter 和 setter ,用于依赖收集和派发更新。
function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep()// 1. 为属性创建一个发布者 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get // 依赖收集 const setter = property && property.set // 派发更新 if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val)// 2. 获取属性值的__ob__属性 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend()// 3. 添加 Dep if (childOb) { childOb.dep.depend()//4. 也为属性值添加同样的 Dep if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
第 4 步非常重要。为对象的属性添加 dep.depend(),达到监听对象(引用的值)属性的目的
重点备注
Vue对数组的处理跟对象还是有挺大的不同,length是数组的一个很重要的属性,无论数组增加元素或者删除元素(通过splice,push等方法操作)length的值必定会更新,为什么不直接操作监听length呢?而需要拦截splice,push等方法进行数组的状态更新?
原因是:在数组length属性上用defineProperty拦截的时候,会报错。
Uncaught TypeError: Cannot redefine property: length 复制代码
再用Object.getOwnPropertyDescriptor(arr, 'length')查看一下://(Object.getOwnPropertyDescriptor用于返回defineProperty.descriptor)
{ configurable: false enumerable: false value: 0 writable: true } configurable为false,且MDN上也说重定义数组的length属性在不同浏览器上表现也是不一致的,所以还是老老实实拦截splice,push等方法,或者使用ES6的Proxy。
第 1075 行至第 1153 行
- set //在对象上设置一个属性。如果是新的属性就会触发更改通知(旧属性也会触发更新通知,因为第一个添加的时候已经监听了,之后自动触发,不再手动触发)
- del //删除一个属性,如果必要触发通知
- dependArray // 收集数组的依赖link
第 1157 行至第 1568 行
// 配置选项合并策略
ar strats = config.optionMergeStrategies; 复制代码
- mergeData
- strats.data
- mergeDataOrFn
- mergeHook
- mergeAssets
- strats.watch
- strats.computed
- defaultStrat
- checkComponents
- validateComponentName
- normalizeProps
- normalizeInject
- normalizeDirectives
- assertObjectType
- mergeOptions
这一部分代码写的就是父子组件配置项的合并策略,包括:默认的合并策略、钩子函数的合并策略、filters/props、data合并策略,且包括标准的组件名、props写法有一个统一化规范要求。
一图以蔽之
强烈推荐阅读:link
阶段小结
这一部分最重要的就是 Observer(观察者) ,这也是 Vue 核心中的核心!其次是 mergeOptions(组件配置项的合并策略),但是通常在用的过程中,就已经了解到了大部分的策略规则。
第 1570 行至第 1754 行
- resolveAsset// resolveAsset 全局注册组件用到
e.g.
我们的调用 resolveAsset(context.options.components[tag],这样我们就可以在 resolveAsset 的时候拿到这个组件的构造函数,并作为 createComponent 的钩子的参数。
- validateProp// prop的格式校验
校验prop:
- prop为Boolean类型时做特殊处理
- prop的值为空时,获取默认值,并创建观察者对象
- prop验证
- getPropDefaultValue// 获取默认 prop 值
获取 prop 的默认值 && 创建观察者对象
- @param {*} vm vm 实例
- @param {*} prop 定义选项
- @param {*} vmkey prop 的 key
// 在非生产环境下(除去 Weex 的某种情况),将对prop进行验证,包括验证required、type和自定义验证函数。
- assertProp //验证 prop Assert whether a prop is valid.
case 1: 验证 required 属性 case 1.1: prop 定义时是 required,但是调用组件时没有传递该值(警告) case 1.2: prop 定义时是非 required 的,且 value === null || value === undefined(符合要求,返回) case 2: 验证 type 属性-- value 的类型必须是 type 数组里的其中之一 case 3: 验证自定义验证函数
- assertType
`assertType`函数,验证`prop`的值符合指定的`type`类型,分为三类: - 第一类:通过`typeof`判断的类型,如`String`、`Number`、`Boolean`、`Function`、`Symbol` - 第二类:通过`Object.prototype.toString`判断`Object`/`Array` - 第三类:通过`instanceof`判断自定义的引用类型
第 1756 行至第 1823 行
// 辅助函数:检测内置类型
- getType
- isSameType
- getTypeIndex
- getInvalidTypeMessage
- styleValue
- isExplicable
- isBoolean
第 1827 行至第 1901 行
// 辅助函数:处理错误、错误打印
- handleError
- invokeWithErrorHandling
- globalHandleError
- logError
第 1905 行至第 2007 行
- flushCallbacks// flushCallbacks 挨个同步执行callbacks中回调
- MutationObserver
- nextTick// 把传入的 cb 回调函数用 try-catch 包裹后放在一个匿名函数中推入callbacks数组中,这么做是因为防止单个 cb 如果执行错误不至于让整个JS线程挂掉,每个 cb 都包裹是防止这些回调函数如果执行错误不会相互影响,比如前一个抛错了后一个仍然可以执行。
精髓中的精髓 —— nextTick
这里有一段很重要的注释
// Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). 在vue2.5之前的版本中,nextTick基本上基于 micro task 来实现的,但是在某些情况下 micro task 具有太高的优先级,并且可能在连续顺序事件之间(例如#4521,#6690)或者甚至在同一事件的事件冒泡过程中之间触发(#6566)。但是如果全部都改成 macro task,对一些有重绘和动画的场景也会有性能影响,如 issue #6813。vue2.5之后版本提供的解决办法是默认使用 micro task,但在需要时(例如在v-on附加的事件处理程序中)强制使用 macro task。
什么意思呢?分析下面这段代码。
<span id='name' ref='name'>{{ name }}</span> <button @click='change'>change name</button> methods: { change() { this.$nextTick(() => console.log('setter前:' + this.$refs.name.innerHTML)) this.name = ' vue3 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + this.$refs.name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + this.$refs.name.innerHTML)) } } //同步方式:vue2 //setter前:vue2 //setter后: vue3 //Promise方式: vue3 //setTimeout方式: vue3
- 同步方式: 当把data中的name修改之后,此时会触发name的 setter 中的 dep.notify 通知依赖本data的render watcher去 update,update 会把 flushSchedulerQueue 函数传递给 nextTick,render watcher在 flushSchedulerQueue 函数运行时 watcher.run 再走 diff -> patch 那一套重渲染 re-render 视图,这个过程中会重新依赖收集,这个过程是异步的;所以当我们直接修改了name之后打印,这时异步的改动还没有被 patch 到视图上,所以获取视图上的DOM元素还是原来的内容。
- setter前: setter前为什么还打印原来的是原来内容呢,是因为 nextTick 在被调用的时候把回调挨个push进callbacks数组,之后执行的时候也是 for 循环出来挨个执行,所以是类似于队列这样一个概念,先入先出;在修改name之后,触发把render watcher填入 schedulerQueue 队列并把他的执行函数 flushSchedulerQueue 传递给 nextTick ,此时callbacks队列中已经有了 setter前函数 了,因为这个 cb 是在 setter前函数 之后被push进callbacks队列的,那么先入先出的执行callbacks中回调的时候先执行 setter前函数,这时并未执行render watcher的 watcher.run,所以打印DOM元素仍然是原来的内容。
- setter后: setter后这时已经执行完 flushSchedulerQueue,这时render watcher已经把改动 patch 到视图上,所以此时获取DOM是改过之后的内容。
- Promise方式: 相当于 Promise.then 的方式执行这个函数,此时DOM已经更改。
- setTimeout方式: 最后执行macro task的任务,此时DOM已经更改。
备注:前文提过,在依赖收集原理的响应式化方法 defineReactive 中的 setter 访问器中有派发更新 dep.notify() 方法,这个方法会挨个通知在 dep 的 subs 中收集的订阅自己变动的 watchers 执行 update。
强烈推荐阅读:link
0 行 至 2000 行小结
0 至 2000 行主要的内容是:
- 工具代码
- 数据监听:Obeserve,Dep
- Vnode
- nextTick
第 2011 行至第 2232 行
- perf// performance
- initProxy// 代理对象是es6的新特性,它主要用来自定义对象一些基本操作(如查找,赋值,枚举等)。link
//proxy是一个强大的特性,为我们提供了很多"元编程"能力。
const handler = { get: function(obj, prop) { return prop in obj ? obj[prop] : 37; } }; const p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37
- traverse// 遍历:_traverse 深度遍历,用于
traverse 对一个对象做深层递归遍历,因为遍历过程中就是对一个子对象的访问,会触发它们的 getter 过程,这样就可以收集到依赖,也就是订阅它们变化的 watcher,且遍历过程中会把子响应式对象通过它们的 dep id 记录到 seenObjects,避免以后重复访问。
- normalizeEvent// normalizeEvents是针对v-model的处理,例如在IE下不支持change事件,只能用input事件代替。
- createFnInvoker// 在初始构建实例时,旧节点是不存在的,此时会调用createFnInvoker函数对事件回调函数做一层封装,由于单个事件的回调可以有多个,因此createFnInvoker的作用是对单个,多个回调事件统一封装处理,返回一个当事件触发时真正执行的匿名函数。
- updateListeners// updateListeners的逻辑也很简单,它会遍历on事件对新节点事件绑定注册事件,对旧节点移除事件监听,它即要处理原生DOM事件的添加和移除,也要处理自定义事件的添加和移除,
阶段小结
第 2236 行至第 2422 行
- mergeVNodeHook// 重点 合并 VNode
// 把 hook 函数合并到 def.data.hook[hookey] 中,生成新的 invoker,createFnInvoker 方法
// vnode 原本定义了 init、prepatch、insert、destroy 四个钩子函数,而 mergeVNodeHook 函数就是把一些新的钩子函数合并进来,例如在 transition 过程中合并的 insert 钩子函数,就会合并到组件 vnode 的 insert 钩子函数中,这样当组件插入后,就会执行我们定义的 enterHook 了。
- extractPropsFromVNodeData// 抽取相应的从父组件上的prop
- checkProp// 校验 Prop
// The template compiler attempts to minimize the need for normalization by // statically analyzing the template at compile time. // 模板编译器尝试用最小的需求去规范:在编译时,静态分析模板 // For plain HTML markup, normalization can be completely skipped because the // generated render function is guaranteed to return Array<VNode>. There are // two cases where extra normalization is needed: // 对于纯 HTML 标签,可跳过标准化,因为生成渲染函数一定会会返回 Vnode Array.有两种情况,需要额外去规范 // 1. When the children contains components - because a functional component // may return an Array instead of a single root. In this case, just a simple // normalization is needed - if any child is an Array, we flatten the whole // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep // because functional components already normalize their own children. // 当子级包含组件时-因为功能组件可能会返回Array而不是单个根。在这种情况下,需要规范化-如果任何子级是Array,我们将整个具有Array.prototype.concat的东西。保证只有1级深度,因为功能组件已经规范了自己的子代。 // 2. When the children contains constructs that always generated nested Arrays, // e.g. <template>, <slot>, v-for, or when the children is provided by user // with hand-written render functions / JSX. In such cases a full normalization // is needed to cater to all possible types of children values. // 当子级包含始终生成嵌套数组的构造时,例如<template>,<slot>,v-for或用户提供子代时,具有手写的渲染功能/ JSX。在这种情况下,完全归一化,才能满足所有可能类型的子代值。
Q:这一段话说的是什么意思呢?
A:归一化操作其实就是将多维的数组,合并转换成一个一维的数组。在 Vue 中归一化分为三个级别,
- 不需要进行归一化
- 只需要简单的归一化处理,将数组打平一层
- 完全归一化,将一个 N 层的 children 完全打平为一维数组
利用递归来处理的,同时处理了一些边界情况。
第 2426 行至第 2490 行
- initProvide
- initInjections
- resolveInject
第 2497 行至第 2958 行
- resolveSlots// Runtime helper for resolving raw children VNodes into a slot object.
- isWhitespace
- normalizeScopedSlots
- normalizeScopedSlot
- proxyNormalSlot
- renderList// Runtime helper for rendering v-for lists.
- renderSlot// Runtime helper for rendering
<slot>
- resolveFilter// Runtime helper for resolving filters
- checkKeyCodes// Runtime helper for checking keyCodes from config.
- bindObjectProps// Runtime helper for merging v-bind="object" into a VNode's data.
- renderStatic// Runtime helper for rendering static trees.
- markOnce// Runtime helper for v-once.
这一部分讲的是辅助程序 —— Vue 的各类渲染方法,从字面意思中可以知道一些方法的用途,这些方法用在Vue生成的渲染函数中。
- installRenderHelpers// installRenderHelpers 用于执行以上。
第 2962 行至第 3515 行
- FunctionalRenderContext// 创建一个包含渲染要素的函数
- createFunctionalComponent
函数式组件的实现
Ctor, //Ctro:组件的构造对象(Vue.extend()里的那个Sub函数) propsData, //propsData:父组件传递过来的数据(还未验证) data, //data:组件的数据 contextVm, //contextVm:Vue实例 children //children:引用该组件时定义的子节点 复制代码
// createFunctionalComponent 最后会执行我们的 render 函数
特注:Vue 组件是 Vue 的核心之一
组件分为:异步组件和函数式组件
这里就是函数式组件相关
Vue提供了一种可以让组件变为无状态、无实例的函数化组件。从原理上说,一般子组件都会经过实例化的过程,而单纯的函数组件并没有这个过程,它可以简单理解为一个中间层,只处理数据,不创建实例,也是由于这个行为,它的渲染开销会低很多。实际的应用场景是,当我们需要在多个组件中选择一个来代为渲染,或者在将children,props,data等数据传递给子组件前进行数据处理时,我们都可以用函数式组件来完成,它本质上也是对组件的一个外部包装。
函数式组件会在组件的对象定义中,将functional属性设置为true,这个属性是区别普通组件和函数式组件的关键。同样的在遇到子组件占位符时,会进入createComponent进行子组件Vnode的创建。**由于functional属性的存在,代码会进入函数式组件的分支中,并返回createFunctionalComponent调用的结果。**注意,执行完createFunctionalComponent后,后续创建子Vnode的逻辑不会执行,这也是之后在创建真实节点过程中不会有子Vnode去实例化子组件的原因。(无实例)
- cloneAndMarkFunctionalResult
- mergeProps
- componentVNodeHooks
- createComponent // createComponent 方法创建一个组件的 VNode。这 createComponent 是创建子组件的关键
// 创建组件的 VNode 时,若组件是函数式组件,则其 VNode 的创建过程将与普通组件有所区别。
- createComponentInstanceForVnode // link
推荐阅读:link
- installComponentHooks // installComponentHooks就是把 componentVNodeHooks的钩子函数合并到data.hook中,,在合并过程中,如果某个时机的钩子已经存在data.hook中,那么通过执行mergeHook函数做合并勾子。
- mergeHook$1
- transformModel
- createElement// 创建元素
- _createElement
- applyNS
- registerDeepBindings
- initRender // 初识渲染
阶段小结
这一部分主要是围绕 Vue 的组件的创建。Vue 将页面划分成各类的组件,组件思想是 Vue 的精髓之一。