1.v-once的作用和用法?
v-once这个指令不需要任何表达式,它的作用就是定义它的元素或组件只会渲染一次,包括元素或者组件的所有字节点。首次渲染后,不再随着数据的改变而重新渲染。也就是说使用v-once,那么该块都将被视为静态内容。
<!--v-once示例-->
<template>
<div>
<div v-once>{{count}}</div>
<button v-on:click="addCount">加个一吧</button>
</div>
</template>
<script>
export default {
name: "Vonce",
data() {
return {
count: 10
}
},
methods: {
addCount: function () {
this.count += 1;
console.log('this.count:'+this.count);
}
}
}
</script>
点击按钮,控制台会依次加一,但是页面还是10,不会再次渲染
这个指令在实际的业务场景中是很少使用的,只有我们想进一步去优化性能的时候,可能会用到
2.Vue中在子组件能不能修改props数据
- 父组件通过标签属性的形式向子组件传递数据
- 传递的数据类型: 基本数据类型,引用数据类型
可以修改,会有警告
追问:Vue子组件修改props的数据会不会影响父组件的状态值
- 如果传递的基本数据类型,不会影响
- 如果传递的引用数据类型,会影响
3.vue单页面应用刷新网页后vuex的state数据丢失怎么解决
产生原因
- Vuex数据保存在运行内存中,vue实例初始化的时候为其分配内存
- 当刷新页面的时候重新初始化Vue实例,所以重新为Vuex分配内存导致之前保存的数据丢失
如何解决 - Vuex的数据都是每次组件加载时候动态请求获取数据保存
a) 优点: 保证数据不会丢失
b) 缺点: 性能差,因为网络问题可能有网络延迟 - 将Vuex中的数据每次同步更新保存到sessionStorage中
a) 优点: 每次页面刷新后从sessionStorage中获取保存的数据,不会丢失
b) 缺点: state中的数据是动态的,就需要一直要同步到sessionStorage中,性能差 - 在页面刷新之前获取Vuex的数据,将数据保存在sessionStorage中,页面加载后从sessionStorage中获取
a) 优点: 减少动态更新sessionStorage的次数,性能好
b) 重点: 给window绑定beforeupload事件监听
beforeunload这个事件在页面刷新时先触发的。那这个事件应该在哪里触发呢?我们总不能每个页面都监听这个事件,所以我选择放在app.vue这个入口组件中,这样就可以保证每次刷新页面都可以触发。
created () {
//在页面加载时读取sessionStorage里的状态信息
if (sessionStorage.getItem("store") ) {
this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem("store"))))
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload",()=>{
sessionStorage.setItem("store",JSON.stringify(this.$store.state))
})
}
4.常用的事件指令有哪些?
v-if,v-else-if,v-else、v-show、v-model、v-bind、v-html、v-text、v-for、v-on
5. r o u t e 和 route和 route和router是什么?区别是什么?
- 而route是正在跳转的这个路由的局部对象,可以获取这个正在跳转的路由的name,path,params,query,hash等等
router其实是VueRouter的一个实例,所以它是一个全局对象,包括了路由的跳转方法(push,replace),钩子函数等
5.VUE中响应式原理?
Vue 的响应式原理是核心是通过 Object.defindeProperty中的 get 和 set 方法,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发,重新渲染当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并进行新旧DOM对比,然后记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上
6.项目做过哪些优化?
1.首先最基本的,CSS样式表放在页面头部Head内且link链式引入,javascript放在底部body结束标签前避免阻塞。
2. 图片懒加载
3. 减少DOM元素数量,减少DOM的操作:
3.1减少 DOM 元素数量,合理利用:after、:before等伪类,避免页面过深的层级嵌套;
3.2优化javascript性能,减少DOM操作次数(或集中操作),能有效规避页面重绘/重排; 防抖和节流
4.静态资源CDN分发
5. 减少http请求次数:
5.1合并JS和CSS文件,利用项目构建工具webpack,做到JS或者CSS文件的合并,减少http请求的开销
5.2.充分利用浏览器缓存,强缓存:不会发起HTTP请求,直接从浏览器缓存中读取文件。协商缓存:向服务器发起HTTP请求,如果资源文件并未更新,response响应码即为304,随后从浏览器缓存中下载该文件,并不会从服务器下载。
5.3.利用雪碧图,使用字体图标
6.模块懒加载
7.使用keep-alive
Vue 基础
组件间通信的方式
根据通信的2个组件间的关系来选择一种通信方式
父子
props
vue自定义事件
v-model
.sync
$ref, $children与$parent
插槽 ==> 作用域插槽
祖孙
$attrs与$listeners
provide与inject
兄弟或其它/任意
全局事件总线
Vuex
方式1: props
1). 实现父向子通信: 属性值是非函数
2). 实现子向父通信: 属性值是函数
应用: 最基本, 用得最多的方式
方式2: vue自定义事件
1). 用来实现子组件向父组件通信
2). 相关语法:
父组件中绑定自定义事件监听:
<Child @eventName="callback">
子组件中分发事件
this.$emit('eventName', data)
应用: elment-ui的组件的事件监听语法都用的是自定义事件
我们项目中的组件也用了不少自定义事件
方式3: 全局事件总线 ===> 消息订阅与发布
1). 实现任意组件间通信
2). 编码:
将入口js中的vm作为全局事件总线对象:
beforeCreate() {
Vue.prototype.$bus = this
}
分发事件/传递数据的组件: this.$bus.$emit('eventName', data)
处理事件/接收数据的组件: this.$bus.$on('eventName', (data) => {})
应用: 前台项目中使用全局事件总线
方式4: v-model
1). 实现父子之间相互通信/同步
2). 组件标签上的v-model的本质: 动态value属性与自定义input监听来接收子组件分发的数据更新父组件数据
父组件:
<CustomInput v-model="name"/>
<!-- 等价于 -->
<CustomInput :value="name" @input="name=$event"/>
子组件:
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
props: ['value']
应用: element-ui中的表单项相关组件都用了v-model: Input / Select / Checkbox / Radio
方式5: .sync
1). 实现父子之间相互通信/同步(在原本父向子的基础上增加子向父)
2). 组件标签的属性上使用.sync的本质: 通过事件监听来接收子组件分发过来的数据并更新父组件的数据
父组件:
<child :money.sync="total"/>
<!-- 等价于 -->
<Child :money="total" @update:money="total=$event"/>
data () {
return {
total: 1000
}
},
子组件:
<button @click="$emit('update:money', money-100)">花钱</button>
props: ['money']
应用:
element-ui在有显示隐藏的组件上: Dialog / Drawer
方式6: a t t r s 与 attrs与 attrs与listeners
1). $attrs
实现当前组件的父组件向当前组件的子组件通信
它是包含所有父组件传入的标签属性(排除props声明, class与style的属性)的对象
使用: 通过 v-bind="$attrs" 将父组件传入的n个属性数据传递给当前组件的子组件
2). $listeners
实现当前组件的子组件向当前组件的父组件通信
$listeners是包含所有父组件传入的自定义事件监听名与对应回调函数的对象
使用: 通过v-on="$listeners" 将父组件绑定给当前组件的事件监听绑定给当前组件的子组件
应用: 利用它封装了一个自定义的带hover文本提示的el-button
方式7: $refs & $children & $parent
1). $refs
实现父组件向指定子组件通信
$refs是包含所有有ref属性的标签对象或组件对象的容器对象
使用: 通过 this.$refs.child 得到子组件对象, 从而可以直接更新其数据或调用其方法更新数据
2). $children
实现父组件向多个子组件通信
$children是所有直接子组件对象的数组
使用: 通过this.$children 遍历子组件对象, 从而可以更新多个子组件的数据
3). $parent
实现子组件向父组件通信
$parent是当前组件的父组件对象
使用: 通过this.$parent 得到父组件对象, 从而可以更新父组件的数据
应用: 在后台管理项目中使用了$refs
方式8: provide与inject
1). 实现祖孙组件间直接通信
2). 使用
在祖组件中通过provide配置向后代组件提供数据
在后代组件中通过inject配置来声明接收数据
3). 注意:
不太建议在应用开发中使用, 一般用来封装vue插件
provide提供的数据本身不是响应式的 ==> 父组件更新了数据, 后代组件不会变化
provide提供的数据对象内部是响应式的 ==> 父组件更新了数据, 后代组件也会变化
应用: element-ui中的Form组件中使用了provide和inject
方式9: vuex
-
vuex用来统一管理多个组件共享的状态数据
-
任意要进行通信的2个组件利用vuex就可以实现
A组件触发action或mutation调用, 将数据保存到vuex的状态中
B组件读取vuex中的state或getters数据, 得到最新保存的数据进行显示
-
面试题
-
mutation负责同步修改状态数据的,能不能异步修改
可以异步修改
如果异步修改的话会导致Vuex的调试工具失效,无法检测异步修改数据
-
设计的时候为什么建议mutation同步修改状态数据,而新增action负责异步
Vuex的作用是给多个组件共享数据
如果支持mutation异步修改数据,又因为异步的特性,会导致store对象中state数据发生错乱甚至是报错
为了数据的安全
-
Vuex刷新页面,数据丢失问题
方案一:Vuex的数据都是每次组件加载时候动态请求获取数据保存(性能差,因为网络问题可能有网络延迟)
方案二:将Vuex中的数据每次同步更新保存到sessionStorage中( state中的数据是动态的,就需要一直要同步到sessionStorage中,性能差)
方案三:在页面刷新之前获取Vuex的数据,将数据保存在sessionStorage中,页面加载后从sessionStorage中获取
// beforeunload 页面即将刷新之前调用 window.addEventListener('beforeunload', () => { sessionStorage.setItem('test2', JSON.stringify(this.personArr)) }) // 读取sessionStorage中是否有之前缓存的数据 let personArr = sessionStorage.getItem('test2') // 如果有: 更新Vuex中状态数据 personArr && this.changePersonArrMutation(JSON.parse(personArr))
-
方式10: 插槽 ==> 作用域插槽slot-scope
1). 实现父组件向子组件传递标签内容
2). 什么情况下使用作用域插槽?
父组件需要向子组件传递标签结构内容
但决定父组件传递怎样标签结构的数据在子组件中
3). 编码:
子组件:
<slot :row="item" :$index="index"> <!-- slot的属性会自动传递给父组件 -->
</slot>
父组件:
<template slot-scope="{row, $index}">
<span>{{$index+1}}</span>
<span :style="{color: $index%2===1 ? 'blue' : 'green'}" >{{row.text}}</span>
</template>
应用: element-ui中的Table组件
computed与method和watch的区别
-
computed
- 支持缓存,多次读取, 只会执行一次计算, 只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当计算属性数据变化时,调用set方法。
-
method
- 没有缓存, 多次读取, 必须多次调用
-
watch
-
watch支持异步;
-
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
-
当一个属性发生变化时,需要执行对应的操作;一对多;
-
监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变
-
Vue监听路由变化
方法一:通过 watch
// 监听,当路由发生变化的时候执行
watch:{
$route(to,from){
console.log(to.path);
}
},
或者
// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
或者
// 监听,当路由发生变化的时候执行
watch: {
'$route':'getPath'
},
methods: {
getPath(){
console.log(this.$route.path);
}
}
watch:{
// this.$route.path
'$route.path':function(newVal,oldVal){
//console.log(newVal+"---"+oldVal);
if(newVal === '/login'){
console.log('欢迎进入登录页面');
} else if(newVal === '/register'){
console.log('欢迎进入注册页面');
}
}
}
方法二;通过 vue-router 的钩子函数 beforeRouteEnter beforeRouteUpdate beforeRouteLeave
<script>
export default {
name: 'app',
// 监听,当路由发生变化的时候执行
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
</script>
props传参三种方式
传参方式:
1. 布尔值(只能搭配params参数使用)
2. 对象(用于自定义参数)
3. 函数(自定义参数 + 路由信息)
4)面试问题2: 如何指定params参数可传可不传?
path: ‘/search/:keyword?’
5)面试问题3: 指定params参数时可不可以用path和params配置的组合?(对象写法)
不可以用path和params配置的组合, 只能用name和params配置的组合
query配置可以与path或name进行组合使用
6)面试问题4: 如果指定name与params配置, 但params中数据是一个"", 无法跳转,路径会出问题
解决1: 不指定params
解决2: 指定params参数值为undefined
this.$router.push({
// path:'/search',
name:'search',
query:{
keyword1:this.keyword.toUpperCase()
},
params:{
//如果传递params参数是一个空串,那么路径会有问题,传过去如果是undefined就没事
keyword:this.keyword || undefined
}
})
前提是路由params参数要可传可不传
{
path:'/search/:keyword?',
component:Search,
name:'search',
props: route => ({keyword:route.params.keyword,keyword1:route.query.keyword1})
},
7)面试问题5: 路由组件能不能传递props数据?
可以: 可以将query或且params参数映射/转换成props传递给路由组件对象
实现: props: (route)=>({keyword1:route.params.keyword, keyword2: route.query.keyword })
路由懒加载
引入路由时,动态引入,代码分割,异步加载,例:
//没有指定webpackChunkName的情况下,每个组件打包成一个js文件
const home1 = () => import('../home1/index')
const home2 = () => import('../home2/index')
//设置了webpackChunkName,会合并打包成一个js文件
{
path: '/home1',
name: 'home1',
component: () =>import(/* webpackChunkName: "home" */,'../home2/index')
}
{
path: '/home2',
name: 'home2',
component: () => import(/* webpackChunkName: "home"*/,'../home2/index')
}
import函数不是webpack提供的语法。是es6语法
面试问题5(非常重要): 编程式路由跳转到当前路由(参数不变), 会抛出NavigationDuplicated的警告错误
面试问题: 在做项目时有没有遇到比较难的问题?(可做回答)
回答步骤:
-
我的问题: 我在上一个项目时没有问题, 后面再做一个新的项目时就有了问题
-
原因分析: vue-router3.1.0之后, 引入了push()的promise的语法, 如果没有通过参数指定回调函数就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
-
解决办法:解决1: 在跳转时指定成功或失败的回调函数, 通过catch处理错误
解决2: 修正Vue原型上的push和replace方法 (优秀)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
//终极解决多次触发push或者repalce,报错的问题
//NavigationDuplicated
const originPush = VueRouter.prototype.push
const originReplace = VueRouter.prototype.replace
VueRouter.prototype.push = function(location,onResolved,onRejected){
if(onResolved === undefined && onRejected === undefined){
// originPush.call目的是让VueRouter实例化对象去调用
//如果不加,那就是window在调用
return originPush.call(this,location).catch(() => {})
}else{
return originPush.call(this,location,onResolved,onRejected)
}
}
VueRouter.prototype.replace = function(location,onResolved,onRejected){
if(onResolved === undefined && onRejected === undefined){
// originPush.call目的是让VueRouter实例化对象去调用
//如果不加,那就是window在调用
return originReplace.call(this,location).catch(() => {})
}else{
return originReplace.call(this,location,onResolved,onRejected)
}
}
import routes from '@/router/routes'
export default new VueRouter({
routes
})
对axios进行二次封装(面试必说)
-
配置通用的基础路径和超时:
axios.create({baseURL, timeout})
-
显示请求进度条
显示: 准备发请求前显示, 在请求拦截器中执行NProgress.start()
隐藏: 请求结束隐藏, 在响应拦截器成功/失败回调中NProgress.done()
-
携带token数据
在请求拦截器中, 将token添加到请求头中
-
成功返回的数据不再是response, 而直接是响应体数据response.data
响应拦截器成功的回调中: return response.data
-
统一处理请求错误, 具体请求也可以选择处理或不处理
在响应拦截器失败的回调中: alert提示错误信息, return Promise.reject(error)
//对axios的二次封装
// 配置基础路径和超时限制
// 添加进度条信息 nprogress
// 返回的响应不再需要从data属性当中拿数据,而是响应就是我们要的数据
// 统一处理请求错误, 具体请求也可以选择处理或不处理
import axios from 'axios'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
const service = axios.create({
baseURL: '/api', // 配置基础路径
timeout: 20000, //和超时限制
});
//请求拦截器
//请求拦截器内部一般不会处理错误的信息
service.interceptors.request.use(config => {
//config是发送请求的配置对象,必须处理完返回这个配置对象
//开启我们的进度条
NProgress.start()
return config
});
// 响应拦截器
service.interceptors.response.use(
response => {
//停止进度条
NProgress.done()
//返回响应的时候,直接返回响应的data
return response.data
},
error => {
NProgress.done()
alert('请求出错' + error.message || '未知错误')
//以后不允许用户继续处理: 中断promise链
return new Promise(() => {}) //返回pending状态的promise 中断
//以后它允许用户继续对错误进行处理
// return Promise.reject(error)
}
);
export default service
什么是ESLint?
官网上告诉我们,ESLint 是一个用来识别 ECMAScript/JavaScript 并且按照规则给出报告的代码检测工具,哦,所以我们可以知道,ESLint 就是一个工具,而且是一个用来检查代码的工具。
render函数是什么,怎么用,详情
render函数是什么
简单的说,在vue中我们使用模板HTML语法组建页面的,使用render函数我们可以用js语言来构建DOM
因为vue是虚拟DOM,所以在拿到template模板时也要转译成VNode的函数,而用render函数构建DOM,vue就免去了转译的过程。
当使用render函数描述虚拟DOM时,vue提供一个函数,这个函数是就构建虚拟DOM所需要的工具。官网上给他起了个名字叫createElement。
node.js是什么,详情
一种一个能够在服务器端运行JavaScript,并且可以开放源代码,以及跨平台运行JavaScript的一种运行环境。