vue源码解读
‘壹’ vuejs源码用了什么设计模式,具体点的
最简单的订阅者模式
// Observer
class Observer {
constructor (data) {
this.walk(data)
}
walk (data) {
// 遍历
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
defineReactive(data, keys[i], data[keys[i]])
}
}
}
function defineReactive (data, key, val) {
observer(val)
// dep 为什么要在这里实例化, 就是为了实现, 对象每一层的 每一个key都有自己的一个订阅实例, 比如 a.b 对应 dep1, a.c 对应dep2, 这里虽然都是let dep = new Dep()
// 但每次来到这个方法, dep都是独立的, 会一直保留在内存. 这样在每次调用set方法都能找到这个a.b对应的dep
// dep 这里会一直保存, 是因为闭包的关系, Object这个全局的函数, 引用了上层的作用域, 这个作用域包含了 dep, 除非Object = null, 或者退出浏览器, dep才会消失
//实例化之后, dep就有了被订阅, 和发布消息的功能, dep不写在这里也是可以的, 多定义一个全局函数, 每次obser的时候增加一个dep
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
//每次new Watch('a.b'), 都会先执行get方法, 进而来到这里, 触发 dep.depend(), 这个dep就是 a.b 对应的 订阅,
dep.depend()
return val
},
set: function (newVal) {
if(val === newVal){
return
‘贰’ Vue入坑史,插件系统详解
在谈主题插件之前,我想先引出两个关于 Vue 的问题,这也是我存在的两个疑问,希望有人能够帮忙解答。
如果你比较心急,可以直接跳到 Vue.use源码解读 。
这两个是我最近在开发过程中遇到的问题,也没有找到相关的答案,希望有谁能够帮忙解答,在此先行谢过了。
Vue 的插件一般就是用来 扩展Vue的功能 。比如,当需要 Vue 实现 Ajax 请求功能,我们希望通过 this.$get(url) 的形式就可以发送一个 get 请求。那么,我们就需要给 Vue 的实例添加一个 $get 方法, Vue 实例本身是没有这个方法的。
Vue 的一些插件:
在创建 Vue 实例之前,通过全局方法 Vue.use() 来使用插件:
是不是很简单,好像也没有什么好说的。
有时候,我们看到某些插件使用起来可能有些不一样。比如使用 vuex :
其实本质上还是一样的,也是通过 Vue.use() 方法注册插件。只不过它有一个 store 对象,然后并将 store 对象作为 Vue 根实例的属性,以便组件通过 this.$store 这种形式来访问。
其实当通过 Vue.use() 注册插件时,内部会自动调用插件的 install() 方法。也就是说,插件必须要提供一个公开的 install() 方法,作为接口。该方法第一个参数是 Vue ,第二个参数是可选的 options 对象。
总结起来说,插件是一个对象。该对象要有一个公开的 install() 方法,那么写起来可能是这样的:
在 install() 方法中,我们通过参数可以拿到Vue对象,那么,我们就可以对它做很多事情。
这里直接就看几个插件的源码吧,看看他们是怎么写的,其实我也是参照了这些源码才真正弄明白了插件是怎么一回事。源码很长,这里只说一些关键点。
针对 vue-resource 插件问题,我查看了一下 vue 的源码,它的源码是这样的:
通过源码,我们知道, Vue插件可以是一个对象或者是一个函数 。只有当插件实现了 install 接口时(有 install 这个函数时),才会调用插件的 install 方法;否则再判断插件本身是否是一个函数,如果是,就直接调用它。
现在就能很好的解释 vue-resource 插件的写法了。好吧,我也是刚刚得知,又长了一点见识。
其实官网也有 说明 :
写一篇文章,对别人来说是一种分享,其实对自己来说更是一种提高。因为你要写好一篇文章,一方面你得尽量保证其正确性,有时候你不得不亲自去验证,另一方面也是对自己所学的知识进行一遍系统的复习和整理。
如果你有时间,我建议你多写一些技术类文章;如果你实在没时间写,那也要多读读别人写的文章。
‘叁’ 这种VUE代码 是怎么写的
应该是打包工具自动生成的吧。
像这种代码类似库源码,是挺难阅读的。
‘肆’ vue生命周期详解
vue源码中最终执行生命周期函数都是调用 callHook 方法, callHook 函数的逻辑很简单,根据传入的生命周期类型 hook ,去拿到 vm.$options[hook] 对应的回调函数数组,然后遍历执行,执行的时候把 vm 作为函数执行的上下文。
1. new Vue(options) :创建一个vm实例;
2. mergeOptions(resolveConstructorOptions(vm.constructor), options, vm) :合并Vue构造函数里options和传入的options或合并父子的options。比如:在mergeOptions函数中会调用mergeHook方法合并生命周期的钩子函数,mergeHook方法原理是只有父时返回父,只有子时返回数组类型的子。父、子都存在时,将子添加在父的后面返回组合而成的数组。这也是父子均有钩子函数的时候,先执行父的后执行子的的原因;
3. initLifecycle(vm)、initEvents(vm)、initRender(vm) :在创建的vm实例上初始化生命周期、事件、渲染相关的属性;
4. callHook(vm, 'beforeCreate') :调用beforeCreate生命周期钩子函数;
5. initInjections(vm)、initState(vm)、initProvide(vm) :初始化数据:inject、state、provide。initState 的作用是初始化 props、data、methods、watch、computed 等属性;
6. callHook(vm, 'created') :调用created生命周期钩子函数;
7. vm.$mount(vm.$options.el) : $mount 方法在多个文件中都有定义,如"src/platform/web/entry-runtime-with-compiler.js"、"src/platform/web/runtime/index.js"、"src/platform/weex/runtime/index.js"。因为 $mount 方法的实现是和平台、构建方式相关的。以"entry-runtime-with-compiler.js"为例,关键步骤是查看 vm.$options 中是否有render方法,如果没有则会根据el和template属性确定最终的template字符串,再调用 compileToFunctions 方法将template字符串转为render方法,最后,调用原先原型上的$mount方法,即开始执行"lifecycle.js"中 mountComponent 方法;
8. callHook(vm, 'beforeMount') :调用beforeMount生命周期钩子函数;
9. vm._render() => vm._update() => vm.__patch__() :先执行vm._render方法,即调用createElement生成虚拟DOM,即VNode ,每个VNode有children ,children 每个元素也是⼀个 VNode,这样就形成了⼀个 VNode Tree;再调用vm._update方法进行首次渲染,vm._update方法核心是调用vm. patch 方法,这个方法跟vm.$mount一样跟平台相关;vm. patch 方法则是根据生成的VNode Tree递归createElm方法创建真实Dom Tree挂载到Dom上;
10. callHook(vm, 'mount') :调用mount生命周期钩子函数:VNode patch 到 Dom 之后会执行 'invokeInsertHook'函数,把 insertedVnodeQueue 中保存的mount钩子函数执行一遍,insertedVnodeQueue队列中的钩子函数是在根据VNode Tree递归createElm方法创建真实Dom Tree过程生成的钩子函数顺序队列,因此mounted钩子函数的执行顺序是先子后父;
11. data changes :数据更新,nextTick中执行 flushSchelerQueue 方法,该方法会执行watcher队列中的watcher;
12. callHook(vm, 'beforeUpdate') :执行watcher时会执行watcher的before方法,即调用beforeUpdate生命周期钩子函数;
13. Virtual DOM re-render and patch :重新render生成新的Virtual DOM,并且patch到DOM上;
14. callHook(vm, 'updated') :调用updated生命周期钩子函数;
15. vm.$destroy() :启动卸销毁过程;
16. callHook(vm, 'beforeDestroy') :调用beforeDestroy生命周期钩子函数;
17. Teardown watchers, childcomponents and event listeners :执行一系列销毁动作,在 $destroy 的执行过程中,它又会执行 vm.__patch__(vm._vnode, null) 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 destroyed 钩子函数执行顺序是先子后父,和 mounted 过程一样。
18. callHook(vm, 'destroyed ') :调用destroyed 生命周期钩子函数。
‘伍’ web前端开发培训去哪好
很多人对前端开发工程师的岗位比较感兴趣但是却不清楚该怎么下手,这是大多数人都会想到去前端培训班进行学习。这主要是因为前端培训符合更多人的需求,大家都想要系统的学好前端开发技术,并且在最快的时间能够掌握这些技术知识,并且找到相对应的工作。那么,这样一来培训班就成为了大家最好的选择。
如果你想要快速入行找到一家比较好的前端开发培训机构,个人觉得大家可以通过下边这几个步骤进行。
第一步、首先,去了解一下这些前端培训机构的口碑怎么样,通过口碑我们可以初步的筛选出一些不错的机构,毕竟口碑也是需要得到广大学员的认可,并通过长时间的积累才能够达到的,参考价值还是比较高的。
第二步、就是去了解每一个机构的师资、课程内容、学习环境以及就业服务等多方面和学习就业有直接关系的内容,从而保障大家在培训学习过程中的教学质量,让自己能够在学习结束后获得更多的知识。
第三步、就是进行实地考察,我们在网络上或者是很朋友哪里得到的情况,很多的时候还是会存在应的差别。因为培训机构也并不是一层不变的,有些机构可能之前确实比较好,但经过多年的发展也可能变差,所以想要了解真实的情况,还是免不了要自己去实地进行考察一番,这样对于自己的选择更有益处。
‘陆’ 【面试题解析】从 Vue 源码分析 key 的作用
最近看了面试题中有一个这样的题, v-for 为什么要绑定 key?
Vue 中 key 很多人都弄不清楚有什么作用,甚至还有些人认为不绑定 key 就会报错。
其实没绑定 key 的话,Vue 还是可以正常运行的,报警告是因为没通过 Eslint 的检查。
接下来将通过源码一步步分析这个 key 的作用。
Virtual DOM 最主要保留了 DOM 元素的层级关系和一些基本属性,本质上就是一个 JS 对象。相对于真实的 DOM,Virtual DOM 更简单,操作起来速度更快。
如果需要改变 DOM,则会通过新旧 Virtual DOM 对比,找出需要修改的节点进行真实的 DOM 操作,从而减小性能消耗。
传统的 Diff 算法需要遍历一个树的每个节点,与另一棵树的每个节点对比,时间复杂度为 O(n²)。
Vue 采用的 Diff 算法则通过逐级对比,大大降低了复杂性,时间复杂度为 O(n)。
VNode 更新首先会经过 patch 函数, patch 函数源码如下:
vnode 表示更新后的节点,oldVnode 表示更新前的节点,通过对比新旧节点进行操作。
1、vnode 未定义,oldVnode 存在则触发 destroy 的钩子函数
2、oldVnode 未定义,则根据 vnode 创建新的元素
3、oldVnode 不为真实元素并且 oldVnode 与 vnode 为同一节点,则会调用 patchVnode 触发更新
4、oldVnode 为真实元素或者 oldVnode 与 vnode 不是同一节点,另做处理
接下来会进入 patchVnode 函数,源码如下:
1、vnode 的 text 不存在,则会比对 oldVnode 与 vnode 的 children 节点进行更新操作
2、vnode 的 text 存在,则会修改 DOM 节点的 text
接下来在 updateChildren 函数内就可以看到 key 的用处。
key 的作用主要是给 VNode 添加唯一标识,通过这个 key,可以更快找到新旧 VNode 的变化,从而进一步操作。
key 的作用主要表现在以下这段源码中。
updateChildren 过程为:
1、分别用两个指针(startIndex, endIndex)表示 oldCh 和 newCh 的头尾节点
2、对指针所对应的节点做一个两两比较,判断是否属于同一节点
3、如果4种比较都没有匹配,那么判断是否有 key,有 key 就会用 key 去做一个比较;无 key 则会通过遍历的形式进行比较
4、比较的过程中,指针往中间靠,当有一个 startIndex > endIndex,则表示有一个已经遍历完了,比较结束
从 VNode 的渲染过程可以得知,Vue 的 Diff 算法先进行的是同级比较,然后再比较子节点。
子节点比较会通过 startIndex、endIndex 两个指针进行两两比较,再通过 key 比对子节点。如果没设置 key,则会通过遍历的方式匹配节点,增加性能消耗。
所以不绑定 key 并不会有问题,绑定 key 之后在性能上有一定的提升。
综上,key 主要是应用在 Diff 算法中,作用是为了更快速定位出相同的新旧节点,尽量减少 DOM 的创建和销毁的操作。
希望以上内容能够对各位小伙伴有所帮助,祝大家面试顺利。
Vue 的文档中对 key 的说明如下:
关于就地修改,关键在于 sameVnode 的实现,源码如下:
可以看出,当 key 未绑定时,主要通过元素的标签等进行判断,在 updateChildren 内会将 oldStartVnode 与 newStartVnode 判断为同一节点。
如果 VNode 中只包含了文本节点,在 patchVnode 中可以直接替换文本节点,而不需要移动节点的位置,确实在不绑定 key 的情况下效率要高一丢丢。
某些情况下不绑定 key 的效率更高,那为什么大部分Eslint的规则还是要求绑定 key 呢?
因为在实际项目中,大多数情况下 v-for 的节点内并不只有文本节点,那么 VNode 的字节点就要进行销毁和创建的操作。
相比替换文本带来的一丢丢提升,这部分会消耗更多的性能,得不偿失。
了解了就地修改,那么我们在一些简单节点上可以选择不绑定 key,从而提高性能。
如果你喜欢我的文章,希望可以关注一下我的公众号【前端develop】