整理vue常见基础面试题
1. 谈谈你对MVVM开发模式的理解
MVVM分为Model、View、ViewModel三者。
- Model:代表数据模型,数据和业务逻辑都在Model层中定义;
- View:代表UI视图,负责数据的展示;
- ViewModel:负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
这种模式实现了Model和View的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作dom。
2.Vue常用指令
- v-text:元素的InnerText属性,用于数据绑定
- v-html:元素的InnerHTML属性,用于绑定一段html
- v-bind:给元素的属性赋值,
v-bind
后面是 :属性名=[变量名],简写为:属性名
- v-for:列表的循环渲染。语法
v-for="item in data"
,data 可以是数组或者对象 - v-show、v-if:控制页面元素的显示和隐藏
- v-if、v-else-if、v-else:
v-if
的条件渲染和JavaScript条件判断语句中if、else、else if
非常类似。 - v-on:给元素绑定事件,用法
v-on:事件名="方法"
- v-model:实现数据的双向绑定
- v-pre:跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理
- v-once:模板只会在第一次更新时显示数据,此后再次更新该DOM里面引用的数据时,内容不会自动更新
3.v-if和v-show
v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;
v-if会控制这个 DOM 节点的存在与否。
- 主要区别是是否渲染,当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
- 当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
- 如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。
4.V-model原理
v-model是一个十分强大的指令,它可以自动让原生表单组件的值自动和你选择的值绑定,我们经常用它做双向绑定。view层输入值影响data的属性值,data属性值发生改变会更新view层的数值变化。
- 依赖收集:在组件初始化时,比如render function里引用到了某个响应式对象,就会触发这个对象属性的getter,这时候就会把这个render function放到这个对象的订阅者列表里(track)
- 响应下次去设置这个响应式对象的属性时就会触发对象属性的getter,继而调用对应的订阅者列表(trigger)
1、实现原理浅层理解:触发input事件来修改value值
v-model其实是个语法糖,它实际上是做了两步动作:
- v-bind:绑定响应式数据
- 触发 input 事件 并传递数据 (核心和重点) input>标签有一个oninput事件,该事件类似于onchange事件,不同之处在于 oninput 事件在元素值发生变化是立即触发, onchange 在元素失去焦点时触发。
2、 深层理解:利用Object.defineProperty()数据劫持来实现。
现目前使用的很多框架,最大的优点就是可以实现数据绑定,再也不需要手动进行DOM操作了,它们实现的原理之一就是数据劫持。
3、Object.defineProperty()实现数据劫持
Object.defineProperty(obj,prop,descriptor)的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。但不能监听数组的变化。
在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知。我们完全可以控制对象属性的设置和读取。getter 函数用于在数据读取时进行依赖收集,在对应的 dep 中存储所有的 watcher;setter 则是数据更新后通知所有的 watcher 进行更新。
Object.defineProperty(a, 'b', {
enumerable: false,
configurable: false,
get: function(){
console.log('b' + '被访问');
},
set: function(newVal){
console.log('b' + '被修改,新' + 'b' + '=' + newVal);
}
});
a.b = 2; // b被修改,新b=2
a.b; // b被访问
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set:一旦目标属性被赋值,就会调回此方法。
注意:当使用了getter或setter方法,不允许使用writable和value这两个属性
5.如何优化SPA应用的首屏加载速度慢的问题?
- 将公用的JS库通过script标签外部引入,减小 app.bundel 的大小,让浏览器并行下载资源文件,提高下载速度;
- 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
- 加一个首屏loading图,提升用户体验;
6.vue组件间传值
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,一般来说,组件可以有以下几种关系:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
ref
与$parent / $children
适用 父子组件通信
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
parent/children:访问父 / 子实例- 父子组件传递数据
props/$emit
EventBus ($emit / $on)
适用于 父子、隔代、兄弟组件通信」这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件$emit/$on
通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级- attrs/listeners 适用于 隔代组件通信
attrs
:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
listeners
:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners" 传入内部组件 provide / inject
适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。vuex
适用于 父子、隔代、兄弟组件通信
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
7.vuex
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当然,若应用比较简单,共享状态也比较少,可以用 Vue.observe 去替代 Vuex,省去安装一个库也挺好。
- 当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。
- 当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
属性: State、 Getter、Mutation 、Action、 Module
- state => 基本数据(数据源存放地),定义并初始化全局状态。
- getters => 从基本数据派生出来的数据,依赖 State 中的状态,进行二次包装,不会影响 State 源数据。
- mutations => 更改 State 状态的函数,必须是同步。
- actions => 用于提交 Mutation,可包含任意异步操作。
- modules => 若应用复杂,Store 会集中一个比较大的对象而显得臃肿,Module允许我们将 Store模块化管理。
8.computed和watch
- computed:
- 计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。
- 当一个属性受多个属性影响的时候就需要用到computed
- 适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。
购物车商品结算的时候
- watch:
- 当一条数据影响多条数据的时候就需要用watch
- 观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。
- 适用于数据变化时执行异步或开销比较大的操作。
搜索数据
9.vue组件中data为什么必须是一个函数,key的作用
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
- 因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
- 需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。作用主要是为了高效的更新虚拟DOM。
9.$nextTick的使用
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,
你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。
10.单页面应用(SPA)和多页面应用(MPA)区别及优缺点
- SPA:通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。
- MPA:就是指一个应用中有多个页面,页面跳转时是整页刷新
单页面的优点:用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小;前后端分离;页面效果会比较炫酷(比如切换页面内容时的专场动画)。
单页面缺点:不利于seo;导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理);初次加载时耗时多;页面复杂度提高很多。
11.vue常用修饰符
.stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;
.prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
.capture:与事件冒泡的方向相反,事件捕获由外到内;
.self:只会触发自己范围内的事件,不包含子元素;
.once:只会触发一次。
12.vue两个核心点
数据驱动:ViewModel,保证数据和视图的一致性。
组件系统:应用类UI可以看作全部是由组件树构成的。
13.vue生命周期
- beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
- create:data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
- beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
- mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
- beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
- updated:页面显示的数据和data中的数据已经保持同步了,都是最新的
- beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
- destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。
- 第一次加载接口触发:beforeCreate, created, beforeMount, mounted
- created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
- mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
- 获取数据:一般 created/beforeMount/mounted 皆可.比如如果你要操作 DOM , 那肯定 mounted 时候才能操作.
14.vue-router
1. vue用来写路由一个插件。组件:router-link、router-view
2. vue-router模块的router-link组件。children数组来定义子路由
3. 定义动态路由:在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id。
4. 导航钩子:
- 全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
- 组件内的钩子
- 单独路由独享组件
5. 导航守卫
- 全局前置守卫:在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑。
const router = new VueRouter({})
router.beforeEach((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
- 全局解析守卫:与 beforeEach 类似,也是路由跳转前触发,区别是还需在
所有组件内守卫和异步路由组件被解析之后
,也就是在组件内 beforeRouteEnter 之后被调用。
const router = new VueRouter({})
router.beforeResolve((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
- 全局后置钩子:和守卫不同的是,这些钩子不会接受
next
函数也不会改变导航本身。
router.afterEach((to, from) => {
// ...
})
- 路由独享守卫:可在路由配置上直接定义 beforeEnter
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => {
}
}
]
})
- 组件内的守卫:组件内可直接定义如下路由导航守卫
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 this
// 当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变,但是组件被复用时调用
// 可访问实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开组件时被调用
}
}
6. 路由传参
- 路由配置
// 路由配置
{
path: '/detail/:id',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ path: '/detail/${id}'})
// 获取参数
this.$route.params.id
- URL 虽然不显示我们的传参,但是是可以在子组件获取参数的。当然也有问题:会存在刷新丢失参数。若想不丢失,需和方案一路由配置一样。原因是第二种方式传参是上一个页面 push 函数中携带的,刷新没有 push 的动作。
// 路由配置
{
path: '/detail',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ name: 'Detail', params: { id: id } })
// 获取参数
this.$route.params.id
- 路由配置
// 路由配置
{
path: '/detail',
name: 'Detail',
component: () => import('./Detail.vue')
}
// 路由跳转
let id = 1
this.$router.push({ name: 'Detail', query: { id: id } })
// 获取参数
this.$route.query.id
15. hash
、history
和abstract
-
hash
:监听的hashchange事件,即地址栏 URL 中的 # 符号;hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。兼容性好,支持低版本浏览器和 IE 浏览器。 -
history
:history模式监听pushstate和popstate,(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。 -
abstract
:支持所有 JS 运行模式,Vue-Router 自身会对环境做校验,如果发现没有浏览器 API,路由会自动强制进入 abstract 模式。在移动端原生环境也是使用 abstract 模式。
使用场景:
- 一般场景下,hash 和 history 都可以,除非你更在意颜值,# 符号夹杂在 URL 里看起来确实有些不太美丽。
history优势:
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
- pushState() 可额外设置 title 属性供后续使用。
history劣势:
- 跳转后刷新或者回跳,会报一个404的错误,找不到指定的路由,最后后端去指向正确的路由 加了/hd/xxx 去匹配是否有这个/hd/{:path} 才得以解决
15.Vue nextTick实现原理
nextTick:在下次 DOM 更新循环结束之后执行延迟回调。常用于修改数据后获取更新后的DOM。
源码位置:vue/src/core/util/next-tick.js
按照浏览器是否支持promise->mutation-observe->setImmediate->setTimeout的顺序来实现,表现出来的效果跟setTimeout大致一样的
- 运用异步锁的概念,保证同一时刻任务队列中只有一个 flushCallbacks。当 pengding 为 false 的时候,表示浏览器任务队列中没有 flushCallbacks 函数;当 pengding 为 true 的时候,表示浏览器任务队列中已经放入 flushCallbacks;待执行 flushCallback 函数时,pengding 会被再次置为 false,表示下一个 flushCallbacks 可进入任务队列。
- 环境能力检测,选择可选中效率最高的(宏任务/微任务)进行包装执行,保证是在同步代码都执行完成后再去执行修改 DOM 等操作。
- flushCallbacks 先拷贝再清空,为了防止nextTick嵌套nextTick导致循环不结束。
16.vue源码核心
- 模板编译compiler
- 虚拟dom及diff
- 虚拟dom转真实dom
- 基于依赖收集的响应式原理
17.vue的Diff算法
核心源码:vue/src/core/vdom/patch.js
为什么要diff、同层对比、深度优先、两端对比
-
首先需要diff的原因是操作dom的代价是昂贵的,我们要减少操作dom所以需要对比出新老虚拟dom的最小差异点,尽可能复用原有dom
-
因为我们很少跨层级的去移动dom,所以diff算法只会对比同一父节点下的子节点,使算法的时间复杂度是O(n)
-
深度优先,比较新vnode找到对应的老vnode,这时会先执行patchChildren进入子节点的比较,所以是深度优先
-
首尾两端对比法,依次把旧vnode数组的首尾分别和新vnode数组的首尾进行对比,能匹配上就进入他们子节点的对比,都匹配不上就通过key索引找新vnode数组第一个节点对应的在旧vnode数组里的index,还是找不到的话就新建一个vnode。当旧vnode数组里还有节点,而新vnode数组已经遍历完了时就remove掉对应的多出来的真实dom
18.slot插槽
slot 插槽,可以理解为slot
在组件模板中提前占据了位置。当复用组件时,使用相关的slot标签时,标签里的内容就会自动替换组件模板中对应slot标签的位置,作为承载分发内容的出口。
主要作用是复用和扩展组件,做一些定制化组件的处理。
- 默认插槽
// 子组件
<template>
<slot>
<div>默认插槽备选内容</div>
</slot>
</template>
// 父组件
<template>
<Child>
<div>替换默认插槽内容</div>
</Child>
</template>
- 具名插槽:slot 标签没有
name
属性,则为默认插槽。具备name
属性,则为具名插槽
// 子组件
<template>
<slot>默认插槽的位置</slot>
<slot name="content">插槽content内容</slot>
</template>
// 父组件
<template>
<Child>
<template v-slot:default>
默认...
</template>
<template v-slot:content>
内容...
</template>
</Child>
</template>
- 作用域插槽:子组件在作用域上绑定的属性来将组件的信息传给父组件使用,这些属性会被挂在父组件接受的对象上
// 子组件
<template>
<slot name="footer" childProps="子组件">
作用域插槽内容
</slot>
</template>
// 父组件
<template>
<Child v-slot="slotProps">
{{ slotProps.childProps }}
</Child>
</template>
19. Vue.directive
Vue.directive 可以注册全局指令和局部指令。
指令定义函数提供如下钩子函数
- bind:指令第一次绑定到元素时调用(只调用一次)
- inserted: 被绑定元素插入父节点时使用(父节点存在即可调用)
- update:被绑定元素所在模板更新时调用,不论绑定值是否变化。通过比较更新前后的绑定值。
- componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
- unbind: 只调用一次,指令与元素解绑时调用。
项目中有涉及 一键copy、权限控制 都可以用指令的方式控制,目的就是简化我们的工作量。
20.Vue 过滤器
Vue 过滤器可用在两个地方:双花括号插值和 v-bind 表达式。
Vue3 中已经废弃这个特点。
- 局部过滤器
<div>{{ message | formatMessage }}</div>
</template>
<script>
export default {
filters: {
formatMessage: function(value) {
// 可基于源值做一些处理
return value
}
}
}
</script>
- 全局过滤器
// 可基于源值做一些处理
return value
})
过滤器可串联,执行顺序从左到右,第二个过滤器输入值是第一个过滤器的输出值。
<div>{{ message | formatMessage1 | formatMessage2 }}</div>
21.proxy与Object.defineProperty 优劣对比
「Proxy 的优势如下:」
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是
- Object.defineProperty 不具备的;
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
- Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
「Object.defineProperty 的优势如下:」
- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue
的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
22.自定义组件
- 在components目录新建你的组件文件(smithButton.vue),script一定要
export default {
- 在需要用的页面(组件)中导入:
import smithButton from ‘../components/smithButton.vue’
- 注入到vue的子组件的components属性上面,
components:{smithButton}
- 在
template
视图view中使用,<smith-button> </smith-button>
smithButton
命名,使用的时候则smith-button
。
23.Vue.js的template编译的理解
先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)
-
首先,通过
compile
编译器把templat
e编译成AST
语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile
是createCompiler
的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。 -
然后,
AST
会经过generate
(将AST语法树转化成render funtion字符串的过程)得到render
函数,render的返回值是VNode
,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)
24.vue项目优化
- 代码层面优化
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化无限列表性能
- 服务端渲染 SSR or 预渲染
- webpack层面优化
- Webpack 对图片进行压缩
- 减少 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建结果输出分析
- Vue 项目的编译优化
3.基础web技术优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶颈
25.vue3.0新特性
- 检测机制的改变
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:- 只能监测属性,不能监测对象
- 检测属性的添加和删除;
- 检测数组索引和长度的变更;
- 支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下特性:
- 用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
- 默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
- 更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
- 不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
- 更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染
- 模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。 - 对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
4.其它方面的更改
vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:
- 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
- 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
- 基于 treeshaking 优化,提供了更多的内置功能。
26.源码结构
-- config // 项目开发环境配置
| |-- index.js // 项目打包部署配置
|-- src // 源码目录
| |-- components // 公共组件
| |-- header.vue // 页面头部公共组件
| |-- footer.vue // 页尾头部公共组件
| |-- index.js // 加载各种公共组件
| |-- config // 路由配置和程序的基本信息配置
| |-- routes.js // 配置页面路由
| |-- css // 各种css文件
| |-- common.css // 全局通用css文件
| |-- iconfont // 各种字体图标
| |-- images // 公共图片
| |-- less // 各种less文件
| |-- common.less // 全局通用less文件
| |-- pages // 页面组件
| |-- home // 个人中心
| |-- index // 网站首页
| |-- login // 登录
| |-- signout // 退出
| |-- store // vuex的状态管理
| |-- index.js // 加载各种store模块
| |-- user.js // 用户store
| |-- template // 各种html文件
| |-- index.html // 程序入口html文件
| |-- util // 公共的js方法,vue的mixin混合
| |-- app.vue // 页面入口文件
| |-- main.js // 程序入口文件,加载各种公共组件
|-- .babelrc // ES6语法编译配置
|-- gulpfile.js // 启动,打包,部署,自动化构建
|-- webpack.config.js // 程序打包配置
|-- server.js // 代理服务器配置
|-- README.md // 项目说明
|-- package.json // 配置项目相关信息,通过执行 npm init 命令创建
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!