vue渐进式框架的理解
Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念
声明式渲染 render)-> 组件系统 component)-> 客户端路由vue-router )-> 大规模状态管理vuex )-> 构建工具webpack
vue.js的两个核心概念是什么?
1.数据驱动,也叫双向数据绑定。
2.组件系统。 组件的核心选项: 模板(template)/初始数据(data)/接受的外部参数(props)
/方法(methods)/生命周期钩子函数(lifecycle hooks)/私有资源(assets)
Vue响应式原理
Vue数据双向绑定是通过采用数据劫持结合发布者-订阅者模式的方式来实现的。
通过Object.defineProperty()来劫持各个属性的setter,getter。修改触发set方法赋值,获取触发get方法取值,在数据变动时发布消息给订阅者,触发相应的回调并通过数据劫持发布信息。
用一句话来概括Vue的响应式原理。
当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。
细说 vue 的双向绑定原理
第一步 “数据劫持”:
vue 2.x 用 Object.defineProperty() 方法来实现数据劫持,为每个属性分配一个订阅者集合的管理数组 dep;vue 3.x 用 ES6 的 Proxy 构造函数来实现数据劫持。
第二步 “添加订阅者”:
在编译的时候在该属性的数组 dep 中添加订阅者,添加方式包括:v-model 会添加一个订阅者,{{}} 也会,v-bind 也会,只要用到该属性的指令理论上都会。
第三步 “为 input 添加监听事件”:
为 input 添加监听事件,修改值就会为该属性赋值,触发该属性的 set() 方法,在 set() 方法内通知订阅者数组 dep,订阅者数组循环调用各订阅者的 update() 方法更新视图。
v-model语法糖是怎么实现的
<!-- text和textarea 元素使用value property 和 input事件 -->
<!-- checkbox 和radio使用checked property 和 change事件-->
<!-- select 字段将value 作为prop 并将change 作为事件 -->
vue全局api
注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称
Vue.component( id, [definition] )
import MyComponent from '../../'
Vue.component('my-component', MyComponent)
注册或获取全局过滤器。
Vue.filter('my-filter', function (value) {})
局部过滤
filters:{filterA:function(value){}}
注册或获取全局指令。
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
})
局部指令
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
Vue的生命周期
生命周期 | 是否获取dom节点 | 是否可以获取data | 是否获取methods |
---|---|---|---|
beforeCreate | 否 | 否 | 是 |
created | 否 | 是 | 是 |
beforeMount | 否 | 是 | 是 |
mounted | 是 | 是 | 是 |
vue3生命周期
onMounted()
onUpdated()
onUnmounted()
onBeforeMount()
onBeforeUpdate()
onBeforeUnmount()
onActivated()
onDeactivated()
keep-alive的生命周期
keep-alive 作用:在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性.
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
activated 被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件失活时调用。
初次进入时:created > mounted > activated;退出后触发 deactivated
再次进入:会触发 activated;事件挂载的方法等,只执行一次的放在 mounted 中;
组件每次进去执行的方法放在 activated 中
Vue父子组件生命周期执行顺序
加载渲染过程:父beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子组件更新过程:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父组件更新过程:父beforeUpdate -> 父updated
销毁过程:父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
Vue 组件 data 为什么必须是函数
每个组件都是 Vue 的实例。
组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他。
v-for / key值作用 / key为什么不要用 index
v-for的四种使用方法分别是:
1.使用v-for循环一个简单的数组
2.使用v-for循环一个复杂的数组
3.使用v-for循环对象
4.v-for循环一个迭代的数字 v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。
如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,
而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。
如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
key为什么不要用 index
1.性能消耗
使用 index 做 key,破坏顺序操作的时候, 因为每一个节点都找不到对应的 key,导致部分节点不能复用,所有的新 vnode 都需要重新创建。
如果不存在对数据逆序添加,逆序删除等破坏顺序的操作时,仅用于渲染展示用时,使用 index 作为 key 也是可以的
2.数据错位
v-if 与 v-show 的差别
1.v-if通过控制dom的节点的存在与否来控制元素的显隐;v-show通过设置dom元素的display样式,block显示,none隐藏。
2.v-if切换有一个局部编译/卸载的过程,切换过程中销毁和重建内部的事件监听和子组件;v-show 基于css切换。
3.v-if是惰性的,如果初始条件为false,则什么也不做;只有条件第一次变为true时才开始局部编译;v-show任何条件下都编译,然后被缓存,而且dom元素保留;
4.v-if 切换消耗高,v-show 初次渲染消耗高。
computed 与 watch 的差别
1.支持缓存,只有依赖数据发生改变,才会重新进行计算。
2.不支持异步,当computed内部有异步操作时无线,无法监听数据的变化。
3.computed 属性值会默认走缓存,计算属性是基于它们的相应式依赖进行缓存。
4.如果computed属性属性值是函数,默认走get方法;函数的返回值就是属性的属性值。
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
}
fullName: function () {
return this.firstName + ' ' + this.lastName
}
fullName: {
get: function () {},
set: function (newValue) {}
}
watch
1.不支持缓存,数据发生改变,直接触发相应的操作。
2.支持异步。
3.监听的函数接收两个参数,第一个参数为最新的值,第二个参数为之前的值。
4.监听的数据必须是data中声明过,或者父组件传递过来的props中的数据。
5.监听对象时,使用handlder() 函数处理,
6.immediate: true 将立即以表达式的当前值触发回调
7.deep: true 为了发现对象内部值的变化,可以在选项参数中指定
watch:{ //监听普通变量时:
name(newVal,oldVal){},
name:function(newVal,oldVal){},
obj:{ //深度监听,可监听到对象、数组的变化
handlder(newVal,oldVal){},
immediate: true,
deep: true
}
}
需要在数据变化时执行异步或开销较大的操作时使用watch。
computed 与 函数
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。在for循环中,computed的属性执行一次,函数执行多次。
- 计算属性有缓存,有且仅有计算属性内部的属性值发生变化时才会被调用,性能上会有很大的提升;函数没有缓存,每次执行都会被调用;
- 计算属性默认只有get函数,没有set,只支持单向,若想使用双向可进行手动添加;函数只有单向;
- 调用时计算属性是一种属性,函数是一种方法。
- 应用场景:需要进行数值计算,并且依赖于其他数据时,应该使用computed
filters
当全局过滤器和局部过滤器重名时,会采用局部过滤器。
过滤器可以串联:{{ message | filterA | filterB }}
过滤器是 JavaScript 函数,因此可以接收参数:{{ message | filterA('arg1', arg2) }}
filters:{ filterA:function(value){} }
组件之间通讯
- props/$emit :父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。
- $children/$parent :
- provide/inject:简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。
- refs:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据。
怎样理解 Vue 的单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
nextTick
Vue 在更新 DOM 时是异步执行的。
只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
$nextTick() 返回一个 Promise 对象
延迟调用优先级如下:
Promise > MutationObserver > setImmediate > setTimeout
MutationObserver
先简单介绍下MutationObserver:MO是HTML5中的API,是一个用于监视DOM变更的接口,
它能够监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等。
数组调用过程是要先给它绑定回调,获得MO实例,这个回调会在MO实例监听到变更时触发。
这里MO的回调是放在microtask中执行的。
触发时机:在同一事件循环中的数据变化后,DOM完成更新,立即执行Vue.nextTick()的回调。
同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发
虚拟DOM / diff算法
Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点
虚拟DOM在Vue.js中主要做了两件事:
1.提供与真实DOM节点所对应的虚拟节点vnode
2.将虚拟节点vnode和旧虚拟节点oldVnode进行比对,然后更新视图
diff 算法包括几个步骤:
1.用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
2.当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
3.把所记录的差异应用到所构建的真正的DOM树上,视图就更新了
检测变化的注意事项
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的
Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property
Vue 不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
Vue是如何监听数组变化的
在将数组处理成响应式数据后,如果使用数组原始方法改变数组时,数组值会发生变化,但是并不会触发数组的setter来通知所有依赖该数组的地方进行更新,为此,vue通过重写数组的某些方法来监听数组变化,重写后的方法中会手动触发通知该数组的所有依赖进行更新。
Vue重新的属性:
push / pop / shift / unshift / splice / sort / reverse
Vue怎么实现对象和数组的监听?
Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化。
Vue 框架是通过遍历数组和递归遍历对象,从而达到利用 Object.defineProperty()
也能对对象和数组(部分方法的操作)进行监听。
异步组件
这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
components: {
"async-example": () => import('@/components/async-example.vue')
}
vue中异步组件和动态组件的有什么区别
1、动态组件是Vue中一个特殊的Html元素“<component>”,它拥有一个特殊的is属性,属性值可以是“已注册组件的名称”或“一个组件的选项对象”;而异步组件不是实物,是一个概念,一个可以让组件异步加载的方式。
2、动态组件用于不同组件之间进行动态切换;而异步组件用于性能优化,比如减小首屏加载时间、加载资源大小。
插槽slot
1.默认插槽
<header>
<slot name="header"></slot>
</header>
2.具名插槽
<header>
<slot name="header"></slot>
</header>
3.作用域插槽
vuex
state保存全局数据
Getter 类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据
mutation 改变state数据时,需要调用mutation中的提供的方法。
action 涉及异步操作修改state时或者同步批量操作,需要调用action中的方法;
Module Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action
action中方法无法直接修改state,还需要通过调用mutation中的方法来修改state数据。
dispatch 唯一能执行action的方法。
commite 对mutation中的方法执行调用。
语法糖:
...mapActions
...mapState
...mapGetters
...mapMutations
vue 中 $route 和 $router 有什么区别?
通过 this.$router 访问路由器,相当于获取了整个路由文件,在$router.option.routes中,或查看到当前项目的整个路由结构 具有实例方法
this.$route 当前激活的路由信息对象。
这个属性是只读的,里面的属性是 immutable (不可变) 的,不过可以 watch (监测变化) 它。
通过 this.$route 访问的是当前路由,获取和当前路由有关的信息
路由传参方式
1.query传参:
name和path都行,
url会带上参数,
通过this.$route.query获取
this.$router.push({ path: '/detail', query: {id: id} })
2.params传参:
只能name
url不会带上参数,刷新就没有了
通过this.$route.params获取
怎么定义vue-router的动态路由?怎么获取传过来的动态参数?
在router目录下的index.js文件中,对path属性加上/:id。
使用router对象的params.id。
// 这些都会传递给 createRouter
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
]
现在像 /users/johnny 和 /users/jolyne 这样的 URL 都会映射到同一个路由。
路径参数 用冒号 : 表示。
当一个路由被匹配时,它的 params 的值将在每个组件中以
this.$route.params 的形式暴露出来。
路由 及 路由守卫
全局路由守卫:
router.beforeEach((to, from) => {}
路由独享的守卫:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {},
},
]
组件内的守卫:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
路由懒加载
const UserDetails = () => import('./views/UserDetails.vue')
vue3.0/vue.2.0区别
数据双向绑定方面:
Vue2.0使用Object.defineProperty;
原理:
1.实例初始化时遍历data里的对象所有的property,并使用Object.defineProperty把这些property全部转为getter/setter,
访问对象的属性时getter函数触发并通过dep.depend()收集依赖,修改对象属性的值时setter函数触发并通过dep.notify()通知watcher
2.vue2对对象的检测是,为对象的每一个属性绑定Object.defineProperty的get、set方法(包括嵌套属性,需要递归)从而实现响应式,而对数组的检测,是对数组变更方法进行重载(7个方法,push()、pop()、shift()、unshift()、splice()、sort()、reverse())
Vue3.0通过ES6的新特性proxy来劫持数据,当数据改变时发出通知
Vue2使用defineProperty存在一些原因:
1.对数组拦截有问题,需要做特殊处理
2.不能拦截新增、删除的属性
3.defineProperty方案在初始化时候,需要深度递归遍历待处理的对象才能对它进行完全拦截,明显增加了初始化的时间。
无法检测数组元素的新增或删除,需要对数组方法进行重写,无法检测数组长度修改
无法检测到对象属性的添加或删除
必须遍历对象的每个属性,为每个属性都进行get、set拦截
必须遍历深层嵌套的对象属性
vue3使用proxy的优点:
1.使用proxy不污染源对象,会返回一个新对象,defineProperty是注入型的,会破坏源对象。
2.使用proxy只需要监听整个源对象的属性,不需要循环使用Object.definedProperty监听对象的属性。
3.使用proxy可以获取到对象属性的更多参数,使用definedProperty只能获取到监听属性的新值newValue。
不需要对数组的方法进行重载(解决vue2问题)
可以检测到对象属性的添加或删除,Proxy支持多达13种拦截操作(解决vue2问题)
针对整个对象,而不是对象的某个属性(浅层,仍需递归)
仍需要将嵌套对象属性进行遍历为响应式
生命周期函数比较
vue3的新特性
1.Fragments 碎片化节点 ( 不需要根节点 )
vue3是允许有多个根节点的,即template里面可以同时写多个节点并列,而vue2只能有一个根节点,在根节点里面再去添加其他节点。
在给组件上编写class名时,由于组件中可以有多个根节点,那么需要定义哪部分将接收这个class。
可以使用$attrs组件property执行此操作。
2.Teleport 传送组件
响应式变量 / 响应式工具
ref() ref一般用于定义普通数据类型和dom节点,使用 .value去取值 ( toRefs 结构数据,变成响应式数据)
rective() reactive一般用于定义复杂数据类型,使用的时候,直接取值即可
isRef() 检查某个值是否为 ref。
unref() 如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。
toRef() 基于响应式对象上的一个属性,创建一个对应的 ref。
toRefs() 将一个响应式对象转换为一个普通对象
isProxy() 检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
isReactive() 检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
isReadonly() 检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。
watchEffect
watchEffect它会立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const stop = watchEffect(() =>{},{flush:'post'}) //对写在回调函数里的所有数据监听
stop() //停止监听
flush:post,打印的是挂载后或更新后的数据
父子之间传参不同
Vue2.0 props $emit
Vue3.0 defineProps defineEmit emit
v-if、v-for优先级区别
Vue2:当在同一个元素上使用v-if时,将优先v-for
Vue3:v-if优先,再v-for
全局变量
Vue2.0 vue.protoType.$axios = axios;
vue3.0 全局api
const app = createApp({ /* root component options */})
app.provide()
app.directive()
app.component()
app.use()
app.config.globalProperties.info = info;
内置组件、动态组件、异步组件
内置组件:<slot>、<keep-alive>、<transition>、<transition-group>、<teleport>
<router-link>、<router-view>
动态组件:(不分包,打包进父组件js文件里)
<component :is="currentTabComponent"></component>
异步组件:
vue2:const asyncModal = () => import('./Modal.vue')
vue3: const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
vue性能优化
代码层面的优化
1.computed 和 watch 区分使用场景
2.v-if 和 v-show 区分使用场景
v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
3.key 保证唯一性,不要使用索引 ( vue 中diff算法会采用就地复用策略)。
4.合理使用路由懒加载、异步组件。
路由懒加载:
const Foo = () => import('./Foo.vue') //按需加载
const router = new VueRouter({routes: [ { path: '/foo', component: Foo } ] })
5.数据持久化的问题,使用防抖、节流进行优化,尽可能的少执行和不执行。
6.第三方插件的按需引入:例如ElementUI
v-on可以监听多个方法吗?
v-on可以监听多个方法
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。