前言
本文整理了前端高频vue考点,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。
详细版vue笔记 segmentfault.com/a/119000001…
1.vue优点
-
轻量级框架:只关注视图层,大小只有几十kb
-
简单易学:国人开发,中文文档,易于理解和学习;
-
双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
-
组件化:保留了react的优点,实现了html的封装和复用,利于构建单页面应用;
-
视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
-
虚拟DOM:dom操作非常耗费性能,改用VDom+diff
-
运行速度更快: 相比较与react而言,同样是操作虚拟dom,vue性能更好。
缺点:单页面不利于seo,首次加载主页面时间长(ssr解决)
2.vue是渐进式框架的理解
就是你可以只用我的一部分,而不是用了我这一点就必须用我的所有部分(例如:你想用component就用,不用也行,你想用vuex就用,不用也可以)
3. Vue跟React的异同点
相同点:
- 1.都使用了虚拟dom
- 2.组件化开发
- 3.都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
- 4.都支持服务端渲染
区别 | vue | react |
---|---|---|
监听数据变化 | Vue 通过defineproperty对数据劫持,监听数据变化。Vue 强调可变数据 | React 不精确监听数据变化,因为而React更强调数据的不可变 |
生命周期不同 | ||
数据状态管理 | vuex | redux |
dom操作很方便 ,for指令 if指令 | 代码更加美观 | |
数据流的不同 | 组件和DOM之间是双向数据流(v-model),父子之间是单向数据流(props) | 父子之间(props),组件和dom之间(state)单向数据流 |
4. MVVM和MVC区别
MVC
- Model(模型):负责从数据库中取数据
- View(视图):负责展示数据的界面
- Controller(控制器):用户交互的地方,例如点击事件等等
- 思想:Controller将Model的数据展示在View上
MVVM
- VM(视图模型):实现数据的双向绑定,(1)将后端传递的数据转化成所看到的页面。(2)将所看到的页面转化成后端的数据
- 思想:Vue数据驱动,即数据改变后对应的view层显示自动改变
- vue不是严格符合MVVM,因为MVVM规定Model和View不能直接通信,而Vue的
ref
可以做到这点
7. 为什么data是个函数并且返回一个对象呢?
data
之所以只一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行data函数
并返回新的数据对象,这样可以避免多处调用之间的数据污染
。
8. 使用过哪些Vue的修饰符呢?
可以看这篇文章「百毒不侵」面试官最喜欢问的13种Vue修饰符
9. 使用过哪些Vue的内部指令呢?
- v-once一旦绑定,数据就不会改变
<template>
<div>
<h3>v-once</h3>
<input type="text" v-model="voncetext" /> 输入 测试v-once123
<p>{{voncetext}}</p> 测试v-once123
<p v-once>{{voncetext}}</p> 测试v-once
</div>
</template>
<script>
export default({
name:"voncetest",
data(){
return{
voncetext:"测试v-once"
}
}
})
</script>
10. 组件之间的传值方式有哪些(通信)?
- 父组件传值给子组件,子组件使用
props
进行接收 - 子组件传值给父组件,子组件使用
$emit+事件
对父组件进行传值 - 组件中可以使用
$parent
和$children
获取到父组件实例和子组件实例,进而获取数据
//父组件
mounted(){
console.log(this.$children)
//可以拿到 一级子组件的属性和方法
//所以就可以直接改变 data,或者调用 methods 方法
}
//子组件
mounted(){
console.log(this.$parent) //可以拿到 parent 的属性和方法
}
- 使用
$attrs
和$listeners
,在对一些组件进行二次封装时可以方便传值,例如A->B->C
1.attrs 场景:如果父传子有很多值,那么在子组件需要定义多个 props 解决:attrs场景:如果父传子有很多值,那么在子组件需要定义多个props解决
相对应的如果子组件定义了 props,打印的值就是剔除定义的属性
props: {
width: {
type: String,
default: ''
}
},
mounted() {
console.log(this.$attrs) //{title: "这是标题", height: "80", imgUrl: "imgUrl"}
},
2.listeners场景:子组件需要调用父组件的方法解决:父组件的方法可以通过v−on="listeners" 传入内部组件——在创建更高层次的组件时非常有用
// 父组件
<home @change="change"/>
// 子组件
mounted() {
console.log(this.$listeners) //即可拿到 change 事件
}
- 使用
$refs
获取组件实例,进而获取数据
// 父组件
<home ref="home"/>
mounted(){
console.log(this.$refs.home) //即可拿到子组件的实例,就可以直接操作 data 和 methods
}
- 使用
Vuex
进行状态管理store
state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问.可以在这里设置默认的初始状态
getter:允许组件从 Store 中获取数据,可认为是 store 的计算属性,可通过this.$store.getter 或
mapGetters访问(computed)
mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用。是唯一更改 store 中状态的方法
action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions访问
modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
- 使用
eventBus
进行跨组件触发事件,进而传递数据
@method $on 事件订阅, 监听当前实例上的自定义事件。必须在事件广播($emit)前注册;
@method $off 取消事件订阅,移除自定义事件监听器。必须跟事件订阅($on)成对出现
@method $emit 事件广播, 触发当前实例上的事件
@method $once 事件订阅, 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
//event-bus.js创建事件总线并将其导出,方便调用
import Vue from 'vue'
export const $EventBus = new Vue()
// main.js全局事件总线
Vue.prototype.$EventBus = new Vue()
如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…))
A组件
beforeDestroy () {
//事件广播
this.$EventBus.$emit('testing', color)
}
B组件
created () {
//事件订阅
this.$EventBus.$on('testing', (res) => { console.log(res) })
},
beforeDestroy () {
this.$EventBus.$off('testing')
}
- $root
// 父组件
mounted(){
console.log(this.$root) //获取根实例,最后所有组件都是挂载到根实例上
console.log(this.$root.$children[0]) //获取根实例的一级子组件
console.log(this.$root.$children[0].$children[0]) //获取根实例的二级子组件
}
- 修饰符.sync
// 父组件
<home :title.sync="title" />
//编译时会被扩展为
<home :title="title" @update:title="val => title = val"/>
// 子组件
// 所以子组件可以通过$emit 触发 update 方法改变
mounted(){
this.$emit("update:title", '这是新的title')
}
- 使用
provide
和inject
,官方建议我们不要用这个,我在看ElementUI
源码时发现大量使用
主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中;
- 使用浏览器本地缓存,例如
localStorage
12. 如何设置动态class,动态style?
- 动态class对象:
<div :class="{ 'is-active': true, 'red': isRed }"></div>
- 动态class数组:
<div :class="['is-active', isRed ? 'red' : '' ]"></div>
- 动态style对象:
<div :style="{ color: textColor, fontSize: '18px' }"></div>
- 动态style数组:
<div :style="[{ color: textColor, fontSize: '18px' }, { fontWeight: '300' }]"></div>
13. v-if和v-show有何区别,v-for和v-if不能一块使用
- 1.
v-if
是通过控制dom元素的删除和生成来实现显隐,显隐决定了组件的生成和销毁 - 2.
v-show
是通过控制dom元素的css样式来实现显隐,不会销毁 - 3.频繁或者大数量显隐使用
v-show
,否则使用v-if
v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。
- 原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
vue高阶面试题
1.<keep-alive></keep-alive>
的作用是什么?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件 保留状态,或避免重新渲染。
keep-alive给我们提供了三个属性:
- include:接受值为字符串或者正则表达式,只有匹配的才被缓存
- exclude:接受值为字符串或者正则表达式,被匹配的路由将不会被缓存;
- max:缓存组件最大值
对组件的缓存:
//只缓存组件name为a或者b的组件
<keep-alive include="a,b">
<component :is="currentView"/>
</keep-alive>
//组件名为c的组件不缓存
<keep-alive exclude="c">
<component :is="currentView"/>
</keep-alive>
// 如果同时使用include,exclude,那么exclude优先于include, 下面的例子也就是只缓存a组件
<keep-alive include="a,b" exclude="b">
<component :is="currentView"/>
</keep-alive>
// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
<keep-alive exclude="c" max="5">
<component :is="currentView"/>
</keep-alive>
对路由组件的缓存:
使用keep-alive可以将所有路径匹配到的路由组件都缓存起来
,包括路由组件里面的组件。
<keep-alive> //若有些组件不需要缓存,跟上面方法一样,添加exclude属性
<router-view />
</keep-alive>
也可以实现对部分路由的缓存
- 通过在meta属性中添加
keepAlive
属性实现
{
path: '/login',
name: 'login',
meta: {
keepAlive: false //设置不缓存
}
},
{
path: '/argu',
name: 'argu',
component: () => import('@/views/argu.vue'),
meta: {
keepAlive: true //设置缓存
}
},
- 在app.vue中设置,通过$route来获取meta中的keepAlive属性进而判断是否缓存。
<template>
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
</template>
- 当组件被包裹在keep-alive组件中时,会多出两个钩子函数:activated()和deactivated()
-
activated(): 这个钩子函数在组件第一次渲染时候被调用,之后在每次缓存组件被激活时候调用。
-
deactivated():组件被停用(离开路由)的时候调用。
-
使用了keep-alive的组件是不会触发beforeDestroy()和destroyed()这两个钩子函数的,因为组件并没有被销毁,而是被缓存了起来。
-
从进入一个缓存组件钩子函数的调用顺序很重要:
- 进入一个缓存组件:deactivated(如果是从另一个缓存组件中进入则有这个钩子,否则无)——>mounted——>activated 进入缓存组件——> 执行beforeRouteLeave 回调。
- 离开一个缓存组件:beforeRouteLeave ——> 全局前置守卫beforeEach——>deactivated 离开当前组件 ——> activated(如果进入的还是一个缓存组件则有,否则无)
2.如何获取dom?
在组件内设置ref="domName" ------ 用法:this.$refs.domName
3.v-model的使用。
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
- v-bind绑定一个value属性;
- v-on指令给当前元素绑定input事件。
4.请说出vue-cli项目中src目录每个文件夹和文件的用法?
- assets文件夹是放项目中template需要的样式文件js文件等,走打包,减少体积
- components是放组件;
- router是定义路由相关的配置;
- app.vue是一个应用主组件;
- main.js是入口文件。
static/
目录下的文件并不会被 Webpack 处理,在打包时,它们会直接被复制到最终目录(默认是dist/static
)。
- 存放项目中引入的第三方的资源文件如iconfoont.css等文件
- 任何放在
static/
中文件需要以绝对路径的形式引用:/static/[filename]
。这是通过在config.js
文件中的build.assetsPublicPath
和build.assetsSubDirectory
连接来确定的。
5.分别简述computed和watch和filter的使用场景
computed:
当一个属性受多个属性影响的时候就需要用到computed,适用于复杂的数据转换、统计等场景
- 最典型的栗子: 购物车商品结算的时候
- computed 属性具有缓存能力,如下:index由0变成1,那么会触发视图更新,这时候methods会重新执行一次,而computed不会,因为computed依赖的两个变量num和price都没变
<div>
<div>{{howMuch1()}}</div>
<div>{{howMuch2()}}</div>
<div>{{index}}</div>
</div>
data: () {
return {
index: 0
}
}
methods: {
howMuch1() {
return this.num + this.price
}
}
computed() {
howMuch2() {
return this.num + this.price
}
}
watch:
当一条数据影响多条数据的时候就需要用watch。栗子:搜索数据
- 当我们监听一个基本数据类型时:
watch: {
value () {
// do something
}
}
- 当我们监听一个引用数据类型时:
docData: {//对象
'doc_id': 1,
'tpl_data': 'abc'
}
watch: {
docData: {
handler () { // 执行回调
// do something
},
deep: true, // 是否进行深度监听 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变
immediate: true // 是否初始组件时就执行handler函数
}
}
filter:
filter 无法缓存,调用频率高,适用于格式化输出场景,比如日期格式化
-
全局过滤器
-
通过插值表达式使用单个过滤器
{{msg | upper}} -
通过插值表达式叠加使用多个过滤器
{{msg | upper | lower}}进行了两次的数据格式化,先是变成大小,再变成小写,最后呈现小写的结果 -
// main.js import Vue from "vue" import App from "./App.vue" Vue.filter('upper',function(val){ return val.charAt(0).toUpperCase()+val.slice(1) }) Vue.filter('lower',function(val){ return val.charAt(0).toLowerCase()+val.slice(1) }) new Vue({ el:"#app", render(h){ return h(App); } })
-
通过使用过滤器来格式化属性绑定的值
<div v-bind:id="id | formatId">Test</div>
-
-
局部过滤器
<template>
<div>
<input type="text" v-model="msg">
<div>{{msg|upper}}</div>
<button @click="handle(test)">测试methods方法调用filter</button>
</div>
</template>
<script>
export default {
data(){
return {
msg:'hzy',
test:'hello'
}
},
filters:{
upper(val){
return val.charAt(0).toUpperCase()+val.slice(1)
}
},
methods:{
handle(val){
//使用this.$options.filters[]方式获取本地过滤器
let localFilter = this.$options.filters['upper'];
//调用本地过滤器格式化
localFilter(val);
}
}
}
</script>
6.v-on可以监听多个方法吗?
可以,栗子:<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }">
。
7. 不需要响应式的数据应该怎么处理?
在我们的Vue开发中,会有一些数据,从始至终都未曾改变过
,这种死数据
,既然不改变
,那也就不需要对他做响应式处理
了,不然只会做一些无用功消耗性能,比如一些写死的下拉框,写死的表格数据,这些数据量大的死数据
,如果都进行响应式处理,那会消耗大量性能。
// 方法一:将数据定义在data之外
data () {
this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
return {}
}
// 方法二:Object.freeze()
data () {
return {
list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
}
}
8. 父子组件生命周期顺序
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
9. 对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?
-
原因:
Object.defineProperty
没有对对象的新属性进行属性劫持 -
对象新属性无法更新视图:使用
Vue.$set(obj, key, value)
,组件中this.$set(obj, key, value)
-
删除对象属性无法更新视图:使用
Vue.$delete(obj, key)
,组件中this.$delete(obj, key)
-
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
-
Vue.delete 直接删除了数组 改变了数组的键值。
-
arr[0,1,2,3],使用delete删除第二个元素,剩下的元素键值和原数组一样。arr[0]=0;arr[2]=2;arr[3]=3;此处a[1]是empty
-
使用vue.delete删除会自动往前填充 为:arr[0]=0;arr[1]=2;arr[2]=3
-
10. 自定义指令
建议看这篇文章8个非常实用的Vue自定义指令
11. 插槽的使用以及原理?
建议看我这篇文章「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗
A.匿名插槽(也叫默认插槽): 没有命名,有且只有一个;
// 父组件调用子组件
<todo-list>
<template v-slot:default>
任意内容
<p>我是匿名插槽 </p>
</template>
</todo-list>
// 子组件todoList.vue
<slot>我是默认值</slot>
//v-slot:default写上感觉和具名写法比较统一,容易理解,也可以不用写
B.具名插槽: 相对匿名插槽组件slot标签带name命名的;
// 父组件,调用的子组件
<todo-list>
<template v-slot:todo>
任意内容
<p>我是匿名插槽 </p>
</template>
</todo-list>
//子组件
<slot name="todo">我是默认值</slot>
C.作用域插槽: 父组件可以拿到子组件的数据
// 父组件
<todo-list>
<template v-slot:todo="slotProps" >
{{slotProps.user.firstName}}
</template>
</todo-list>
//slotProps 可以随意命名
//slotProps 接取的是子组件标签slot上属性数据的集合所有v-bind:user="user"
// 子组件
<slot name="todo" :user="user" :test="test">
{{ user.lastName }}
</slot>
data() {
return {
user:{
lastName:"Zhang",
firstName:"yue"
},
test:[1,2,3,4]
}
},
// {{ user.lastName }}是默认数据 v-slot:todo 当父页面没有(="slotProps")
12. 为什么不建议用index做key,为什么不建议用随机数做key?
举个例子:
<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
{ name: '小明', id: '123' },
{ name: '小红', id: '124' },
{ name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
新旧对比
<div key="0">小明</div> <div key="0">小林</div>
<div key="1">小红</div> <div key="1">小明</div>
<div key="2">小花</div> <div key="2">小红</div>
<div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
现在我们使用id来做key,渲染为
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' }),渲染为
<div key="122">小林</div>
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
新旧对比
<div key="122">小林</div>
<div key="123">小明</div> <div key="123">小明</div>
<div key="124">小红</div> <div key="124">小红</div>
<div key="125">小花</div> <div key="125">小花</div>
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果
用index
和用随机数
都是同理,随机数
每次都在变,做不到专一性,也很消耗性能
13. nextTick的用处
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。
<div ref="testDiv">{{name}}</div>
name: '小林'
this.name = '林三心' //修改变量
console.log(this.$refs.testDiv.innerHTML) // 这里是啥呢
答案是“小林”,前面说了,Vue是异步更新
,所以数据一更新,视图却还没更新,所以拿到的还是上一次的旧视图数据(dom更新前)
this.name = '林三心'
this.$nextTick(() => {
console.log(this.$refs.testDiv.innerHTML) // 林三心
})
14. Vue的SSR是什么?有什么好处?
SSR
就是服务端渲染- 基于
nodejs
服务环境开发,所有html
代码在服务端渲染 - 将渲染后的数据(html)返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
- SSR优化了首次主页面加载、防止爬虫
15.Vue中双向数据绑定是如何实现的?
详细请看这里:Vue中的观察者与发布订阅
要保持数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
- 数据监听器observer:采用数据劫持,通过Object.defineProperty()的getter函数将各个属性用Watcher类来监听,每个Watcher对应一个属性,将所有的Watcher类添加到Dep类(一个容器,用于消息管理)。当某个属性改变时,触发setter函数,对属性值进行修改,并重新将新的属性值添加Watcher类添加到Dep类中,然后dep.notify()执行compiler绑定的更新函数
- 指令解析器compiler:对每一个元素节点的指令进行解析,根据指令模板替换数据,以及绑定相应的更新函数
- watcher:作为observer和compiler的桥梁,能够订阅并收到每个属性的变动通知,执行指令绑定的相应回调函数,从而更新视图
class Dep{
constructor(){
this.subs = [] //存放所有的 watcher
}
//订阅
addSub(watcher){ //添加 watcher
this.subs.push(watcher)
}
//发布
notify(){
this.subs.forEach(watcher=>watcher.updata());
}
}
class Observer{
constructor(data) {
this.observer(data);
}
observer(data){
//如果是对象才观察
if(data && typeof data === 'object'){
for (let key in data) { //循环 data 中的所有子项
this.defineReactive(data,key,data[key]);
}
}
}
//实现数据劫持
defineReactive(obj,key,value){
this.observer(value); //如果传进来的参数是对象,就回调一下这个函数,就是一个递归函数
let dep = new Dep(); //给每一个属性都添加一个具有发布和订阅的功能
Object.defineProperty(obj,key,{
get(){
//创建watcher时,会获取到对应的内容 并且把watcher放到了全局上
Dep.target && dep.addSub(Dep.target);
return value;
},
set: (newVal)=>{
if(value !== newVal){
this.observer(newVal); //给设置的新值也加上 get set 方法
value = newVal;
dep.notify(); //执行观察者更新时的函数
}
}
})
}
}
16.单页面应用和多页面应用区别及优缺点
单页面应用(SPA),通俗说就是指只有一个主页面的应用
- 浏览器一开始要加载所有必须的 html, js, css,初次加载耗时多。
- 所有的页面内容都包含在这个所谓的主页面中。
- 单页面的页面跳转,由路由程序动态载入,仅刷新局部资源。
- 多应用于pc端。
单页面首次加载主页面慢:可通过安装动态懒加载所需插件;使用CDN资源。
单页面应用(SinglePage Web Application,SPA) | 多页面应用(MultiPage) | |
---|---|---|
组成 | 一个主页面 | 多个完整页面构成 |
资源共用(css,js) | 共用,浏览器一开始要加载所有必须的 html, js, css | 不共用,每个页面都有自己的css、js |
刷新方式 | 页面局部刷新或更改 | 整页刷新 |
url 模式 | a.com/#/pageone a.com/#/pagetwo | a.com/pageone.html a.com/pagetwo.html |
转场动画 | 容易实现、页面片段间切换快,用户体验良好 | 无法实现,页面切换加载缓慢,流畅度不够,用户体验比较差 |
数据传递 | 容易,组件之间传值 | 依赖 url传参、或者cookie 、localStorage等 |
搜索引擎优化(SEO) | 需要单独方案、实现较为困难、不利于SEO检索 可利用服务器端渲染(SSR)优化 | 实现方法简易 |
试用范围 | 高要求的体验度、追求界面流畅的应用 | 适用于追求高度支持搜索引擎的应用 |
开发、维护成本 | 开发成本较高,常需借助专业的框架,维护相对容易 | 开发成本较低 ,但页面重复代码多,维护相对复杂 |
17.Vue-router跳转和location.href有什么区别
使用location.href='/url'来跳转,简单方便,但是刷新了页面;
使用location.href实现页面div块的快速定位
location.href='#divClass'//<div id = "divClass"></div>,通过事件直接跳转到该div
location.href可直接获取当前路径
parent.location.href跳转至上一层页面
top.location.href跳转至最外层页面
使用history.pushState('/url'),无刷新页面,静态跳转;需要服务端配合
18.请说下封装 vue 组件的过程?
- 建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。
- 准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
- 准备好组件的数据输出。即根据组件逻辑,做好暴露出来的方法(this.$emit)。
19.vue初始化页面闪动问题
- 加载时遇到
{{value.name}}
闪烁,是因为你在渲染时是这么写的<p>{{value.name}}</p>
- 加载时遇到一个空的盒子里边什么也没有,是因为你在渲染时是这么写的
<p v-html="value.name"></p>
解决办法
v-cloak并不需要添加到每个标签,只要在el挂载的标签上添加就可以,这是最简单有效的办法
//app.vue
<div class="#app" v-cloak>
<p>{{value.name}}</p>
</div>
//css
[v-cloak] {
display: none;
}
//这样就可以防止页面闪烁了。
但是有的时候会不起作用,可能的原因有二:
- v-cloak的display属性被层级更高的给覆盖掉了,所以要提高层级
[v-cloak] {
display: none !important;
}
- 样式放在了@import引入的css文件中(传统的开发方式) v-cloak的这个样式放在@import 引入的css文件中不起作用,可以放在link引入的css文件里或者内联样式中
冷门的知识点
1. 如果子组件改变props里的数据会发生什么
- 改变的props数据是基本类型
如果修改的是基本类型,则会报错
props: {
num: Number,
}
created() {
this.num = 999
}
- 改变的props数据是引用类型
props: {
item: {
default: () => {},
}
}
created() {
// 不报错,并且父级数据会跟着变
this.item.name = 'sanxin';
// 会报错,跟基础类型报错一样
this.item = 'sss'
},
复制代码
2. props怎么自定义验证
props: {
num: {
default: 1,
validator: function (value) {
// 返回值为true则验证不通过,报错
return [
1, 2, 3, 4, 5
].indexOf(value) !== -1
}
}
}
3. watch监听一个对象时,如何排除某些属性的监听
下面代码是,params发生改变就重新请求数据,无论是a,b,c,d属性改变
data() {
return {
params: {
a: 1,
b: 2,
c: 3,
d: 4
},
};
},
watch: {
params: {
deep: true,
handler() {
console.log('属性变化执行的内容')
},
},
}
但是如果我只想要a,b改变时重新请求,c,d改变时不重新请求呢?
mounted() {
const handler = function(){
console.log('属性变化执行的内容')
}
Object.keys(this.params)
.filter(item1=> !["c","d"].includes(item1))//不包含c,d属性
.forEach(item2=> {
this.$watch(vm => vm.params[item2], handler, {
deep: true
});
});
}
5. 审查元素时发现data-v-xxxxx,这是什么
这是在标记vue文件中css时使用scoped标记产生的,因为要保证各文件中的css不相互影响,给每个component都做了唯一的标记,所以每引入一个component就会出现一个新的'data-v-xxx'标记
6. computed如何实现传参?
// html
<div>{{ total(3) }}
// js
computed: {
total() {
return function(n) {
return n * this.num
}
},
}
7. vue的hook的使用
- 同一组件中使用
这是我们常用的使用定时器的方式
export default{
data(){
timer:null
},
mounted(){
this.timer = setInterval(()=>{
//具体执行内容
console.log('1');
},1000);
}
beforeDestory(){
clearInterval(this.timer);
this.timer = null;
}
}
缺点:
- clearInterval 后没有清空 timer 为 null。
- 开启定时器和清除定时器的代码分散开在两个地方,有损可读性/维护性,用尤大大的话说,这使得我们比较难于程序化地清理我们建立的东西。
- timer 被定义在 data 里,实际上 timer 不需要什么响应式操作,定义在 data 里是没必要的,反而造成性能浪费。
使用 hook 监听 beforeDestroy 生命周期,这样 timer 只需被定义在生命周期里
export default{
methods:{
fn(){
const timer = setInterval(()=>{
//具体执行代码
console.log('1');
},1000);
this.$once('hook:beforeDestroy',()=>{
clearInterval(timer);
timer = null;
})
}
}
}
问题:在后台系统中,我们常常会设置页面缓存,而当路由被 keep-alive 缓存时是不走 beforeDestroy 生命周期的。借助 activated 和 deactivated 这两个生钩子
export default {
data() {
return {
}
},
mounted() {
let timer = setInterval(() => {
console.log('setInterval')
}, 2000)
this.$on('hook:activated', () => {
if (timer === null) { // 避免重复开启定时器
timer = setInterval(() => {
console.log('setInterval')
}, 2000)
}
})
this.$on('hook:deactivated', () => {
clearInterval(timer)
timer = null
})
}
}
由于缓存原因,所以需要用 $on 而不是 $once,不然执行一次后就不会再触发了。
- 7.2 父子组件使用
如果子组件需要在mounted时触发父组件的某一个函数,平时都会这么写:
//父组件
<rl-child @childMounted="childMountedHandle"/>
method () {
childMountedHandle() {
// do something...
}
},
// 子组件
mounted () {
this.$emit('childMounted')
},
使用hook的话可以更方便:
//父组件
<rl-child @hook:mounted="childMountedHandle"/>
method () {
childMountedHandle() {
// do something...
}
},
8. provide和inject是响应式的吗
作用:用于父组件向子孙组件传递数据
使用方法:provide在父组件中返回要传给下级的数据,inject在需要使用这个数据的子辈组件或者孙辈等下级组件中注入数据。
// 祖先组件
provide(){
return {
// keyName: { name: this.name }, // value 是对象才能实现响应式,也就是引用类型
keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]
// keyName: 'test' value 如果是基本类型,就无法实现响应式
}
},
data(){
return {
name:'张三'
}
},
methods: {
changeValue(){
this.name = '改变后的名字-李四'
}
}
// 后代组件
inject:['keyName']
create(){
console.log(this.keyName) // 改变后的名字-李四
}
9.Vue的el属性和$mount优先级?
比如下面这种情况,Vue会渲染到哪个节点上
new Vue({
router,
store,
el: '#app',
render: h => h(App)
}).$mount('#ggg')
这是官方的一张图,可以看出
el
和$mount
同时存在时,el优先级
>$mount
10. 动态指令和参数使用过吗?
<template>
...
<aButton @[someEvent]="handleSomeEvent()" :[someProps]="1000" />...
</template>
<script>
...
data(){
return{
...
someEvent: someCondition ? "click" : "dbclick",
someProps: someCondition ? "num" : "price"
}
},
methods: {
handleSomeEvent(){
// handle some event
}
}
</script>
11. 相同的路由组件如何重新渲染?
开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染
const routes = [
{
path: "/a",
component: MyComponent
},
{
path: "/b",
component: MyComponent
},
];
如果依然想重新渲染,怎么办呢?可以使用
key
<template>
<router-view :key="$route.path"></router-view>
</template>
12. 如何将获取data中某一个数据的初始状态?
在开发中,有时候需要拿初始状态去计算
可以通过this.$options.data().xxx来获取初始值
data() {
return {
num: 10
},
mounted() {
this.num = 1000
},
methods: {
howMuch() {
// 计算出num增加了多少,那就是1000 - 初始值
console.log(1000 - this.$options.data().num)
}
}
总结
觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀
后续更新前端其它知识总结,请关注我,整理好,分享给你们,我们一起学前端