Vue3.0 和 2.0 的响应式原理区别
Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。
相干代码如下
import { mutableHandlers } from "./baseHandlers"; // 代理相干逻辑import { isObject } from "./util"; // 工具办法export function reactive(target) { // 依据不同参数创立不同响应式对象 return createReactiveObject(target, mutableHandlers);}function createReactiveObject(target, baseHandler) { if (!isObject(target)) { return target; } const observed = new Proxy(target, baseHandler); return observed;}const get = createGetter();const set = createSetter();function createGetter() { return function get(target, key, receiver) { // 对获取的值进行喷射 const res = Reflect.get(target, key, receiver); console.log("属性获取", key); if (isObject(res)) { // 如果获取的值是对象类型,则返回以后对象的代理对象 return reactive(res); } return res; };}function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { console.log("属性新增", key, value); } else if (hasChanged(value, oldValue)) { console.log("属性值被批改", key, value); } return result; };}export const mutableHandlers = { get, // 当获取属性时调用此办法 set, // 当批改属性时调用此办法};
mixin 和 mixins 区别
mixin
用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
Vue.mixin({ beforeCreate() { // ...逻辑 // 这种形式会影响到每个组件的 beforeCreate 钩子函数 },});
尽管文档不倡议在利用中间接应用 mixin
,然而如果不滥用的话也是很有帮忙的,比方能够全局混入封装好的 ajax
或者一些工具函数等等。
mixins
应该是最常应用的扩大组件的形式了。如果多个组件中有雷同的业务逻辑,就能够将这些逻辑剥离进去,通过 mixins
混入代码,比方上拉下拉加载数据这种逻辑等等。
另外须要留神的是 mixins
混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。
delete和Vue.delete删除数组的区别
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
Vue 子组件和父组件执行程序
加载渲染过程:
- 父组件 beforeCreate
- 父组件 created
- 父组件 beforeMount
- 子组件 beforeCreate
- 子组件 created
- 子组件 beforeMount
- 子组件 mounted
- 父组件 mounted
更新过程:
- 父组件 beforeUpdate
- 子组件 beforeUpdate
- 子组件 updated
- 父组件 updated
销毁过程:
- 父组件 beforeDestroy
- 子组件 beforeDestroy
- 子组件 destroyed
- 父组件 destoryed
Vue模版编译原理晓得吗,能简略说一下吗?
简略说,Vue的编译过程就是将template
转化为render
函数的过程。会经验以下阶段:
- 生成AST树
- 优化
- codegen
首先解析模版,生成AST语法树
(一种用JavaScript对象的模式来形容整个模板)。 应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM也不会变动。那么优化过程就是深度遍历AST树,依照相干条件对树节点进行标记。这些被标记的节点(动态节点)咱们就能够跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是将优化后的AST树转换为可执行的代码
。
Vue组件data为什么必须是个函数?
- 根实例对象
data
能够是对象也能够是函数 (根实例是单例),不会产生数据净化状况 - 组件实例对象
data
必须为函数 一个组件被复用屡次的话,也就会创立多个实例。实质上,这些实例用的都是同一个构造函数。如果data
是对象的话,对象属于援用类型,会影响到所有的实例。所以为了保障组件不同的实例之间data
不抵触,data
必须是一个函数,
简版了解
// 1.组件的渲染流程 调用Vue.component -> Vue.extend -> 子类 -> new 子类// Vue.extend 依据用户定义产生一个新的类function Vue() {}function Sub() { // 会将data存起来 this.data = this.constructor.options.data();}Vue.extend = function(options) { Sub.options = options; // 动态属性 return Sub;}let Child = Vue.extend({ data:()=>( { name: 'zf' })});// 两个组件就是两个实例, 心愿数据互不感化let child1 = new Child();let child2 = new Child();console.log(child1.data.name);child1.data.name = 'poetry';console.log(child2.data.name);// 根不须要 任何的合并操作 根才有vm属性 所以他能够是函数和对象 然而组件mixin他们都没有vm 所以我就能够判断 以后data是不是个函数
相干源码
// 源码地位 src/core/global-api/extend.jsexport function initExtend (Vue: GlobalAPI) { Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const SuperId = Super.cid const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } const Sub = function VueComponent (options) { this._init(options) } // 子类继承大Vue父类的原型 Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub // 记录本人 在组件中递归本人 -> jsx } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub }}
Vue模版编译原理
vue中的模板template无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的HTML语法,所有须要将template转化成一个JavaScript函数,这样浏览器就能够执行这一个函数并渲染出对应的HTML元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。
- 解析阶段:应用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为形象语法树AST。
- 优化阶段:遍历AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行diff比拟时,间接跳过这一些动态节点,优化runtime的性能。
- 生成阶段:将最终的AST转化为render函数字符串。
什么是 mixin ?
- Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
- 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、 办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
- 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
双向数据绑定的原理
Vue.js 是采纳数据劫持联合发布者-订阅者模式的形式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:
- 须要observe的数据对象进行递归遍历,包含子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变动
- compile解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器(dep)外面增加本人 ②本身必须有一个update()办法 ③待属性变动dep.notice()告诉时,能调用本身的update()办法,并触发Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听本人的model数据变动,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据model变更的双向绑定成果。
参考:前端vue面试题具体解答
Vue中的key到底有什么用?
key
是为Vue中的vnode标记的惟一id,通过这个key,咱们的diff操作能够更精确、更疾速
diff算法的过程中,先会进行新旧节点的首尾穿插比照,当无奈匹配的时候会用新节点的key
与旧节点进行比对,而后超出差别.
diff程能够概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量互相比拟,一共有4种比拟形式。如果4种比拟都没匹配,如果设置了key,就会用key进行比拟,在比拟的过程中,变量会往两头靠,一旦StartIdx>EndIdx表明oldCh和newCh至多有一个曾经遍历完了,就会完结比拟,这四种比拟形式就是首、尾、旧尾新头、旧头新尾.
- 精确: 如果不加
key
,那么vue会抉择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug. - 疾速: key的唯一性能够被Map数据结构充分利用,相比于遍历查找的工夫复杂度O(n),Map的工夫复杂度仅仅为O(1).
应用 Object.defineProperty() 来进行数据劫持有什么毛病?
在对一些属性进行操作时,应用这种办法无奈拦挡,比方通过下标形式批改数组数据或者给对象新增属性,这都不能触发组件的从新渲染,因为 Object.defineProperty 不能拦挡到这些操作。更准确的来说,对于数组而言,大部分操作都是拦挡不到的,只是 Vue 外部通过重写函数的形式解决了这个问题。
在 Vue3.0 中曾经不应用这种形式了,而是通过应用 Proxy 对对象进行代理,从而实现数据劫持。应用Proxy 的益处是它能够完满的监听到任何形式的数据扭转,惟一的毛病是兼容性的问题,因为 Proxy 是 ES6 的语法。
vue-router 路由钩子函数是什么 执行程序是什么
路由钩子的执行流程, 钩子函数品种有:全局守卫、路由守卫、组件守卫
残缺的导航解析流程:
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入。
用过pinia吗?有什么长处?
1. pinia是什么?
- 在
Vue3
中,能够应用传统的Vuex
来实现状态治理,也能够应用最新的pinia
来实现状态治理,咱们来看看官网如何解释pinia
的:Pinia
是Vue
的存储库,它容许您跨组件/页面共享状态。- 实际上,
pinia
就是Vuex
的升级版,官网也说过,为了尊重原作者,所以取名pinia
,而没有取名Vuex
,所以大家能够间接将pinia
比作为Vue3
的Vuex
2. 为什么要应用pinia?
Vue2
和Vue3
都反对,这让咱们同时应用Vue2
和Vue3
的小伙伴都能很快上手。pinia
中只有state
、getter
、action
,摈弃了Vuex
中的Mutation
,Vuex
中mutation
始终都不太受小伙伴们的待见,pinia
间接摈弃它了,这无疑缩小了咱们工作量。pinia
中action
反对同步和异步,Vuex
不反对- 良好的
Typescript
反对,毕竟咱们Vue3
都举荐应用TS
来编写,这个时候应用pinia
就十分适合了 - 无需再创立各个模块嵌套了,
Vuex
中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而pinia
中每个store
都是独立的,相互不影响。 - 体积十分小,只有
1KB
左右。 pinia
反对插件来扩大本身性能。- 反对服务端渲染
3. pinna应用
pinna文档(opens new window)
- 筹备工作
咱们这里搭建一个最新的Vue3 + TS + Vite
我的项目
npm create [email protected] my-vite-app --template vue-ts
pinia
根底应用
yarn add pinia
// main.tsimport { createApp } from "vue";import App from "./App.vue";import { createPinia } from "pinia";const pinia = createPinia();const app = createApp(App);app.use(pinia);app.mount("#app");
2.1 创立store
//sbinsrc/store/user.tsimport { defineStore } from 'pinia'// 第一个参数是应用程序中 store 的惟一 idexport const useUsersStore = defineStore('users', { // 其它配置项})
创立store
很简略,调用pinia
中的defineStore
函数即可,该函数接管两个参数:
name
:一个字符串,必传项,该store
的惟一id
。options
:一个对象,store
的配置项,比方配置store
内的数据,批改数据的办法等等。
咱们能够定义任意数量的store
,因为咱们其实一个store
就是一个函数,这也是pinia
的益处之一,让咱们的代码扁平化了,这和Vue3
的实现思维是一样的
2.2 应用store
<!-- src/App.vue --><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();console.log(store);</script>
2.3 增加state
export const useUsersStore = defineStore("users", { state: () => { return { name: "test", age: 20, sex: "男", }; },});
2.4 读取state
数据
<template> <img alt="Vue logo" src="./assets/logo.png" /> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <p>性别:{{ sex }}</p></template><script setup lang="ts">import { ref } from "vue";import { useUsersStore } from "../src/store/user";const store = useUsersStore();const name = ref<string>(store.name);const age = ref<number>(store.age);const sex = ref<string>(store.sex);</script>
上段代码中咱们间接通过store.age
等形式获取到了store
存储的值,然而大家有没有发现,这样比拟繁琐,咱们其实能够用解构的形式来获取值,使得代码更简洁一点
import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store); // storeToRefs获取的值是响应式的
2.5 批改state
数据
<template> <img alt="Vue logo" src="./assets/logo.png" /> <p>姓名:{{ name }}</p> <p>年龄:{{ age }}</p> <p>性别:{{ sex }}</p> <button @click="changeName">更改姓名</button></template><script setup lang="ts">import child from './child.vue';import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store);const changeName = () => { store.name = "张三"; console.log(store);};</script>
2.6 重置state
- 有时候咱们批改了
state
数据,想要将它还原,这个时候该怎么做呢?就比方用户填写了一部分表单,忽然想重置为最初始的状态。 - 此时,咱们间接调用
store
的$reset()
办法即可,持续应用咱们的例子,增加一个重置按钮
<button @click="reset">重置store</button>// 重置storeconst reset = () => { store.$reset();};
当咱们点击重置按钮时,store
中的数据会变为初始状态,页面也会更新
2.7 批量更改state
数据
如果咱们一次性须要批改很多条数据的话,有更加简便的办法,应用store
的$patch
办法,批改app.vue
代码,增加一个批量更改数据的办法
<button @click="patchStore">批量批改数据</button>// 批量批改数据const patchStore = () => { store.$patch({ name: "张三", age: 100, sex: "女", });};
- 有教训的小伙伴可能发现了,咱们采纳这种批量更改的形式仿佛代价有一点大,如果咱们
state
中有些字段无需更改,然而依照上段代码的写法,咱们必须要将state中的所有字段例举出了。 - 为了解决该问题,
pinia
提供的$patch
办法还能够接管一个回调函数,它的用法有点像咱们的数组循环回调函数了。
store.$patch((state) => { state.items.push({ name: 'shoes', quantity: 1 }) state.hasChanged = true})
2.8 间接替换整个state
pinia
提供了办法让咱们间接替换整个state
对象,应用store
的$state
办法
store.$state = { counter: 666, name: '张三' }
上段代码会将咱们提前申明的state
替换为新的对象,可能这种场景用得比拟少
getters
属性getters
是defineStore
参数配置项外面的另一个属性- 能够把
getter
设想成Vue
中的计算属性,它的作用就是返回一个新的后果,既然它和Vue
中的计算属性相似,那么它必定也是会被缓存的,就和computed
一样
3.1 增加getter
export const useUsersStore = defineStore("users", { state: () => { return { name: "test", age: 10, sex: "男", }; }, getters: { getAddAge: (state) => { return state.age + 100; }, },})
上段代码中咱们在配置项参数中增加了getter
属性,该属性对象中定义了一个getAddAge
办法,该办法会默认接管一个state
参数,也就是state
对象,而后该办法返回的是一个新的数据
3.2 应用getter
<template> <p>新年龄:{{ store.getAddAge }}</p> <button @click="patchStore">批量批改数据</button></template><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();// 批量批改数据const patchStore = () => { store.$patch({ name: "张三", age: 100, sex: "女", });};</script>
上段代码中咱们间接在标签上应用了store.gettAddAge
办法,这样能够保障响应式,其实咱们state
中的name
等属性也能够以此种形式间接在标签上应用,也能够放弃响应式
3.3 getter
中调用其它getter
export const useUsersStore = defineStore("users", { state: () => { return { name: "test", age: 20, sex: "男", }; }, getters: { getAddAge: (state) => { return state.age + 100; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, },});
3.3 getter
传参
export const useUsersStore = defineStore("users", { state: () => { return { name: "test", age: 20, sex: "男", }; }, getters: { getAddAge: (state) => { return (num: number) => state.age + num; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, },});
<p>新年龄:{{ store.getAddAge(1100) }}</p>
actions
属性- 后面咱们提到的
state
和getter
s属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的data
数据和computed
计算属性一样。 - 那么,如果咱们有业务代码的话,最好就是卸载
actions
属性外面,该属性就和咱们组件代码中的methods
类似,用来搁置一些解决业务逻辑的办法。 actions
属性值同样是一个对象,该对象外面也是存储的各种各样的办法,包含同步办法和异步办法
4.1 增加actions
export const useUsersStore = defineStore("users", { state: () => { return { name: "test", age: 20, sex: "男", }; }, getters: { getAddAge: (state) => { return (num: number) => state.age + num; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, }, actions: { // 在理论场景中,该办法能够是任何逻辑,比方发送申请、存储token等等。大家把actions办法当作一个一般的办法即可,非凡之处在于该办法外部的this指向的是以后store saveName(name: string) { this.name = name; }, },});
4.2 应用actions
应用actions
中的办法也非常简单,比方咱们在App.vue
中想要调用该办法
const saveName = () => { store.saveName("poetries");};
总结
pinia
的知识点很少,如果你有Vuex根底,那么学起来更是大海捞针
pinia无非就是以下3个大点:
state
getters
actions
Vue性能优化
编码优化:
- 事件代理
keep-alive
- 拆分组件
key
保障唯一性- 路由懒加载、异步组件
- 防抖节流
Vue加载性能优化
- 第三方模块按需导入(
babel-plugin-component
) - 图片懒加载
用户体验
app-skeleton
骨架屏shellap
p壳pwa
SEO优化
- 预渲染
谈谈对keep-alive的理解
keep-alive 能够实现组件的缓存,当组件切换时不会对以后组件进行卸载。罕用的2个属性
include/exclude ,2个生命周期
activated ,
deactivated
理解nextTick吗?
异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用nextTick
会将办法存入队列,通过异步办法清空以后队列。
Vue的长处
- 轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十
kb
; - 简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;
- 双向数据绑定:保留了
angular
的特点,在数据操作方面更为简略; - 组件化:保留了
react
的长处,实现了html
的封装和重用,在构建单页面利用方面有着独特的劣势; - 视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
- 虚构DOM:
dom
操作是十分消耗性能的,不再应用原生的dom
操作节点,极大解放dom
操作,但具体操作的还是dom
不过是换了另一种形式; - 运行速度更快:相比拟于
react
而言,同样是操作虚构dom
,就性能而言,vue
存在很大的劣势。
如何从实在DOM到虚构DOM
波及到Vue中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的JS语法(将实在DOM转换成虚构DOM) - 优化树
- 将
ast
树生成代码
Vue3.0有什么更新
(1)监测机制的扭转
- 3.0 将带来基于代理 Proxy的 observer 实现,提供全语言笼罩的反馈性跟踪。
- 打消了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限度:
(2)只能监测属性,不能监测对象
- 检测属性的增加和删除;
- 检测数组索引和长度的变更;
- 反对 Map、Set、WeakMap 和 WeakSet。
(3)模板
- 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会从新渲染,而 3.0 把作用域插槽改成了函数的形式,这样只会影响子组件的从新渲染,晋升了渲染的性能。
- 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来不便习惯间接应用 api 来生成 vdom 。
(4)对象式的组件申明形式
- vue2.x 中的组件是通过申明的形式传入一系列 option,和 TypeScript 的联合须要通过一些装璜器的形式来做,尽管能实现性能,然而比拟麻烦。
- 3.0 批改了组件的申明形式,改成了类式的写法,这样使得和 TypeScript 的联合变得很容易
(5)其它方面的更改
- 反对自定义渲染器,从而使得 weex 能够通过自定义渲染器的形式来扩大,而不是间接 fork 源码来改的形式。
- 反对 Fragment(多个根节点)和 Protal(在 dom 其余局部渲染组建内容)组件,针对一些非凡的场景做了解决。
- 基于 tree shaking 优化,提供了更多的内置性能。
v-on能够监听多个办法吗?
能够监听多个办法
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
v-on 罕用修饰符
.stop
该修饰符将阻止事件向上冒泡。同理于调用event.stopPropagation()
办法.prevent
该修饰符会阻止以后事件的默认行为。同理于调用event.preventDefault()
办法.self
该指令只当事件是从事件绑定的元素自身触发时才触发回调.once
该修饰符示意绑定的事件只会被触发一次