MVC与MVVM有什么区别
出现频率: 50%
掌握难度:70分
作用:
参考答案:
MVC:
M:数据模型层,指的是数据逻辑层面,通常是数据在数据库的存储。
v:视图层,指的是页面对数据的展示、输入等。
C:控制器,数据模型层和视图层桥梁。
因为数据模型层和视图层之间还需要进行一些业务逻辑的操作,不能直接交互,所以把两者之间的业务逻辑的操作抽离出来,做成一个控制器层,当作两者的交互的桥梁。这样就实现了各功能模块的分层,便于开发维护。
MVVM:
M:数据模型层,指的是数据逻辑层面,通常是数据库返回的数据。
V:视图层,指的是页面对数据的展示、输入等。
VM:视图模型层,数据模型层和视图层桥梁,并且实现了数据模型层和视图层的双向绑定。
共同点和区别:
总的来说,两种软件架构模式都是把这个流程分成了三个功能模块层,目的也都是为了便于开发维护;也没有绝对的好坏之分,要根据实际的业务场景选择合适的软件架构模式。
如果有大量的数据操作、DOM操作,MVVM更适合一些,因为MVVM实现了数据模型层和视图层的双向绑定,能够自动完成数据模型层和视图层的实时更新,避免了繁琐的DOM操作,开发效率更高。
参考答案:
模式说明:MVVM = Model + View + ViewModel
- MVVM,一种软件架构模式,决定了写代码的思想和层次
● M: model数据模型 (data里定义)
● V: view视图 (template里定义html页面结构)
● VM: ViewModel视图模型 (vue组件实例this)
- MVVM通过数据双向绑定让数据自动地双向同步 不再需要操作DOM
● V(修改视图) -> M(数据自动同步)
● M(修改数据) -> V(视图自动同步)
实际使用场景:
说一下 Vue 的优点
出现频率: 50%
掌握难度:40分
作用:
参考答案:
优缺点是相对于某一参照标准来说的,而vue之所以能够有广大的市场,是因为相对之前juery这个参照标准来说具有很大优势:
响应式编程
之前juery设计思想还和js类似,对于页面的交互更新需要繁琐的dom操作,只不过相对于js而言编写起来代码更加简洁,相当于js的简化版而已。繁琐的dom操作对于开发人员来说效率低下。
而vue通过Object.defineProperty()来劫持各个属性的setter、getter,再结合订阅-发布模式来实现数据的双向绑定后,不需要进行大量的dom操作,只需要关注业务层面的逻辑代码编写,把数据丢给vue就会自动更新,这样开发人员的效率会提升很多。
由于操作dom会耗费很大的性能开销,vue在虚拟DOM层面采用diff算法计算出需要更新的部分,其余部分不做处理,只更新需要更新的部分,这样就在更新页面时最大限度的降低了性能损耗。
组件化开发
vue支持组件化开发,可以把一个复杂的项目细化拆分成多个不同功能模块的组件,每个组件之间相互独立避免污染,有利于开发人员协同开发;同时组件间还有多种传参方式,也兼顾了组件之间的关联性和灵活性。
实际使用场景:
Vue双向绑定
出现频率: 70%
掌握难度:80分
作用:
参考答案:
通过Object.defineProperty()来劫持各个属性的setter、getter,再结合订阅-发布模式来实现数据的双向绑定。
通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据model变更的双向绑定效果。
当Observer监听到model数据变化后,会通知订阅器dep更新所有使用此数据的watcher进行Compile中更新函数的执行,达到页面数据的更新;当页面视图层的数据发生了变化,会通过事件的方式去操作数据模型层中数据更新。
vue3中使用Proxy代理替代了vue2中的Object.defineProperty(),Proxy代理有以下优点:
Proxy代理可以监测整个对象、包括动态添加的对象属性,而Object.defineProperty()只能监测对象的单个属性,然后通过循环遍历每个属性的方式才能达到监测整个对象、且不能监测动态添加属性;
Proxy代理可以监测数组、并且还可以监测数组下标、length属性的变化,而Object.defineProperty()不能监测数组的变化;
Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢?
判断当前 Reflect.get 的返回值是否为 Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次 get/set,那么如何防止触发多次呢?
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。
在检测器Observer中通过Object.defineProperty对对象属性的getter/setter进行劫持;在订阅器Dep中收集订阅者Wacher(依赖);在订阅者Wacher中把更新函数和变量进行绑定;在解析器Compile中初始化更新函数、变量等。
在初始化时,首先解析器Compile中初始化更新函数、变量;然后触发getter/setter进行劫持;然后触发创建订阅者Wacher;然后触发订阅器Dep中收集订阅者Wacher;
Vue 在初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher),如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。
Vue2.x 采用数据劫持结合发布订阅模式(PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 的 getter/setter劫持对象的获取修改操作。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果。
Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用 Object.defineProperty 所存在的一些问题。
———————————————————————————————————————— blog.csdn.net/junjunaijij…
实际使用场景:
v-model 双向绑定的原理是什么?
出现频率:80%
掌握难度:80分
作用:考察对v-model 双向绑定的原理理解程度
参考答案:
v-model 本质就是 :value + input 方法的语法糖。可以通过 model 属性的 prop 和 event 属性来进行自定义。原生的 v-model,会根据标签的不同生成不同的事件和属性。
例如:
- text 和 textarea 元素使用 value 属性和 input 事件
- checkbox 和 radio 使用 checked 属性和 change 事件
- select 字段将 value 作为 prop 并将 change 作为事件
以输入框为例,当用户在输入框输入内容时,会触发 input 事件,从而更新 value。而 value 的改变同样会更新视图,这就是 vue 中的双向绑定。双向绑定的原理,其实现思路如下:
首先要对数据进行劫持监听,所以我们需要设置一个监听器 Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者 Watcher 看是否需要更新。
因为订阅者是有很多个,所以我们需要有一个消息订阅器 Dep 来专门收集这些订阅者,然后在监听器 Observer 和订阅者 Watcher 之间进行统一管理的。
接着,我们还需要有一个指令解析器 Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
因此接下去我们执行以下 3 个步骤,实现数据的双向绑定:
-
实现一个监听器 Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
-
实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
-
实现一个解析器 Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
实际使用场景:
Vue2.x 中如何检测数组的变化?
出现频率: 40%
掌握难度:50分
作用:
参考答案:
由于Object.defineProperty只能拦截对象属性的getter、setter,无法对数组元素产生拦截作用,所以导致无法监测到数组的更新,导致页面也无法实时更新。
为了让页面能够实时响应数组的变化,vue对数组原型上的方法进行了重写,把触发页面更新的操作写进数组重写的原型方法中,这样在通过数组原型方法修改数组时页面就会实时更新了。
但是有两种修改数组的方式不是通过数组重写的原型方法修改的--修改数组length值和利用索引直接设置一个数组项时,这时无法监测到数组的变化,页面就不能对数组进行实时更新。vue通过vm.items.splice(indexOfItem, 1, newValue)和vm.$set(vm.items, indexOfItem, newValue)解决了这两个问题。
实际使用场景:
diff 算法
出现频率: 50%
掌握难度:80分
作用:
参考答案:
简单来说,diff算法的大致分为一下过程:
先进行同级节点的比较,如果有子节点再进行子节点的比较;
如果新节点有子节点而旧节点没有子节点,直接创建新的节点;如果新节点没有子节点而旧节点有子节点直接删除子节点;
如果新旧节点都有子节点则进行diff算法(核心)的对比;
最后递归对比所有子节点。
在进行同级新旧节点对比时,利用节点唯一key值进行双端diff算法的比较,这样较大限度的能够复用节点,节省性能。
既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 监测差异 ?
Vue 的响应式系统则是 push 的代表,当 Vue 程序初始化的时候就会对数据 data 进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知,因此 Vue 是一开始就知道是「在哪发生变化了」
但是这又会产生一个问题,通常绑定一个数据就需要一个 Watcher,一但我们的绑定细粒度过高就会产生大量的 Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此 Vue 的设计是选择中等细粒度的方案,在组件级别进行 push 侦测的方式,也就是那套响应式系统。
通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行 Virtual Dom Diff 获取更加具体的差异,而 Virtual Dom Diff 则是 pull 操作,Vue 是 push + pull 结合的方式进行变化侦测的。
实际使用场景:
Vue complier
出现频率: 50%
掌握难度:80分
作用:
参考答案:
complier的原理是将vue模板转化为一个表示页面结构ast树对象,然后再对动态节点和静态节点做标记,能够节省后期更新时进行diff对比的性能,静态节点直接跳过,只对比动态节点。然后再将标记优化后的ast树对象生成render函数,执行render函数会生成vnode。
实际使用场景:
vue3.0 是如何变得更快的?
出现频率:30%
掌握难度:50分
作用:
参考答案: 打包层面:
在打包时对于一些无用的代码、没有引用的代码、引用没有被执行的代码直接排除在外,降低了打包的体积,提升了传输效率和用户体验。移除了一些不常用的组件、api(inline-template、filter、keep-alive)
更新渲染层面:juejin.cn/post/729492…
之前的vue更新时是对整个ast树节点做静态标记,然后判断如果是动态节点就进行diff的对比,如果是静态节点就直接跳过;vue3将模版中的静态节点和属性提取到render函数外面,在组件更新的时候,减少vnode的创建带来的性能损耗。
响应式系统提升:
Vue3中响应式数据原理改成proxy,可监听动态新增删除属性,以及数组变化
一些其他的变化:juejin.cn/post/720319…
-
源码组织方式变化:使用 TS 重写
-
支持 Composition API:基于函数的API,更加灵活组织组件逻辑(vue2用的是options api)
-
生命周期的变化:使用setup代替了之前的beforeCreate和created
-
Vue3 的 template 模板支持多个根标签
-
Vuex状态管理:创建实例的方式改变,Vue2为new Store , Vue3为createStore
-
Route 获取页面实例与路由信息:vue2通过this获取router实例,vue3通过使用 getCurrentInstance/ userRoute和userRouter方法获取当前组件实例
-
Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props
-
父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下
实际使用场景:
vue 的数据为什么频繁变化但只会更新一次?
出现频率:30%
掌握难度:30分
作用:
参考答案:
因为频繁的dom更新比较浪费性能,为了节约性能,vue采用了异步更新方式。
每一次的更新操作都会触发相应的setter把wacther存入一个队列,并且同一个wacther只能被存入队列一次,等到此次微任务事件循环结束才会执行队列进行更新。
实际使用场景:
Vue 组件的 data 为什么必须是函数
出现频率: 50%
掌握难度:20分
作用:
参考答案:
组件中的 data 写成一个函数,数据以函数返回值形式定义。这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。
实际使用场景:
computed
出现频率: 50%
掌握难度:50分
作用:
参考答案:
computed和date的实现原理大体相同,只是在date的基础之上添加了lazy机制,通过dirty来判断是否重新计算更新。当其依赖的数据发生变化时,会将dirty设置为true,然后进行计算更新,最后再将dirty设置为false;等到下次发生变化时再重复以上步奏。当dirty为false时,无论是否有页面其他操作变化,只要dirty不为false,就不进行更新。
computed中的属性是自定义的属性值,如果监测data和prop中的值会报错;而watch监测的值是data和prop中的值。
实际使用场景:
Vue 生命周期
出现频率: 70%
掌握难度:50分
作用:
参考答案:
Vue 生命周期是vue实例在从创建到销毁过程中,不同的阶段对应着不同的钩子函数,开发者可以根据需要在不同的钩子函数中添加业务逻辑代码,来实现特定的功能。
- vue 生命周期有几个阶段
它可以总共分为 8 个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。
- beforeCreate:是 new Vue( ) 之后触发的第一个钩子,在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。
- created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 DOM 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 DOM 。
接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面 loading 时间
- SSR 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于代码的一致性
- created 是在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。如果在 mounted 钩子函数中请求数据可能导致页面闪屏问题
- beforeMount:发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 DOM 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated。
- mounted:在挂载完成后发生,在当前阶段,***真实的 DOM 挂载完毕,数据完成双向绑定,可以访问到 DOM 节点,***使用 $refs 属性对 DOM 进行操作。
- beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟 DOM 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
- updated:发生在更新完成之后,当前阶段组件 DOM 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
- beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
- destroyed:发生在实例销毁之后,这个时候只剩下了 DOM 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。组件的销毁操作是先父后子,销毁完成的顺序是先子后父。(开始阶段是:先父后自,完成阶段是:先子后父)
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate-> 子created->子beforeMount- >子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
实际使用场景:
传参的方式
出现频率: 80%
掌握难度:50分
作用:
参考答案:
父子组件间进行传参
父向子组件传参:
在父作用域中子组件标签上同v-bind绑定传递参数,在子组件中通过prop进行接收数据;
$attrs/$listeners方式也可以实现父子组件之间的传参,在父作用域中子组件标签上同v-bind绑定listeners,在子组件中可以通过listeners获取到父组件除了原生事件外的所有事件。
子向父组件传参:
在父作用域中子组件标签上同v-on绑定事件以参数形式接收参数,在子组件中通过$emit以参数形式进行接收数据;
通过children与 ref也可以实现父子间之间的传参。
跨级组件之间的传参
祖先向子孙组件传参:
// 祖先组件
data() {
return {
obj:{name:'dax1'},
}
}
provide(){//此过程不会对数据进行响应式处理,需要将具有响应式功能的数据注册才可以实现响应式
return{
username:this.obj // 此处provide一个对象
}
},
// 后代组件
export default {
inject: ['username']
}
任意层级组件之间的传参方式
evenbus事件中心传参:
创建一个vue实例,通过调用这个实例来监听、触发事件,达到通过事件传参实现传参。可以通过引入实例或者把实例挂在根实例原型上来获取调用事件中心实例。但是此种方式不便于参数操作的跟踪,对于复杂的大型项目会产生较大的维护负担。
vuex方式传参:
此方式传递的参数可以实现追踪,便于后期维护;但是一旦刷新页面,vuex中的状态数据就会被初始化,无法保存vuex的状态数据。可以把vuex中的数据保存到localStorage中,刷新页面时再从localStorage中获取。
localStorage、sessionStorage方式传参:
可以实现组件之间的参数传递,但是此种方式只能接受字符串格式的数据,数据无法实现响应式。
地址栏传参:
可以通过原生地址栏传参、vue路由传参也可以实现不同组件之间的参数的传递。
?后的数据是传递的参数,#后的页面的哈希值。
vue路由query传递的参数会在地址栏显示,支持路径路由和命名路由两种方式,刷新不会丢失;pamar只能通过命名路由的方式进行传递参数,不会在地址栏显示,刷新会丢失。
实际使用场景:
v-if 与 v-show 的区别
出现频率: 50%
掌握难度:20分
作用:
参考答案:
v-if每一次的切换都是dom节点的重建、与销毁,所以频繁切换的场景下会消耗较大的性能;但是在初始状态为隐藏时,且后期大概率不需要显示时用v-if较好。如:根据用户角色通过v-if控制展示不同内容
v-show只有初始状态时会创建真实dom节点,以后每次的隐藏与现实都是通过切换display的值来实现的,所以真实dom节点并没有进行销毁和重建;在初始状态会消耗较大性能,后期频繁切换只需较小性能,所以在频繁切换的业务场景下建议使用v-show;如选项卡场景
实际使用场景:
自定义指令
出现频率: 10%
掌握难度:30分
作用:
参考答案:
Vue.directive('my-directive', {
bind(el, binding, vnode) {
// 初始化设置
},
inserted(el, binding, vnode) {
// 元素插入父元素时调用
},
update(el, binding, vnode) {
// 组件更新时调用
},
componentUpdated(el, binding, vnode) {
// 组件及子组件更新后调用
},
unbind(el, binding, vnode) {
// 解绑时调用
}
});
实际使用场景:
delete 和 Vue.delete 删除数组的区别是什么?
出现频率: 20%
掌握难度:20分
作用:
参考答案:
delete 只是数组或对象被删除的元素或者属性变成了 empty/undefined 其他的元素的键值还是不变。 Vue.delete 是直接将元素从数组中完全删除,改变了数组其他元素的键值。
delete会删除数组的值,但是会保留占位。
vue.delete删除数组的值并且删除占位。
vue.delete可以避免vue检测不到新的property
实际使用场景:
<style scoped></style>中scoped的实现原理
出现频率: 30%
掌握难度:20分
作用:
参考答案:
vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现的。PostCSS 给一个组件中的所有 DOM 添加了一个独一无二的动态属性,然后,给 CSS 选择器额外添加一个对应的属性选择器来选择该组件中 DOM,这种做法使得样式只作用于含有该属性的 DOM,即组件内部 DOM。
转译前
<template>
<div class="example">hi</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
转译后:
<template>
<div class="example" data-v-5558831a>
hi
</div>
</template>
<style>
.example[data-v-5558831a] { color: red; }
</style>
::v-deep 操作符( >>> 的别名)实现穿透。有些像 Sass 之类的预处理器无法正确解析 >>>,所以需要使用 ::v-deep 操作符来代替。
实际使用场景:
keep-alive内置组件
出现频率: 40%
掌握难度:30分
作用:
参考答案:
keep-alive是vue内置的一个组件,主要用于缓存需要频繁切换的组件,通过这个组件的缓存实例组件在切换时不需要重复的创建、销毁,只需要读取缓存就行了;这样不仅节约了性能,同时还可以起到记忆组件状态的作用。
include和exclude属性是用于控制组件是否进入缓存序列,max属性用于控制缓存序列的个数,当缓存的组件个数超过最大值会先移除最早进入缓存的组件。
actived和deactived这两个钩子函数,是其内部缓存组件具有的两个钩子,当缓存的组件被激活和失活时被触发。
原理: 通过组件id和tag生成一个唯一的key值,然后通过数组来存储所有的key值,同时通过把key值当作组件对象的键、把组件的vnode当作组件对象的值,然后把组件对象缓存入一个指定对象,当缓存的组件超过max值,则先移除数组中最先加入key值,同时移除key值所对应的组件对象;当渲染一个组件时,先查找是否有对应的key值,如果有则直接调用缓存,并更新key值在数组中的位置;如果没有则创建一个新的缓存对象,并将key值加入数组。
实际使用场景:
nextTick
出现频率: 50%
掌握难度:40分
作用:
参考答案:
因为vue的dom更新是异步更新,也就是在所有的实例数据更新完后再进行dom渲染更新,这样是为了节约性能。
nextTick是在dom更新完成后再进行相应的操作。
nextTick是通过宏任务和微任务来实现异步更新的,因为宏任务大于微任务的性能消耗,所以是优先使用微任务实现异步更新,如果不支持微任务再使用宏任务实现。
实际使用场景:
vue事件及其修饰符都有哪些
出现频率: 20%
掌握难度:20分
作用:
参考答案:
vue中事件的写法:
写法三:在一个事件里面书写多个事件处理函数 <div @事件类型=“事件处理函数1,事件处理函数2”>
写法四:在事件处理函数内部调用其他的函数
在事件处理程序中调用 event.preventDefault 或 event.stopPropagation 方法是非常常见的需求。但是像这种有关dom层面的细节一般我们都是放在dom层面更合理,所以vue使用了修饰符来实现这个dom层面的细节问题。
事件修饰符:
.self:只在当前dom元素上触发事件;
.stop:阻止冒泡修饰符,如:@click.stop;
.capture:允许事件捕获修饰符;
.once:只触发一次修饰符;
.prevent:阻止默认行为修饰符;
.passive:马上触发默认行为;
按键修饰符
除了事件修饰符以外,在 vue 中还提供了有鼠标修饰符,键值修饰符,系统修饰符等功能。如:@keyup.13、@keyup.enter
js原生实现方式:
window.addEventListener("keydown", function (e) {
var keyCode = e.keyCode ? e.keyCode : e.which;
if(e.ctrlKey && keyCode==80){
e.preventDefault();
} });
- .left:左键
- .right:右键
- .middle:滚轮
- .enter:回车
- .tab:制表键
- .delete:捕获 “删除” 和 “退格” 键
- .esc:返回
- .space:空格
- .up:上
- .down:下
- .left:左
- .right:右
- .ctrl:ctrl 键
- .alt:alt 键
- .shift:shift 键
- .meta:meta 键
表单修饰符
vue 同样也为表单控件也提供了修饰符,常见的有 .lazy、 .number 和 .trim。如:v-model.lazy。
- .lazy:在文本框失去焦点时才会渲染
- .number:将文本框中所输入的内容转换为number类型
- .trim:可以自动过滤输入首尾的空格
实际使用场景:
Vue路由实现
出现频率: 70%
掌握难度:50分
作用:
参考答案:
hash模式
监听hashchange事件实现前端路由,利用url中的hash来模拟一个hash,以保证url改变时,页面不会重新加载。
history模式
利用pushstate和replacestate来将url替换但不刷新,但是有一个致命点就是,一旦刷新的话,就会可能404,因为没有当前的真正路径,要想解决这一问题需要后端配合,将不存在的路径重定向到入口文件。
实际使用场景:
解释 hash 模式和 history 模式的实现原理
后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState 和 replaceState,这两个API可以在不进行刷新的情况下,操作浏览器的历史记录。这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
blog.csdn.net/weixin_4612…
history模式原理可以这样理解,首先我们要改造我们的超链接,给每个超链接增加onclick方法,阻止默认的超链接跳转,改用history.pushState或history.replaceState来更改浏览器中的url,并修改页面内容。由于通过history的api调整,并不会向后端发起请求,所以也就达到了前端路由的目的。
如果用户使用浏览器的前进后退按钮,则会触发window.onpopstate事件,监听页面根据路由地址修改页面内容。
也不一定非要用超链接,任意元素作为菜单都行,只要在点击事件中通过 history 进行调整即可。
两种模式的区别:
- 首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
- 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
- 在兼容性上,hash 可以支持低版本浏览器和 IE
vueRouter 有哪几种导航守卫?
- 全局前置/钩子(Router实例上的方法钩子):beforeEach、beforeR-esolve、afterEach
- 路由独享的守卫(某个route配置内的方法钩子):beforeEnter
- 组件内的守卫(某个组件内部配置的方法钩子):beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
解释一下 vueRouter 的完整的导航解析流程是什么
一次完整的导航解析流程如下:
- 1.导航被触发。
- 2.在失活的组件里调用离开守卫。
- 3.调用全局的 beforeEach 守卫。
- 4.在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 5.在路由配置里调用 beforeEnter。
- 6.解析异步路由组件。
- 7.在被激活的组件里调用 beforeRouteEnter。
- 8.调用全局的 beforeResolve 守卫(2.5+)。
- 9.导航被确认。
- 10.调用全局的 afterEach 钩子。
- 11.触发 DOM 更新。
- 12.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
实际使用场景:
vuex
出现频率: 70%
掌握难度:50分
作用:
参考答案:
vuex是用来存储、共享各组件中状态的容器。特别对于中大型项目,组件间状态的存储、共享比较频繁。
例如登录状态、加入购物车、音乐播放等
import { createStore } from 'vuex'
export default createStore({
strict: true,
//全局state,类似于vue种的data
state() {
return {
vuexmsg: "hello vuex",
name: "xiaoyue",
};
},
//修改state函数
mutations: {
setVuexMsg(state, data) {
state.vuexmsg = data;
},
},
//提交的mutation可以包含任意异步操作
actions: {
async getState({ commit }) {
//const result = await xxxx 假设这里进行了请求并拿到了返回值
commit("setVuexMsg", "hello juejin"); },
},
//类似于vue中的计算属性
getters: {
},
//将store分割成模块(module),应用较大时使用
modules: {
}
})
<template>
<div></div>
</template>
<script setup>
import { useStore } from 'vuex'
let vuexStore = useStore()
vuexStore.commit('setVuexMsg', 'hello juejin')
vuexStore.dispatch('getState')
console.log(vuexStore.state.vuexmsg); //hello vuex
</script>
Pinia
``
export const storeA = defineStore("storeA", {
state: () => {
return {
piniaMsg: "hello pinia",
count1: 1,
count2: 2,
};
},
getters: {
sum() {
console.log('我被调用了!')
return this.count1 + this.count2;
},
},
actions: {
setName(data) {
this.name = data;
},
},
});
<template>
<div>{{ piniaStoreA.piniaMsg }}</div>
</template>
<script setup>
import { storeA } from '@/piniaStore/storeA'
let piniaStoreA = storeA()
console.log(piniaStoreA.piniaMsg); //hello pinia
piniaStoreA.piniaMsg = 'hello juejin'
console.log(piniaStoreA.piniaMsg); //hello juejin
piniaStoreA.$patch({
piniaMsg: 'hello juejin',
name: 'daming'
})
console.log(piniaStoreA.name);//daming
cartStore.$patch((state) => {
state.name = 'daming'
state.piniaMsg = 'hello juejin'
})
piniaStoreA.setName('daming')
piniaStoreA.$reset()
</script>
实际使用场景:
vue 项目中的性能优化
出现频率: 80%
掌握难度:80分
作用:
参考答案:
工具层面:
-
压缩代码
-
Tree Shaking/Scope Hoisting
-
使用可视化工具分析打包后的模块体积:webpack-bundle- analyzer 这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化
使用 svg 图标:相对于用一张图片来表示图标,svg 拥有更好的图片质量,体积更小,并且不需要开启额外的 http 请求
压缩图片:可以使用 image-webpack-loader,在用户肉眼分辨不清的情况下一定程度上压缩图片
编码层面:
第三方模块按需导入;
使用路由懒加载、异步组件:myComponent: () => import('./my-async-component');
长列表滚动到可视区域动态加载、图片懒加载;
对于无用的代码删除或者注释掉;
尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
v-if 和 v-for 不能连用;在更多的情况下,使用 v-if 替代 v-show;如果需要使用 v-for 给每项元素绑定事件时使用事件代理;key 保证唯一;
SPA 页面采用 keep-alive 缓存组件;
防抖、节流;
组件销毁的时候会断开所有与实例联系,但是除了addEventListener,所以当一个组件销毁的时候需要手动去removeEventListener
网络层面:
-
预渲染
-
服务端渲染 SSR 参考文章:juejin.cn/post/722543…
-
使用 cdn 加载第三方模块
-
缓存:将长时间不会改变的第三方类库或者静态资源设置为强缓存,将 max-age 设置为一个非常长的时间,再将访问路径加上哈希达到哈希值变了以后保证获取到最新资源,好的缓存策略有助于减轻服务器的压力,并且显著的提升用户的体验
-
gzip:开启 gzip 压缩,通常开启 gzip 压缩能够有效的缩小传输资源的大小。
http2:如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同域名的 tcp 连接数量是有限制的(chrome 为 6 个)超过规定数量的 tcp 连接,则必须要等到之前的请求收到响应后才能继续发送,而 http2 则可以在多个 tcp 连接中并发多个请求没有限制,在一些网络较差的环境开启 http2 性能提升尤为明显。
实际使用场景:
vue-cli 工程相关
出现频率: 50%
掌握难度:80分
作用:
参考答案:
- 构建 vue-cli 工程都用到了哪些技术?他们的作用分别是什么?
- vue-cli 工程常用的 npm 命令有哪些?
构建 vue-cli 工程都用到了哪些技术?他们的作用分别是什么?
- vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统。
- vue-router:vue 官方推荐使用的路由框架。
- vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
- axios(或者 fetch、ajax):用于发起 GET 、或 POST 等 http请求,基于 Promise 设计。
- vux等:一个专为vue设计的移动端UI组件库。
- webpack:模块加载和vue-cli工程打包器。
- eslint:代码规范工具
vue-cli 工程常用的 npm 命令有哪些?
- 下载 node_modules 资源包的命令:npm install
- 启动 vue-cli 开发环境的 npm命令:npm run dev
- vue-cli 生成 生产环境部署资源 的 npm命令:npm run build
- 用于查看 vue-cli 生产环境部署资源文件大小的 npm命令:npm run build --report
实际使用场景:
webpack常用的几个对象及解释
出现频率: 70%
掌握难度:80分
作用:
参考答案:
entry 入口文件
output 输出文件
一般配合node的path模块使用
// 入口文件
entry:"./src/index.js",
output:{
// 输出文件名称
filename:"bundle.js",
// 输出的路径(绝对路径)
path:path.resolve(__dirname,"dist") //利用node模块的path 绝对路径
},
// 设置模式
mode:"development"
mode 设计模式
module(loader)
里面有一个rules数组对某种格式的文件进行转换处理(转换规则)
use数组解析顺序是从下到上逆序执行的
module:{
// 对某种格式的文件进行转换处理(转换规则)
rules:[
{
// 用到正则表达式
test:/\.css$/, //后缀名为css格式的文件
use:[
// use数组解析顺序是从下到上逆序执行的
// 先用css-loader 再用style-loader
// 将js的样式内容插入到style标签里
"style-loader",
// 将css文件转换为js
"css-loader"
]
}
]
}
// -----------------------------------------------------vue的
module.exports={
module:{
rules:[
{
test: /\.vue$/,
use:["vue-loader"]
}
]
}
}
plugin
插件配置
const uglifyJsPlugin = reqiure('uglifyjs-webpack-plugin')
module.exports={
plugin:[
new uglifyJsPlugin() //丑化
]
}
devServer
热更新
devServer:{
// 项目构建路径
contentBase:path.resolve(__dirname,"dist"),
// 启动gzip亚索
compress:true,
// 设置端口号
port:2020,
// 自动打开浏览器:否
open:false,
//页面实时刷新(实时监听)
inline:true
}
resolve
配置路径规则
alias 别名
module.exports= {
resolve:{
//如果导入的时候不想写后缀名可以在resolve中定义extensions
extensions:['.js','.css','.vue']
//alias:别名
alias:{
//导入以vue结尾的文件时,会去寻找vue.esm.js文件
'vue$':"vue/dist/vue.esm.js"
}
}
}
babel(ES6转ES5)
下载插件babel-loader,在module(loader)中配置
loader和plugin的区别是什么?⭐⭐⭐
https://blog.csdn.net/weixin_41351184/article/details/101008831?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170900354716800213045370%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=170900354716800213045370&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-101008831-null-null.142^v99^pc_search_result_base2&utm_term=loader%E5%92%8Cplugin%E3%80%81babel&spm=1018.2226.3001.4187
webpack只能打包js模块,其他模块的打包需要借助loader。loader可以处理各种各样文件类型。将es6转义打包为es5使用bable-loader,css-loader打包为css文件。
loader
loader是用来解析非js文件的,因为Webpack原生只能解析js文件,如果想把那些文件一并打包的话,就需要用到loader,loader使webpack具有了解析非js文件的能力
plugin
用来给webpack扩展功能的,可以加载许多插件
实际使用场景:
****
出现频率:
掌握难度:
作用:
参考答案:
实际使用场景: