一、vue2和vue3的概述
vue3引入了一些新特性:composition api,更好的支持ts,更高效的虚拟dom,和 tree-shaking。这些特性使得vue3在代码组织、性能优化和开发效率方面都有了显著的提升。
vue2和vue3在前端开发中都有着重要的地位。vue2作为成熟的框架,为许多项目提供了可靠的解决方案。而vue3则代表着前端技术的发展方向,为开发者带来了更多的可能性和创新空间。通过对比vue3和vue2,我们可以更好地理解前端框架和发展趋势,为项目选择合适的技术栈。
二、vue3的优势
(一)性能提升
vue3通过多种方式提升了性能。在优化Virtual DOM和模版编译方面,vue3引入了静态提升和预字符串化等技术。静态提升使得不参与更新的元素只会被创建一次,在渲染时直接复用,从而优化了运行时的内存占用。预字符串化则是当编译器遇到大量连续的静态内容时,直接将其编译为一个普通字符串节点,避免了创建虚拟dom的开销。此外,vue3使用proxy代理对象来实现响应式系统。相比vue2中的Object.defineProperty,proxy可以对整个对象进行监听,不需要深度遍历,因此性能更高。他可以监听动态属性的添加,数组的索引和长度变化以及属性的删除,使得数据变得更加高效和可预测。
(二)响应式系统改进
vue3的响应式系统使用proxy更加高效灵活。proxy可以直接对整个对象进行监听,而不是像vue2那样需要深度遍历每个属性添加getter和setter。这使得vue3的响应式系统更加高效, 能够更快的响应数据变化。同时,数据变更更加可预测和透明,开发者可以更容易地理解和追踪数据的变化过程。例如,当一个属性被修改时,proxy可以准确的捕获到这个变化,并通知相关的依赖进行更新,从而实现响应式的更新。
(三)更简单的组件开发方式
Composition API 让组件代码更简洁可复用,开发更加灵活自由。Composition API 支持将逻辑代码以函数或一组相关联的函数形式封装为可复用的组合式函数,可以更方便地进行逻辑复用和组合。在使用Composition API 时,代码结构更加直观明了,开发者可以更加优雅的组织代码,将相关功能的代码按照功能点进行打包,打出来的包就是一个hook函数。修改时,只要在hook函数中修改此功能即可,方便得多。
(四)更好的typescript支持
vue3对typescript的支持更严格完整,有更好的类型检查和提示。vue3在核心库和官方插件中提供了完整的typescript类型声明,这意味着开发者可以在开发过程中获得更好的代码提示和类型检查支持,减少错误调试时间。组件选项中引入了更强大的类型推导出来,不在需要显式的定义类型。这样可以减少重复代码,提高开发效率。composition API也提供了更好的类型推导,使得代码更加清晰,提高了代码的可读性和可维护性。
(五)更好的可维护性和拓展性
通过组件化和模块化增加代码可维护性和拓展性。在vue3中,组件化的架构更加清晰,每个组件都可以独立开发、测试和维护。同时,模块化的设计使得代码更加易于组织和管理,可以根据功能将相关代码组织在一起,提高了代码的可维护性。此外,vue3提供了更好的扩展性,开发者可以通过自定义指令、插件等方式扩展框架的功能,满足不同项目的需求。
(六)更灵活的自定义渲染
vue3自定义渲染API让开发者更灵活控制组件渲染方式。开发者可以使用自定义渲染API来实现更加复杂的渲染逻辑,例如渲染不同类型的元素、处理特殊的属性或事件等。这使得开发者可以根据项目的需求定制化组件的渲染方式,提高了开发的灵活性和可拓展性。
(七)更好的懒加载支持
vue3默认开启懒加载机制提升页面加载速度和性能。懒加载机制可以在需要的时候才加载组件或模块,避免了一次性加载所有资源导致的页面加载缓慢问题。在vue3中,懒加载可以通过动态导入的方式实现,当组件被首次访问时才会加载响应的模块,从而提高了页面的加载速度和性能。
( 八)更小的体积
vue3经过重构和体积更小,适合现代前端项目。vue3使用了tree-shaking技术,只会打包使用到的代码,从而减小了打包后的体积。此外,vue3还移除了一些不常用的API,进一步减小了体积,相比vue2,vue3的体积更小,加载速度更快,适合现代前端项目的需求。

三、vue2的特点
(一)源码目录结构
vue2的源码目录结构清晰,各部分分工明确。其中
- compiler目录包含vue.js所有编译相关的代码,包括吧模版解析成ast语法树、ast语法树优化、代码生成等功能。
- core目录是核心部分,包含内置组件、全局API封装、vue实例化、观察者、虚拟dom、工具函数等
- platform目录有两个子目录,分别对应web和weex,代表了vue可以编译出在浏览器运行的框架,也可以配合weex在其他平台下运行
- serve目录包含所有跟服务端渲染的相关代码,vue2支持服务端渲染,将组件渲染为服务器端的html字符串,发送到浏览器会混合为完全交互的应用程序。
- sfc目录可以把.vue文件内容解析成一个javascript的对象
- shared目录定义了一些工具方法,被其他目录共享
(二)编译时与运行时
在vue2中,编译时分为离线编译和在线编译。离线编译借助webpack、vue-loader等辅助插件在构建时进行,更推荐这种方式,因为编译是一项耗性能的工作。在线编译则是在运行时使用包含构建功能的vue.js。运行时主要负责维持数据与模版的绑定关系,当数据发生变化时,能够及时更新模版,实现页面的动态更新。
(三)核心部分
vue2的core核心部分包含多个重要模块
- .component模块包含vue自带的组件,如keep-alive
- global-api模块负责vue函数属性的初始化,如vue.use()、vue.component()等全局API的封装
- instance模块包含vue对象实例化的逻辑
- 此外,还有observer、util、vdom等模块,分别负责数据响应式、工具函数和虚拟dom的实现
(四)observer核心
在vue2中,observer利用Object.defineProperty实现数据操作的拦截。对于每个数据对象,observer会为其每个属性创建一个Dep实例作为该属性的依赖。当属性的getter被调用时,observer会记录依赖于该属性的watcher,以便在属性变化时通知它们。通过这种方式,实现了对数据的响应式处理。
(五)watcher和dep
在vue2中,watcher是观察者,负责将相应时属性的值与试图蹭绑定。watcher在初始化时会执行一次属性的getter,以收集依赖,并缓存属性的当前值。当依赖的响应式属性发生变化时,watcher会重新计算表达式的值,并将结果更新到试图层。dep时发布者,手机所有依赖于某个响应式属性的watcher。在属性的getter中,dep会记录所有访问该属性的watcher,并将他们存储在一个数组中。当响应式属性发生变化时,dep会遍历存储的watcher列表,并通知他们进行更新。
(六)更新机制
vue2的_update方法在数据更新时起到关键作用。当数据发生变化时,会触发响应式系统,通知相关的watcher进行更新。watcher会重新计算表达式的值,并调用_update方法将近的值更新到视图层。在首次渲染时,也会调用_update方法来初始化试图。此外,patch函数在数据更新和首次渲染中负责比较新旧虚拟dom,得到更新操作。通过这种方式,vue2能够高效的更新视图,实现数据与视图的同步。
四、vue2与vue3的区别
(一)默认懒观察
在vue2中,不管数据大小,都会在一开始就为其创建观察者。当数据量很大时,这可能会在页面载入时造成明显的性能压力。例如,在一个大型电商项目中,如果有大量的商品数据需要加载,vue2会在一开始就为所有数据创建观察者,这可能会导致页面加载缓慢。
而vue3只会对【被用于渲染初始化可见部分的数据】创建观察者,并且3.x的观察者更高效。这样可以大大提高页面的加载速度和性能。比如在一个新闻资讯网站中,初始页面只显示部分热门新闻,vue3只会为这些可见的新闻数据创建观察者,当用户滚动页面加载更多新闻时,才会为新出现的新闻数据创建观察者。
(二)代码组织方式
vue2使用选项类型API,将响应式数据写在data中,操作方法写在methods配置中,各个选项都有固定的书写位置,。这种方式在应用变大后,代码查找起来非常麻烦,尤其是在维护更新之前业务的时候。
vue3采用合成型API,特定功能相关的所有东西都放到一起维护,比如功能A相关的响应式数据、操作数据的方法、watch监听方法、computed计算属性放到一起,形成代码功能上的模块化。这样不管应用多大,都可以快速定位到某个功能的所有相关代码,维护方便,代码逻辑更清晰,可读性更强。
(三)methods编写
在vue2的选项API中,methods被分割到独立的属性区域,直接在这个属性里面添加方法来处理各种前端逻辑。
而在vue3的合成型API里面的setup()方法也可以用来操控methods。创建声明方法和声明数据状态类似,先声明一个方法然后在setup()方法中返回,这样组件内就可以调用这个方法了。
(四)生命周期钩子
在vue2中,我们可以直接在组件属性中调用vue的生命周期钩子。例如,使用mounted生命周期触发狗子在组件挂在完成后执行一些操作。
在vue3中,生命周期钩子不是全局可调用的了,需要从vue中引入。像生命周期的挂载钩子叫onMounted,引入后在setup()方法里面使用。例如,在vue3中的setup()方法中,使用onMounted来替代vue2的mounted钩子。
(五)计算属性
在vue2中实现计算属性,只需要在组件内的computed属性中添加即可。
在vue3使用计算属性,现需要在组件内引入computed。使用方式就和反应性数据一样,在state中加入一个计算属性。vue3的设计模式给予开发者们按需引入需要使用的依赖包,避免了多余的引用导致性能或者打包后的体积过大的问题。
(六)接收props
在vue2中,this代表的是当前组件,所以可以直接使用this访问prop属性值。比如在挂载完成后打印当前传入组件的参数title,可以使用this,title
但是在vue3中,this无法直接拿到props属性、emit events和组件内的其他属性。不过全新的setup()方法可以接受两个参数:props(不可变的组件参数)和context(vue3暴露出来的属性,如emit、slots、attrs)。所以在vue3接收与使用props就会变成在setup(props)中使用props.title
(七)事件发射
在vue2中自定义时间是非常直接的,调用this.emit(‘login’,{username:this.username,password:this.password})。
但是在vue3中。this已经不是和vue2代表着整个组件了,所以在setup()中的第二个参数context对象中就有emit,这个和this.$emit是一样的。在setup(prop,{emit})中,使用分解对象取出emit,然后在setup方法中随意使用。例如,在login方法中编写登陆事件,emit{‘login’,{username:state.username,password:state.password})。
五、vue3源码分析
(一)响应式系统
vue3的响应式系统是其最大的变革之一,他使用了proxy API来代替vue2中的Object.defineProperty()方法。使用proxy API可以更加灵活的监听对象属性的变化,并且支持更多的操作类型。
在vue3中,响应式数据是通过reactive函数创建的。reactive函数接受一个普通对象作为参数,并返回一个响应式对象。这个响应式对象是通过proxy创建的代理对象,他可以拦截对对象属性的访问和修改操作,从而实现响应式更新。
例如,当我们访问响应式对象的属性时,proxy的get拦截器会被触发,从而收集依赖。当我们修改响应式对象的属性时,proxy的set拦截器会被触发,从而通知所有依赖该属性的地方进行更新。
相比vue2的object.defineproperty,proxy具有一下优势:
- 可以监听整个对象,而不仅仅是对象的属性。
- 可以监听数组的索引和长度变化。
- 可以监听属性的删除操作。
(二)组件实例
在vue3中,组件实例被定义为一个“组合对象”,它由多个函数和对象组成。组件实例通过createComponentInstance()函数来创建,该函数会返回一个包含setup()函数、render()函数和其他实例属性的对象。
在组件实例创建后,会调用setup()函数来获取数据和方法。setup()函数是一个特殊的函数,他在组件实例创建之前被调用,可以返回一个对象,这个对象中的属性和方法可以在模版中直接使用。
例如,以下是一个简单的 Vue3 组件实例:
<template>
<div>{
{ message }}</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('Hello, Vue3!');
return { message };
}
};
</script>
在这个例子中,setup () 函数返回了一个包含 message 属性的对象,这个属性可以在模板中直接使用。
(三)渲染器
vue3的渲染器被设计为可插拔的,用户可以自定义渲染器来支持不同平台的场景。渲染器通过createRenderer()函数来创建,该函数会返回一个对象,其中包含了渲染组件所需要的所有方法和属性。
在vue3中,可以使用render()函数来生成虚拟dom,并且可以通过patch()函数将虚拟dom转换为真实的dom元素。渲染器还提供了很多有用的工具函数,例如:createVNode()函数用于创建虚拟DOM节点、normalizeChildren()函数用于规范化子节点等。
例如,以下是一个使用 Vue3 渲染器的简单例子:
import { createRenderer } from 'vue';
const renderer = createRenderer();
const vnode = renderer.createVNode('div', null, 'Hello, Vue3!');
const container = document.getElementById('app');
renderer.render(vnode, container);
在这个例子中,我们使用 createRenderer () 函数创建了一个渲染器,然后使用 renderer.createVNode () 函数创建了一个虚拟 DOM 节点,最后使用 renderer.render () 函数将虚拟 DOM 节点渲染到真实的 DOM 元素中。
(四)编译器
vue3的编译器也是可插拔的,与渲染器一样,用户可以自定义编译器来支持不同的语言或模版格式。编译器通过createCompiler()函数来创建,该函数会返回一个包含compile()函数和其他编译相关方法的对象。
在vue3中,可以使用compile()函数来将模版字符串编译成javascript代码,并且可以使用generate()函数将代码生成可执行的函数。编译器还提供了很多有用的工具函数,例如:parse()函数用于解析模板字符串、optimize()函数用于优化编译后的代码等。
例如,以下是一个使用 Vue3 编译器的简单例子:
import { createCompiler } from 'vue';
const compiler = createCompiler();
const template = '<div>{
{ message }}</div>';
const { code } = compiler.compile(template);
const renderFunction = new Function('Vue', code)(Vue);
const data = { message: 'Hello, Vue3!' };
const vnode = renderFunction.call({ data });
const container = document.getElementById('app');
renderer.render(vnode, container);
在这个例子中,我们使用 createCompiler () 函数创建了一个编译器,然后使用 compiler.compile () 函数将模板字符串编译成 JavaScript 代码,最后使用 new Function () 函数将代码生成为可执行的函数,并将其渲染到真实的 DOM 元素中。
六、总结
vue3与vue2相比,在多个方面展现出了显著的优势。从性能上看,vue3通过静态提升、预字符串化、使用Proxy实现响应式系统等方式,大大提升了性能,减少了内存占用和页面加载时间。在响应式系统方面,vue3的Proxy更加高效灵活,数据变更更加可预测和透明。
Composition API的引入使得组件代码更加简洁可复用,开发更加灵活自由。更好的TypeScript支持提供了更严格完整的类型检查和提示,减少了错误和调试时间。vue3的可维护性和拓展性也得到了极大的提升,通过组件化和模块化,代码更加易于组织和管理,扩展性更强。
自定义渲染API让开发者能够更灵活地控制组件渲染方式,懒加载机制提升了页面加载速度和性能,更小的体积也适合现代前端项目的需求。