1.每天学点Vue源码之vm.$mount挂载函数
2.如何有效的耐克耐克进行Code Review
每天学点Vue源码之vm.$mount挂载函数
在vue实例中,通过$mount()实现实例的童鞋童鞋挂载,下面来分析一下$mount()函数都实现了什么功能。源码源码
$mount函数执行位置
_init这个私有方法是查询在执行initMixin时候绑定到Vue原型上的。
$mount函数是耐克耐克如如何把组件挂在到指定元素
$mount函数定义位置
$mount函数定义位置有两个:
第一个是在src/platforms/web/runtime/index.js
这里的$mount是一个public mount method。之所以这么说是童鞋童鞋三丰云代虚拟主机上传源码因为Vue有很多构建版本, 有些版本会依赖此方法进行有些功能定制, 后续会解释。
// public mount method// el: 可以是源码源码一个字符串或者Dom元素// hydrating 是Virtual DOM 的补丁算法参数Vue.prototype.$mount = function ( el? string | Element, hydrating? boolean): Component { // 判断el, 以及宿主环境, 然后通过工具函数query重写el。 el = el && inBrowser ?查询 query(el) : undefined // 执行真正的挂载并返回 return mountComponent(this, el, hydrating)}
src/platforms/web/runtime/index.js 文件是运行时版 Vue 的入口文件,所以这个方法是运行时版本Vue执行的$mount。
关于Vue不同构建版本可以看 Vue对不同构建版本的耐克耐克解释 。
关于这个作者封装的童鞋童鞋工具函数query也可以学习下:
/** * Query an element selector if it's not an element already. */export function query (el: string | Element): Element { if (typeof el === 'string') { const selected = document.querySelector(el) if (!selected) { // 开发环境下给出错误提示 process.env.NODE_ENV !== 'production' && warn( 'Cannot find element: ' + el ) // 没有找到的情况下容错处理 return document.createElement('div') } return selected } else { return el }}
第二个定义 $mount 函数的地方是src/platforms/web/entry-runtime-with-compiler.js 文件,这个文件是源码源码完整版Vue(运行时+编译器)的入口文件。
关于运行时与编译器不清楚的查询童鞋可以看官网 运行时 + 编译器 vs. 只包含运行时 。
// 缓存运行时候定义的耐克耐克公共$mount方法const mount = Vue.prototype.$mountVue.prototype.$mount = function ( el? string | Element, hydrating? boolean): Component { // 通过query方法重写el(挂载点: 组件挂载的占位符) el = el && query(el) /* istanbul ignore if */ // 提示不能把body/html作为挂载点, 开发环境下给出错误提示 // 因为挂载点是会被组件模板自身替换点, 显然body/html不能被替换 if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } // $options是在new Vue(options)时候_init方法内执行. // $options可以访问到options的所有属性如data, filter, components, directives等 const options = this.$options // resolve template/el and convert to render function // 如果包含render函数则执行跳出,直接执行运行时版本的$mount方法 if (!options.render) { // 没有render函数时候优先考虑template属性 let template = options.template if (template) { // template存在且template的类型是字符串 if (typeof template === 'string') { if (template.charAt(0) === '#') { // template是ID template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${ options.template}`, this ) } } } else if (template.nodeType) { // template 的类型是元素节点,则使用该元素的 innerHTML 作为模板 template = template.innerHTML } else { // 若 template既不是字符串又不是元素节点,那么在开发环境会提示开发者传递的童鞋童鞋 template 选项无效 if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { // 如果template选项不存在,那么使用el元素的源码源码outerHTML 作为模板内容 template = getOuterHTML(el) } // template: 存储着最终用来生成渲染函数的字符串 if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // 获取转换后的render函数与staticRenderFns,并挂在$options上 const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ // 用来统计编译器性能, config是全局配置对象 if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${ this._name} compile`, 'compile', 'compile end') } } } // 调用之前说的公共mount方法 // 重写$mount方法是为了添加模板编译的功能 return mount.call(this, el, hydrating)}
关于idToTemplate方法: 通过query获取该ID获取DOM并把该元素的innerHTML 作为模板
const idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML})
getOuterHTML方法:
/** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { // fix IE9- 中 SVG 标签元素是没有 innerHTML 和 outerHTML 这两个属性 const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML }}
关于compileToFunctions函数, 在src/platforms/web/entry-runtime-with-compiler.js中可以看到会挂载到Vue上作为一个全局方法。
mountComponent方法: 真正执行绑定组件
mountComponent函数中是出现在src/core/instance/lifecycle.js。export function mountComponent ( vm: Component, // 组件实例vm el: ?Element, // 挂载点 hydrating? boolean): Component { // 在组件实例对象上添加$el属性 // $el的值是组件模板根元素的引用 vm.$el = el if (!vm.$options.render) { // 渲染函数不存在, 这时将会创建一个空的vnode对象 vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 触发 beforeMount 生命周期钩子 callHook(vm, 'beforeMount') // vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)。template => render => vnode // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM。 vnode => real dom node let updateComponent // 把渲染函数生成的虚拟DOM渲染成真正的DOM /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${ id}` const endTag = `vue-perf-end:${ id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${ name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${ name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined // 创建一个Render函数的观察者, 关于watcher后续再讲述. new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm}
如何有效的进行Code Review
什么是Code Review?
Code Review代码评审是指在软件开发过程中,通过对源代码进行系统性检查的过程。通常的目的是查找各种缺陷,包括代码缺陷、功能实现问题、编码合理性、性能优化等;保证软件总体质量和提高开发者自身水平。可可登录界面源码 Code Review是轻量级代码评审,相对于正式代码评审,轻量级代码评审所需要的各种成本要明显低得多,如果流程正确,它可以起到更加积极的效果。正因如此,轻量级代码评审经常性地被引入到软件开发过程中。
为什么Code Review?
1.提高代码质量。
2.及早发现潜在缺陷,降低修改/弥补缺陷的成本。
3.促进团队内部知识共享,提高团队整体水平。
4.评审过程对于评审人员来说,也是一种思路重构的过程。帮助更多的人理解系统。
5.是一个传递知识的手段,可以让其它并不熟悉代码的人知道作者的意图和想法,从而可以在以后轻松维护代码。
6.鼓励程序员们相互学习对方的长处和优点。
7.可以被用来确认自己的设计和实现是一个清楚和简单的。
如何做Code Review?
Code Review检查什么?
1.结构问题
代码最大的问题,不是一两个地方有技术缺陷,也不是业务逻辑错误,而是dnf金币查询源码整个软件设计的不好。前两者更容易通过测试或使用来发现和更正,但后者就不同了。如果回想一下自己见过的各种烂摊子,是不是有同感?具体哪里有问题怎么改说不上来,就是整个软件看上去混乱无章,无从下手。
具体结构问题包括:重复拷贝代码(不封装函数,不用Template/泛型……),函数过长(超过一屏幕就叫过长),错误封装(不恰当的public/不用Interface/不内聚/强耦合/在类中封装了无关方法……),内容错误(多个无关类置于一个文件/不恰当的命名……)等等。
改正结构问题,是从编写可靠软件向编写精美软件迈进的重要方法。
2.业务逻辑问题
就是软件是否与需求的要求符合的问题。审核者和被审核者经常对业务需求的理解有差异,借此机会同步一下,必要时引入PO(产品经理/产品负责人)。
有人会说业务逻辑问题不是一测试就知道了吗?可是测试一般发生在很久以后,有些逻辑测试还需要一定的触发条件,而且测试只会发现失效(failure, 与预期不符)而不能发现缺陷(defect, 具体哪里出了错),等积累长了,谁也找不到原因了。
3.编程素养问题
很多问题属于那种“这样也行那样也行”的状态,比如命名/初始值/缩进/断行……但是众测平台源码高手的做法总是比新手好一些。
比如bool result = true; 这句话就有问题,刚初始化就先宣布成功,必有隐患。这是一个真实案例,而下面也的确有一个分支错误地返回了这个true(实际案例是个HRESULT)。而发现这个问题,不是测试而是代码检查。实际上测试几乎发现不了这些问题,比如上面那段代码会在某文件打不开的时候错误地返回这个true,而在测试中几乎不会故事破坏那个文件来测试其结果。
经常进行Code Review
常见的Code Review是高手审核新手,或者师傅走查徒弟。一般而言,大致高手每天能编写多行有效代码(按分号计数),新手会多一些但也不超过(他们编写代码比较费),也就是个屏幕以内。有经验的人一定知道:高手看新手的代码,5秒钟就能发现问题。所以不用花上很长时间去做Code Review,而应该“少吃多餐”,每次可以5分钟,分钟,每天2-3次甚至更多。看到一个问题就要彻底解决,燕窝溯源码真不需要一次检查很多,问题一次比一次少即可。
但是切记不可积累,隔很长时间才去做Code Review,你就会面临那近万行的代码,以前N多掺和在一起的功能,你会发现,整个Code Review变得非常地艰难,用不了一会儿,你就会发现你会疲惫地打着哈欠,但还是要坚持,有时候,这样的Review会持续N个小时以上,相当的夸张。而且会出现相当多的问题和争论,因为,这就好像,人家都把整个房子盖好了,大家Review时这挑一点那挑一点,有时候触动地基或是承重墙体,需要大动手术,让人返工,这当然会让盖房的人一下就跳起来极力地维护自己的代码,最后还伤了他人的感情。
我们怎么做 Code Review
我带过的项目中,做Code Review这方面大多感觉比较凌乱,也没有什么统一的做法。不过从形式上来看大体可以分为两大类:一类是TM技术经理对项目中成员Team一个一个的做Code Review,或者是团队资深人员来做(姑且就叫个人式吧)。一类是做Code Review Meeting,以会议形式来做Code Review(姑且叫会议式)。
1.个人式
对于个人式,其实在上面“如何做Code Review”的话题中已经谈到了很多了。包括我们要及时的不定期的每时每刻的去做Code Review,包括我们要按照结构问题,业务逻辑问题,编程素养问题逐一去检查Code等等。很多项目我们也都做了,甚至是都做到了。只是还有不够好的地方,需要深入的地方。具体的方法上面已经讲了,后面我会具体讲讲如何量化和跟踪。而对于PM来说,如何监控Code Review这件事就显得非常重要。
2.会议式
会议式,真正的会议式去做代码评审,如果做到位了效果应该是最好的,最理想的情况是一堆专家(包括技术专家甚至还有业务专家、测试专家等),拿着代码一行一行的去Review。但是这种做法的成本也非常之高,不管是时间成本也好,还是费用成本都相当的昂贵,一般只有在大型尖端项目才会使用,比如航天航空的项目,做Code Review之后的缺陷率是相当的低的。我们是怎么做Code Review Meeting的呢?首先我们会在开会之前,选出典型的案例或者问题一起拿到会上去讨论,多半是分享一些经验和强调一些容易犯错的地方。一般一次会议不会超过2个小时,每周一次会议即可。这样会议的效果比较好,成本也相对较低。因为由于Team中成员的“素质”参差不齐,所以一起去做代码评审确实效果很差。
我对 Code Review 的一点思考
作为PM我,对Code Review的思考是,我应该如何管理好Code Review?也就是说假设我把Code Review当做一个项目来看,怎样做好这个项目呢?
其实很简单,首先我要有一个正确的、真实的、可执行的计划,然后能在实施Code Review时给予TM或评审人一定的指导,再然后跟踪偏差,分析原因,变更计划。
那麼如何做计划?而且要是正确的、真实的、可执行的。这里我们需要结合一下Project Quality Plan了。可能有的童鞋还不知道,我简单解释一下Project Quality Plan,Project Quality Plan是一个项目质量计划,主要内容有项目交付物以及交付要求,计划达到怎么样的质量目标,要采取怎么样的过程方法,Quality Breakdown各个阶段的质量目标分解等等。通过详细的质量目标分解我们就可以预测各个阶段预计产生的缺陷数是多少。此时我们PM就要思考,有了各个阶段的缺陷数量,我们是不是可以分解一下,那么我们做Code Review的目标是要发现多少缺陷呢?举个例子:假设我们代码的规模是k行,我们目前团队产生缺陷数的基线大概是~ (Bugs/Kloc),Code Review需要找出8~ (Bugs/Kloc),也就是*8~=~。这样一来我们总数就有了,也就是说对于k代码行这种规模的项目我们Code Review总共要找到~个缺陷才算达到了比较好的效果。当然如果做到这里还远远不够,我们还要对这个目标进行细化的分解。要分解到模块,分解到人(如果多人Review的话)。分解到模块很好理解,我们把整个系统分解为几个大的模块,或者模块集(相关性大的可以放一起)。然后分析模块的难易度,以及模块将来可能的负责人,然后评估每类模块我们应该找到多少缺陷。可能对于业务复杂或者算法复杂或者负责人水平较低的模块我们需要更多的时间去Review并产出更多的缺陷,反之则少。如下图: 模块
规模
复杂度
PIC
缺陷分布
(计算)
(调整系数)
1
k
高
中
~
*
1.2
2
k
中
中
*9
1
3
k
中
中
*9
1
4
k
中
弱
~
*9
1.1
5
k
低
弱
*6
1
有了具体的计划Code Review的时候也就有了指导和参考目标。在执行的时候我们也就可以规划出人合理的力投入分配。做起来相对来说就比较容易了。
最后就是跟踪、偏差分析与变更了,当发现我们与实际计划又严重偏差我们要分析原因,然后做计划变更。比如发现偏差时,我们可以用根因分析,人、机、料、法、环、测。我们哪里做的不够好,如果可以解决,找出主要原因立刻解决即可。如果发现是计划有问题就去变更计划好了。这里就不讨论具体方法了。方法有很多,只要适合自己的项目即可。
其实Code Review的方法还有很多,比如结对编程也是一种很好的形式,特别适合敏捷XP团队,但是因为目前我也没有很好的实践,所以也就没有写到。