事前准备
- git 克隆
git@github.com:vuejs/vue.git
- 执行编译:
npx rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev
得到dist/vue.js
问题
- v-if v-for 哪个优先级高?如何优化?
- Vue组件里data为何是函数,根实例直接写就可以?
- vue的key的作用和工作原理
- Vue.use 插件机制 和 beforeCreate
v-if v-for 哪个优先级高?如何优化?
这个问题可以惊艳一把,需要搞清楚是 vue2.x 还是 vue3 ,两者结论不同,因为vue3的list文档是我翻译的哈哈哈,记忆犹新。
结论是 vue2 中 for 优先级高。vue3中 if 优先级高。
vue2 官方文档提到了这一点: v-for v-if 一起使用
v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中
代码中大概是这样子描述的,渲染 render函数是这样书写的:
_l(循环数据,function(){return (if条件)})
进一步地,源码中是如何实现的? src/compiler/codegen/index.js
line 64 搜索 el.for
这是一锤定音:
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) { // 这里先对for进行生成,然后是if
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {}
如何优化?识别意图,分两种。计算或者前置,略。
Vue组件里data为何是函数,根实例直接写就可以?
结论:
在编写代码过程中,vue组件强制要求函数return对象,根实例无此要求。
这里也涉及到一个单例模式、多例模式的概念,因为组件可能存在多个实例,函数会返回能避免污染:通过创建多个实例避免数据污染。
根组件兼容两种写法,如果是函数会自动执行调用获得返回结果,并且只有一个实例,不需要关心。
源码中存在一个 mergeDataOrFn
方法,根实例执行时候会忽略检查。组件会进入判断。
源码有进一步的指明,根组件无所谓函数或者对象:
从 initState 进入,定位到 src/core/instance/state.js 的 #initData()
代码大致如下:
function initData (vm: Component) {
let data = vm.$options.data // 拿到根节点挂载的data
// 如果 挂载的data是函数就执行 getData 否则就直接挂载
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 如果不是一个对象,就要告警
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
}
组件级别的源码如何判断?
合并 MergeOption vm会被判断,看是否会跳过。
// src/core/instance/init.js#initMixin
// merge options 合并选项
if (options && options._isComponent) { // 此时是一个组件
// optimize internal component instantiation 优化内部组件初始化
// since dynamic options merging is pretty slow, and none of the 动态选项合并很慢
// internal component options needs special treatment. 内部组件选项都不用特别关注
initInternalComponent(vm, options)
} else { // 核心 mergeOptions
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
当我们对其打断点会发现,全局组件先执行,根组件后。
观察data策略合并,会定位到 sr/core/util/options.js#mergeDataOrFn
中的 states.data
:
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component // 第三个参数可选,是否是组件
): ?Function {
if (!vm) { // 如果没有传递组件,需要把 parent child 进行合并
if (childVal && typeof childVal !== 'function') {
// 这里就定义了必须是一个函数
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
// 如果传递了组件,需要对 vm 实例额外合并。这里并没有走进去warn
return mergeDataOrFn(parentVal, childVal, vm)
}
vue的key的作用和工作原理
结论: 唯一确定元素,diff更高效。
假定存在下面的demo
<li v-for="item of arr" :key="item.id"></li>
var arr=[a,b,c,d,e]
// do sth
var arr = arr.splice(2,0,'f') // 修改了值
也就变成了:
a b c d e
a b f c d e
- 无 key 的情况下,
a b c d e
a b f c d e
从左向右一次覆盖。c会替换成f,后续覆盖,e单独创建。会执行 5次更新+1次创建
- 有key的情况下,遇到 c 和 f 不同,会进行尾部判断,从右向左。
e d c 都会发现对应。最终创建 f 插入。5次更新+1次创建。
更新次数不变,但不同的是 是否真正会更新 :
- 不加key,执行更新3次+1次创建
- 加key执行0次更新+1次创建。
因为后续会进行 key判断(默认undefined)、 tag类型、内容类型的判断,如果大的方向是一致,会认为是同一个节点,会执行更新,会频繁更新操作。
面对下面的情况:
c d e
f c d e
会判断 c e,e e ,发现后面相同。
patch.js 算法 sameVnode
Vue的 diff 算法
diff算法很多,我们关注的是 vue2.x 的diff 算法。
结论:
diff 是自然而然产生的,涉及到 虚拟DOM 必然的产物。一个组件对应一个watcher,颗粒度适中。整体上:深度优先、同层比较。
观察 path.js # patchVnode
是diff 发生的地方。整体上:深度优先、同层比较。
观察是否有child,先进入子类,递归。
同层比较,文本节点比对。新增子节点、比较相同节点。
高效的 updateChildren
Vue组件化的理解
定义、有点、场景、注意事项、优化。
我们通过书写一个 sfc 单文件通过 vue-loader 渲染为 render函数,导出的本质还是配置对象,框架会基于 vue拓展VueComponent,基于此生成相关构造函数。
从 lifecycle.js#mountCompoent 中看到 Watcher的创建。合理区分颗粒度。
独立可复用是核心,降低复杂度,方便维护和测试。
$nextTick
对异步更新队列和策略有关。
定义、场景、实现
全局api,回调。在下次DOM更新循环结束之后执行。有些数据DOM频繁修改,不会立刻生效,需要使用 nextTick
Vue更新DOM是异步的,会合并,微任务的方式在同步代码执行结束之后更新watcher。
使用 Promise.then 回到函数,其他 mutation 等,最晚是宏任务 settimeout
Vue.use 插件机制 和 beforeCreate
vue插件是如何做到全局注册的。
观察 router源码:
vue-router/src/index.js — init 判断 installed — VueRouter.install
install.js install Vue.mixin beforeCreate
在使用 vue-router 时候 为何 vue.use(xxx) 就可以
Vue.use 源码很简单 https://github.com/vuejs/vue/blob/dev/src/core/global-api/use.js
use 本身是function,会自动判断 install