大家好,我是前端岚枫,今天主要跟大家分享我整理的笔记2021前端面试题系列:
使用Proxy代理跨域、watch监听、keep-alive、vue服务器端渲染、VUEX、vue单页面和多页面的使用等等,此方面内容在我们的工作中常用到, 也是面试官经常提问的问题,希望下面文章对大家有所帮助。
使用Proxy代理跨域
什么是跨域?
域名 协议 ip地址 端口 任何一个不一样 就跨域
解决跨域?
1 jsonp —使用script的src发送 只能get 请求
2 cors 后台设置允许跨域 需要后台设置 允许跨域
所有后台语言 都可以设置
3 服务器代理
重点 现在 前端 vue 框架 是可以自己设置 服务器代理的 proxy
配置:vue在 vue.config.js 可以配置重写webpack
分析: 本题考查如何解决跨域问题
解析: 解决跨域问题的方式有几种,
- 一种是服务端设置 , 但这种方式依赖服务端的设置,在前后分离的场景下 ,不太方便
- 还有一种jsonp形式, 可以利用script标签 的特性解决同源策略带来的跨域问题,但这是这种方案对于请求的类型有限制,只能get
- 还有一种就可以在开发环境(本地调试)期间,进行代理, 说白了 就是通过 在本地通过nodejs 启动一个微型服务,
- 然后我们先请求我们的微型服务, 微型服务是服务端, 服务端代我们去请求我们想要的跨域地址, 因为服务端是不受同源策略的限制的, 具体到开发中,打包工具webpack集成了代理的功能,可以采用配置webpack的方式进行解决, 但是这种仅限于 本地开发期间, 等项目上线时,还是需要另择代理 nginx
Access-Control-Allow-Origin:*
Access-Control-Allow-Methods:"POST, GET, OPTIONS, DELETE"
以下为webpack配置代理的配置
// vue.config.js
module.exports = {
// 修改的配置
devServer: {
proxy: {
'/api': {
target: 'http://122.51.238.153',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
target:接口域名;
changeOrigin: 如果设置为true
,那么本地会虚拟一个服务端接收你的请求并代你发送该请求;
pathRewrite:如果接口中是没有api的,那就直接置空(如上)如果接口中有api,就需要写成{‘^/api’:‘’}
上线了如果还有跨域 可以让后台设置 允许跨域
服务器代理流程图
Vue中的watch如何深度监听某个对象
分析: 此题考查Vue的选项watch的应用方式
解析: watch最基本的用法是
上面代码中: 有个原则监听谁,写谁的名字,然后是对应的执行函数, 第一个参数为最新的改变值,第二个值为上一次改变的值, 注意: 除了监听 data,也可以监听计算属性 或者一个 函数的计算结果
那怎么深度监听对象 ,两种方式
- 字符串嵌套方式
- 启用深度监听方式
export default {
data () {
return {
name: '张三'
}
},
watch: {
name (newValue, oldValue) {
}
}
}
export default {
data () {
return {
a: {
b: {
c :'张三'
}
}
}
},
watch: {
"a.b.c": function (newValue, oldValue) {
}
}
}
export default {
data () {
return {
a: {
b: {
c :'张三'
}
}
}
},
watch: {
a: {
deep: true // deep 为true 意味着开启了深度监听 a对象里面任何数据变化都会触发handler函数,
handler(){
// handler是一个固定写法
}
}
}
}
Vue keep-alive使用
分析: 此题考查Vue中组件缓存的使用
解析: keep-alive是 Vue提供的一个全局组件, Vue的组件是有销毁机制的,比如条件渲染, 路由跳转时 组件都会经历销毁, 再次回到页面时,又会回到 重生, 这一过程保证了生命周期钩子函数各个过程都会在这一生命周期中执行.
但是,我们辛辛苦苦获取的数据 滑动的页面 会因为组件的销毁 重生 而 归零,这影响了交互的体验, 所以 keep-alvie出现了, 可以帮助我们缓存想要缓存的组件实例, 只用用keep-alive 包裹你想要缓存的组件实例, 这个时候, 组件创建之后,就不会再进行 销毁, 组件数据和状态得以保存
但是,没有了销毁,也就失去了重生的环节, 我们失去了 原有的钩子函数, 所以keep-alive包裹的组件 都获取了另外两个事件 --如果缓存组件需要重新获取数据
唤醒 activated重新唤醒休眠组件实例时 执行: 缓存的组件还提供了activated生命周期 可以在这里面重新发送请求
休眠 deactivated组件实例进入休眠状态时执行
但是我们不能缓存所有的组件实例, 如果是针对 组件容器 router-view 这个组件进行的缓存, 一般的策略是在路由的元信息 meta对象中设置是否缓存的标记, 然后根据标记决定是否进行缓存
<div id="app">
<keep-alive>
<!-- 里面是当需要缓存时 -->
<router-view v-if="$route.meta.isAlive" />
</keep-alive>
<!-- 外面是不需要缓存时 -->
<router-view v-if="!$route.meta.isAlive" />
</div>
还有需要注意的问题是: 被缓存的组件中如果还有子组件, 那么子组件也会一并拥有 激活和唤醒事件,并且这些事件会在同时执行
app.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<!-- <keep-alive> -->
<!-- 里面是当需要缓存时 -->
<!-- <router-view v-if="$route.meta.isAlive" /> -->
<!-- </keep-alive> -->
<!-- 外面是不需要缓存时 -->
<!-- <router-view v-if="!$route.meta.isAlive" /> -->
<!-- 这样写的 组件显示 就不会缓存
在路由里面配置的meta 可以直接使用
$route获取
现在我配置 了 home 不需要缓存
home 每次都重新创建--生命周期会走一遍
about需要缓存 --你再回到about组件 他不会重新创建一遍
-->
<router-view v-if="!$route.meta.isAlive"></router-view>
<!-- 如果组件需要缓存 用keep-alive 包裹起来 -->
<keep-alive>
<router-view v-if="$route.meta.isAlive"></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
created(){
// console.log('$route',this.$route)
}
}
</script>
<style lang="less">
*{
margin: 0;
padding: 0;
}
li{
list-style: none;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
// meta 标识符 也是路由配置里面可以写的
// 他可以用来 判断一些操作
meta:{
// 名字:值
isAlive:false // 我想 isAlive false代表不缓存
// 需要缓存的就在这写成 isAlive:true
}
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
meta:{
isAlive:true // about组件 需要缓存
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
需要缓存的组件? 最常见的事 列表(因为很长 到详情页返回 又回到列表)
keep-alive图说明
vue的双向数据绑定原理是什么
分析 :此题考查 Vue的MVVM原理
解答: Vue的双向绑定原理其实就是MVVM的实现原理, Vuejs官网已经说明, 实际就是通过 Object.defineProperty方法 完成了对于Vue实例中数据的 劫持, 通过对于 data中数据 set的监听,
然后通过观察者模式, 通知 对应的绑定节点 进行节点数据更新, 完成数据驱动视图的更新
同理, 通过对于节点的表单值改变事件的监听, 执行对于数据的修改
简单概述 : 通过Object.defineProperty 完成对于数据的劫持, 通过观察者模式, 完成对于节点的数据更新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="btn">点击修改获取值</button>
<h1 id="con">
name的值是:{{name}}
</h1>
<script>
// Object.defineProperty() 原生js就自带的方法
// vue 也是js 他就是一个封装了 js的 库而已
// Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
// vue的原理
// Object.defineProperty(对象,属性名,{ get set 等配置 })
// data.name='李四' 设置值 就会触发 set
// vue 原理
// vue 通过 原生js的 Object.defineProperty 监听到了 我们写的data数据
// 这个data里面的数据 有修改 就会触发 Object.defineProperty 里面的 set
// 在set里面 我们可以 获取到最新的修改的值 去页面上 正则匹配到对应地方 替换修改
// 你看过vue 源码 如果没看过就说 没怎么看过 只是了解了一下 稍微写了一下
function Vue(){
this.data={
name:'zs'
}
// 那 vue的data 那么多数据怎么办?
// vue里面 就循环执行下面这段话 不就全部data 监听到了吗?
// for()
Object.defineProperty(this.data,'name',{
get:function(){
// 当获取监听对象的某个 值 就可以 执行get函数
console.log('get 获取了值')
},
set:function(newval){ // 设置的新值
console.log('set 设置了值',newval)
// 当然vue 没有这么简单去找 他写了很多正则表达式去替换
// 但是思路是这个
// 我只需要 监听到 name值改了 就去页面修改 对应的地方就行 变成新值
let con=document.getElementById("con")
// con.innerHTML 获取内容 name的值是:{{name}} .replace("查找的字符串","替换成这个")
let str=con.innerHTML.replace("{{name}}",newval)
// 重新修内容 innerHTML 是获取内容 设置内容的
con.innerHTML=str
}
})
}
let vm=new Vue()
// vm.data
// console.log(data.name) ;// 获取
// vue 的核心 如果数据改变了 那么页面就跟着改变成最新数据了
// 为什么vue可以知道你的数据更新了?
// 因为vue 帮我监听了 set 然后你只要设置值 就触发set 只需要在set里面找到页面对应的数据修改就行
document.getElementById("btn").onclick=function(){
// data.name='小雷'
// console.log(data.name) ;// 这就是获取值
vm.data.name='李四'
}
</script>
</body>
</html>
页面刷新了之后vuex中的数据消失怎么解决
分析:此题考查 如果将vuex数据进行本地持久化
解析: vuex数据位于内存, 页面的刷新重置会导致数据的归零,也就是所谓的消失, 本地持久化可以解决这个问题.本地持久化用到的技术也就是 本次存储 sesstionStorage 或者 localStorage ,
如果需要保持的更长久 ,浏览器关掉 再打开依然存有数据,需要使用后者
实施方案: state的持久化 也就是分别需要在 state数据初始化 /更新 的时候 进行读取和设置本地存储操作
代码如下
export default new Vuex.store({
state: {
user: localStorge.getItem('user') // 初始化时读取 本地存储
},
mutations: {
updateUser (state, payload) {
state.user = payload.user
localStoregae.setItem('user',payload.user) // 数据更新时 设置本地存储
}
}
})
vue做服务端渲染
分析: 为什么要做服务端渲染, 首先要明白 服务端渲染解决什么问题
解析: vuejs 官网说的很明白, 要做服务端渲染首先必须是有对应的需求,即对 实时到达时间(页面访问时间)的绝对需求. 如果只是简单的一个管理系统, 区区几百毫秒的优化 显得十分小题大做.
服务端渲染这里 有一个成熟优秀的框架 nuxt.js , 正如next.js对于react,nuxt是vue服务端渲染的优秀解决方案
nuxt的出现可以让渲染内容完全服务端化,解决seo不够友好, 首屏渲染速度不够迅速的问题,
但是这里需要注意: 并不是所有页面都需要服务端渲染, 因为服务端渲染比重多大 对于服务器的访问处理能力 要求也会急剧增大
步骤这个nuxt脚手架不需要安装node,默认自带
1 脚手架 npx create-nuxt-app <项目名>
2 yarn dev 启动开发
上线
yarn build
yarn start
为什么使用nuxt.js?
普通vue项目 打开地址查看源代码 是空 他主要是用切换的时候才会有内容
nuxt.js项目 查看源代码 他是已经渲染好了很多html了
- 这个和seo 搜索引擎 比如百度 他会去 找到所有网站 挨个看你的网站内容 有没有 好不好–爬虫
如果普通vue 项目 是 空的 那么 就没有内容 seo不行 网站就很垃圾
如果nuxt.js项目 有内容 就比较好 利于seo
- 普通vue项目 内容打包到js了 那个js会很大 首页就显示很慢
如果nuxt.js项目 只是一些 js 其他的他服务器端就渲染好了 稍微快
vue单页面应用渲染是从服务器获取所需js,在客户端将其解析生成html挂载于
id为app的DOM元素上,这样会存在两大问题。
- 由于资源请求量大,造成网站首屏加载缓慢,不利于用户体验。
- 由于页面内容通过js插入,对于内容性网站来说,搜索引擎无法抓取网站内容,不利于SEO。
Nuxt.js 是一个基于Vue.js的通用应用框架,预设了利用Vue.js开发服务端渲染的应用所需要的各种配置。可以将html在服务端渲染,合成完整的html文件再输出到浏览器。
除此之外,nuxt与vue还有一些其他方面的区别。
- 路由
nuxt按照 pages 文件夹的目录结构自动生成路由
vue需在 src/router/index.js 手动配置路由 - 入口页面
nuxt页面入口为 layouts/default.vue
vue页面入口为 src/App.vue - webpack配置
nuxt内置webpack,允许根据服务端需求,在 nuxt.config.js 中的build属性自定义构建webpack的配置,覆盖默认配置
vue关于webpack的配置存放在build文件夹下 - asyncData 里面发送ajax 这个东西跟生命周期这些都是平级的
要理解asyncData方法执行时,其实是在服务端完成的,这个数据是在服务端渲染好了的
nuxt.js的ajax,你先别往你那个异步上去思考,其实这里面所有的ajax最后都会形成页面。你别想着,我一点按钮,调用一个方法,然后再ajax去加载数据。因为我们最后全部都会生成静态,所以任何的获取数据的操作,最后都会变成页面的跳转。
所以,官方给了一套写法,你必须按照这个去写,
并且这里的ajax会再页面渲染之前就执行。这个东西跟生命周期这些都是平级的。
1 npm install @nuxtjs/axios --save
2 .plugins目录新建axios.js
import * as axios from 'axios'
let options ={}
//需要全路径才能工作
if(process.server){
options.baseURL=http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api
}
export default axios.create(options)
3.Nuxt.config.js增加axios配置
modules:[
'@nuxtjs/axios'
],
4 使用 asyncData 里面发送ajax 这个东西跟生命周期这些都是平级的 在页面渲染之前
export default {
async asyncData({app}){
let res =await app.$axios({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'get',
url: `http://test.yms.cn/testjson.asp`,
data: ''
})
// app.$axios
console.log('res',res.data)
return{
testData:res.data.title
}
},
created(){
console.log('nuxt reg组件')
}
}
下图为关于nuxt的简单概述
vue-router传参
分析:考查vue-router的传值方式
解析 vue-router 传值 可以通过 地址传值
最简单的就是url传值, url传值又两种, params 和 query参数传值
- params传值 是指的动态路由传值
- query传值,指通过?后面的拼接参数传值
{ path: '/user/:id' } // 定义一个路由参数
<router-link to="/user/123"></router-link> // 传值
this.$route.params.id // 取值 params参数
<router-link to="/user?id=123"></router-link> // 传值
this.$route.query.id // 取值 query参数
前端鉴权一般思路
以前讲过 我们可以在axios的,请求拦截器里面配置token
- 有些axios请求需要token,我们是可以陪着请求拦截器
- 有些页面需要登录才能看,我们也可以路由导航守卫, router.beforeEach,判断token
- 导航菜单栏,一般后台侧边栏,有不同的人登录,根据角色权限看到的栏目不一样
分析: 考查前后分离的鉴权思路
**解析**
: 首先要明白 为什么要在前端鉴权? 因为传统项目都是在后端鉴权, 然后通过进行拦截 跳转 对应操作
因为 我们做的并不是传统的项目,而是前后分离项目,也就是前端项目和后端服务进行了**剥离**
, 后端没有办法用session来存储你任意一个前端项目域名下的身份信息, 所以jwt 鉴权模式应运而生.
也就是后端不再提供会话的身份存储,而是通过一个鉴权接口将用户的身份,登录时间,请求端口,协议头..等等信息 组装成一个加密的串 返给前端请求, 前端拿到了这个串,就可以认为自己登录成功
那么这个**加密串**
就成了 前端用户是否登录的成功标志, 这就是我们的token , 那么在接下来的接口请求中,我们几乎都要携带这个加密串,因为它是**唯一**
能**证明我们身份**
的信息.
为了方便,我们会一般在请求工具 axios(举例)的拦截器中**统一注入token**
, 减少代码的重复
token 同时具有时效性,我们也需要在此时对token过期进行处理,一旦出现过期的请求码, 就需要进行 换取新token 或者重新登录的解决方案
除此之外,我们还需要依据**有无加密串**
在前端对于某些页面的访问进行限制, 这个会用到我们的Vue-Router中的导航守卫.
vue 单页项目涉及到多角色用户权限问题,不同的角色用户拥有不同的功能权限, 不同的功能权限对应的不同的页面
一开始 有一些 默认的路由
登录后 比如你是总经理 后台会返回给前端 总经理能看见的 路由页面地址 数组
前端在router.beforeEach 路由导航守卫里面 拿到返回的地址 使用 router.addRouter 动态加上 这个项目路由就好了
routes= 后台返回的 符合条件的 路由数据 类似我们自己写的那个path 等等
this.$router.addRoutes(routes)
例子
router.beforeEach((to, from, next) => {
//判断user信息是否已经获取
if (token) {
//根据用户的角色类型来生成对应的新路由
const newRouter = [{path:"/xxx" ...} ..]
//将新路由添加到路由中
router.addRoutes(newRouter)
//为了正确渲染导航,将对应的新的路由添加到vuex中
渲染对应的侧边栏
}
})
获取路由数组的流程图
前端如何做?
- 写一个所有的路由数组
- 根据后台返回路由列表权限,拿着两个数组对比,处理数据得到需要的路由数组
vue数据流 和 react数据流
vue,react数据流不是双向的数据绑定,是单向的。
在vue 、React中数据流向是单向的,由父节点流向子节点,如果父节点的props发生了改变,那么React会递归遍历整个组件
父组件通过绑定 props 的方式,将数据传递给子组件,但是子组件自己并没有权利修改这些数据,如果要修改,只能把修改这一个行为通过 event 的方式报告给父组件,由父组件本身决定改如何处理数据。
vue 另一个概念 v-model双向数据 无论数据改变,或是用户操作,都能带来互相的变动,自动更新。
如何在组件中监听Vuex的数据变化
分析: 此题考查Vuex的应用及 Vue内部的监听数据变化的机制
解答: 首先确定 Vuex是为了解决什么问题而出现的 ? Vuex是为了解决组件间状态共享而出现的一个框架.
其中有几个要素 是组成Vuex的关键, state(状态) mutations actions ,
- state 表示 需要共享的状态数据
- mutations 表示 更改 state的方法集合 只能是同步更新 不能写ajax等异步请求
- actions 如果需要做异步请求 可以在actions中发起 然后提交给 mutations mutation再做同步更新
也就是 state 负责管理状态 , mutation负责同步更新状态 action负责 异步获取数据 同提交给mutation
所以 组件监听Vuex数据变化 就是 监听 Vuex中state的变化,
1. 我们可以在组件中通过组件的 watch方法来做, 因为组件可以将state数据映射到 组件的计算属性上,
然后 监听 映射的计算属性即可 代码如下
// vuex中的state数据
state: {
count: 0
},
// A组件中映射 state数据到计算属性
computed: {
...mapState(['count'])
}
// A组件监听 count计算属性的变化
watch: {
count () {
// 用本身的数据进行一下计数
this.changeCount++
}
}
2. vuex中store对象本身提供了watch
函数 ,可以利用该函数进行监听
- watch(fn: Function, callback: Function, options?: Object): Function
响应式地侦听 fn
的返回值,当值改变时调用回调函数。fn
接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 [vm.$watch](https://cn.vuejs.org/v2/api/#vm-watch)
方法的参数。
代码
created () {
this.$store.watch((state, getters) => {
return state.count
}, () => {
this.changeCount++
})
}
Vue单页面和多页面的使用
分析: 首先分析,单页面应用和 多页面应用的根本区别
**解答**
: 单页面即所有的模块统统置于一个html文件之上,切换模块,不会重新对html文件和资源进行再次请求,服务器不会对我们**换页面**
的动作 产生任何反应, 所以我们感觉不到任何的刷新动作,速度和体验很畅快多页面应用 即多个html页面 共同的使用, 可以认为一个页面即一个模块,但是不排除 多个单页应用混合到一起的组合情况 , 多页面切换一定会造成 页面资源的重新加载, 这也就意味着 如果 多页面之间切换,一定会造成很数据的
**重置**
一个项目分成很多 小vue项目 你去其实也可以直接创建两个项目
1 **新建多个页面 每个页面是一个单独的小vue类型 **
2 配置 多入口页面在vue.config.js里写上这些 重点是入口选择对应页面的main.js
// vue.config.js
module.exports = {
// 我配置完成 和 文件夹 匹配好
// 相当于 写了两套项目 vue代码
// pages 配置 多页面入口 index 和ui
// 配置和 你的文件夹匹配 两套 文件
pages: {
index: {
// page 的入口
entry: "src/views/index/main.js",
// 模板来源
template: "public/index.html",
// 在 dist/index.html 的输出
filename: "index.html",
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: "Index Page"
},
ui: {
// page 的入口
entry: "src/views/ui/main.js",
// 模板来源
template: "public/ui.html",
// 在 dist/ui.html 的输出
filename: "ui.html",
// 当使用 title 选项时,
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: "ui Page"
}
// 。。。。
}
};
3 public 写上不同的渲染的 html
4 main.js 不同的入口 对应上自己的 根组件和 页面元素
5 通过a标签跳转
<div id="app">
前端岚枫
<a href="home.html">去home页面</a>
</div>
关注:程序员石磊 获取更多面试资料