每个系列一本前端好书,帮你轻松学重点。
本系列来自Zoom前端架构师,前百度、滴滴资深技术专家黄轶所编写的 《Vue.js技术内幕》
你肯定无数次想学源码,又没有勇气把代码下载下来,或者下载下来了,不知从哪学起。
从现在开始,这件事交由“说书匠”来办,读完本系列,你将轻松理解Vue3.0源码的精髓。
为什么学源码
很多人的答案是:应付面试。
因为在面试题中出现“XX是怎么实现”之前,大家只需要会用就可以。
这的确是其中一个诱因,但不是我们要达到的全部目的。既然都做这么“难”的事了,必须有更多收获才行。
大抵包括几点:
大项目组织:每个框架都是一个相当复杂的项目,它需要考虑的东西很多,要功能丰富、好用、避免bug、好维护,它是怎么做的。
开源:每个流行框架都必然是优秀的开源项目,编程行业十分推崇开源,很多公司也会将应聘者是否参与过好的开源项目作为加分项,它是什么做的。
实现:同样是JavaScript,它和写原生有什么不同?是怎么做到数据驱动渲染,又是怎么做的性能优化?我们经常提设计模式和算法,在框架源码里是否有体现?
带着这些想法去读,比纯记忆知识点在面试中去背,要好得多。
举个实际的例子:当我们看到代码报错时,如果是自己写的,很容易排查,一旦涉及框架内部报错,往往束手无策,一句“不是我们的代码,控制不了”交差。对源码有一定了解后,这样的囧况就会减少。
学习的拦路虎
笔者曾有个同行朋友说,学源码不要看别人的,要自己看。
这句话有一定道理,自己捋顺,想明白,才是真正懂了。
但是有个关键问题,大家想想自己平时看别人开发的项目是什么感觉?目录看不懂,组件结构和关系不明确,变量和方法定义不知所以,是不是一心想着能有两样东西辅助就更好,一个是文档,一个是讲解。
与其因为一头雾水摸不着方向而吓退,不妨借助别人好的解读,先把冰破了,门槛入了,再慢慢加深理解,这也更符合学习的规律。
Vue3.x vs Vue2.x
本书重点是3.x源码解读,预设的背景是大家都用过2.x,所以先介绍一下从3.x到2.x发生了哪些主要变化,这也是常见面试题之一。
monorepo
monorepo是一种软件开发模式,叫”单一仓库“,这种模式允许不同的项目在同一个仓库中独立开发,但共享相同的版本控制系统和构建工具。
在Vue2.x中,将代码根据功能模块进行了划分,像complier(编译)、core(运行时)、platforms(平台相关)等,但它是所有的代码都在src目录下,本质上还是一个项目。
在Vue3.x中,则是拆分了不同的packages。
这样以来,模块拆分粒度更细,职责划分更明确,开发人员也更容易阅读和理解,提高了维护性。
另外,它允许一些功能包,如reactivity(响应式库),可以独立于Vue.js使用,这也是最初大家尝鲜时使用最多的包。
TypeScript
Vue.js 1.x的时候还没有用类型语言,当时也还不流行,到了Vue.js 2.x,作者选择了Flow,Flow是Facebook出品的静态类型检查工具,能以低成本迁入现有项目,但缺点是对于复杂场景的类型检查支持并不好。
到了Vue.js 3.x,整个前端行业与几年前已不能同日而语,TypeScript在全世界流行开来,它提供了更好的类型检查,能支持复杂的类型推导,而且有着强大又活跃的生态,所以,Vue.js 3.x很自然地用TypeScript进行重构。
体积优化
我们日常项目常常会进行性能优化,框架也不例外,因为包体积越小,意味着网络传输速度越快,引擎解析速度也越快。
Vue3.x移除了一些冷门功能,比如:filter、inline-template。
同时,引入了被大家频繁讨论的tree-shaking。
tree-shaking是什么意思,简单理解,就是没有用到的代码,不会被打包到最终的项目里。
这一点,不论是自定义的方法还是官方组件都一样,如果项目中没有导入Transition、KeepAlive等组件,对应的代码就不会被打包。有了这个机制,我们发到线上的无用代码就会少很多,从而达到减小体积的目的。
数据劫持优化
Vue.js区别于React.js的一大特色是数据响应,也是选择Vue.js的人最喜欢的特性。
DOM是数据的映射,数据发生变化可以自动更新DOM, 开发者只需要关心数据修改,无需操作DOM,降低了心智负担。
那么,Vue.js是如何实现这一点的?
在Vue.js2.x之前,是通过Object.defineProperty API去劫持数据的getter和setter,核心代码如下:
Object.defineProperty(data,'a',{
get(){
// track
},
set(){
// trigger
}
})
相信大家在学习JavaScript的时候对它都有了解,但因为偏底层,日常几乎不会用到,直到学习Vue,才更多地关注。
不过这个API有一个缺陷,就是必须知道要拦截的key是什么,因此并不能监测到属性的添加和删除,尽管Vue.js提供了额外的方法弥补,对使用来说依然增加了成本。
除此之外,还有另一个问题,就是当数据嵌套层级过多时,如果要劫持深层对象变化,就要遍历整个对象,无疑大大增加了复杂度,成了性能负担。
Vue.js 3.x为解决上述问题,使用了Proxy API。核心代码如下:
observed = new Proxy(data,{
get(){
// track
},
set(){
// trigger
}
})
一个明显区别就是无需再传入key,它劫持的是整个对象的行为,自然就包括属性的增加和删除。
对深层属性,只有真正访问到的对象才会变成响应式,而不是“无脑”递归。
编译优化
Vue.js 2.x中,数据更新的粒度是组件级,虽然能保证更新的组件最小化,但组件内部依然要遍历整个vnode树,这样做的缺点,就是组件内的元素和内容并不都是动态变化的,在进行前后状态对比时,有些对比是浪费掉的。
理想情况是,只关注和处理动态的部分,Vue.js 3.x做到了。
它通过编译阶段对静态模板分析,生成了Block Tree,Block Tree是将模板基于动态节点切割的区块,每个区块内部是固定的,借助于此,将代码更新的性能从与整体大小相关提升到了与动态内容数量相关,是非常大的突破。
API优化
编写Vue.js 2.x时,使用的是Options API,写组件就像在设置一个个选项,每个选项按照data、methods、computed这样分类,好处是非常符合直觉,容易上手。
但在大型组件中,一个组件往往有多个逻辑关注点,每个关注点都有自己的Options,修改的时候,就要在多个Options中切换和寻找,比较费神。
Vue.js 3.x为了改善这个问题进行了较大改动,提供了新的Composition API,它将某个逻辑关注点相关的代码都放在一个函数里,这样在开发和修改时就不需要再跳来跳去。
差异见下图:
除了书写方面优势,它还有更好的类型支持,因为都是函数,在函数调用时,所用类型就被推导出来了,同时对tree-shaking更加友好。
小结
今天这道开胃菜,是不是消化起来没那么难?
相信你对后面的细节探索更加期待了,后续的文章,我会结合书籍,逐步深入地对大家关心的话题做一一解读,要达到的目的,就是”不允许有人读完这个系列还不懂Vue“,flag自此立下,能不能做到,就要我们一起加油啦!
欢迎关注公众号:前端说书匠。好文第一时间接收不迷路!