一、自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
// 如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
<input v-focus>
二、前端优化
1.代码层面
(1)判断有没有下一页数据
1.先根据数组的的总条数算出你的总页数
总页数 totalPages = Math.ceil(总条数 totalNumber/页容量 pagesize)
= Math.ceil(23/10)
2.页面滑动滚动触底触发事件
onTouchBottom(){
如果当前页面大于等于总页数
if(currentPage >=totalPages){
无数据不再请求
}
else{
还有下一页数据,继续发送请求数据
currentPage++;
this.getData();
}
}
(2)computed 和 watch 区分使用场景
计算属性computed :
1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,
也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
5.如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;
在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch:
1. 不支持缓存,数据变,直接会触发相应的操作;
2.watch支持异步;
3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
4. 当一个属性发生变化时,需要执行对应的操作;一对多;
5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现**对象内部值**的变化,复杂类型的数据时使用,例如:
数组中的对象内容的改变,注意监听数组的变动不需要这么做。
注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。这是和computed最大的区别。
(3)v-if 和 v-show 区分使用场景
v-if是创建和销毁dom元素
v-show在初始肯定是创建了元素,只不过用了display的block或none来显示隐藏
频繁操作用show,偶尔用if
(4)v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
key 的作用
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex
它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:
newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex
如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。
具体有无 key 的 diff 过程,可以查看作者写的另一篇详解虚拟 DOM 的文章《深入剖析:Vue核心之虚拟DOM》
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
为什么v-for与v-if不能同时使用?
v-for 的优先级比 v-if 高,会造成渲染出本应该隐藏的元素。
解决方法:
//添加一层<template></template>标签,v-for放在该标签上
<template v-for="item in data">
<el-input :key="item.name" v-if="item.age==3"></el-input>
</<template>
(5)长列表性能优化
长列表性能优化分为两种
1.一种是滚动加载,通过监听页面的高度和位置来触发加载数据,其实也相当于分页,具体通过计算当前页面是否最后一页,来判断是否还有数据
1.先根据数组的的总条数算出你的总页数
总页数 totalPages = Math.ceil(总条数 totalNumber/页容量 pagesize)
= Math.ceil(23/10)
2.页面滑动滚动触底触发事件
onTouchBottom(){
//如果当前页面大于等于总页数
if(currentPage >=totalPages){
//无数据不再请求
}
else{
//还有下一页数据,继续发送请求数据
currentPage++;
this.getData();
}
}
2.另一种是按需加载,如果可视区域内只能看见10条数据,那么无论总数据有多少条,加载返回的一共就只有10条数据,滚动加载会根据数据的下标进行计算,从总数据里面截取数据替换当前数据,每次渲染的只有10条,而懒加载是不断请求数据不断增加数据
(6)事件的销毁
加载:
mounted() {
window.addEventListener("scroll",this.handleFun)
}
滚动触发方法:
handleFun(){
let scorll = Math.max(document.body.scrollTop || document.documentElement.scrollTop);
let emptyDiv=$("#emptyDiv").offset().top;
emptyDiv = emptyDiv - 300;
if (scorll > 200&&scorll<emptyDiv) {
$("#toolbar").addClass("myfix");
}else {
$("#toolbar").removeClass("myfix");
}
}
销毁:
beforeDestroy(){
window.removeEventListener("scroll",this.handleFun)
}
(7)路由懒加载
1.vue异步组件技术 (异步加载)
vue异步组件技术
{ path: '/home', name: 'home', component: resolve => require(['@/components/home'],resolve) }
{ path: '/index', name: 'Index', component: resolve => require(['@/components/index'],resolve) },
2.指定了相同的webpackChunkName,会合并打包成一个js文件
{ path: '/about', component: About }, { path: '/index', component: Index },
{ path: '/home', component: Home }
// 下面没有指定webpackChunkName,每个组件打包成一个js文件。
const Home = () => import('@/components/home')
const Index = () => import('@/components/index')
const About = () => import('@/components/about')
//下面指定了相同的webpackChunkName,会合并打包成一个js文件。
把组件按组分块
const Home = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
const About = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about')
3.webpack提供的require.ensure()
{ path: '/home', name: 'home', component: r => require.ensure([], () => r(require('@/components/home')), 'demo') },
{ path: '/index', name: 'Index', component: r => require.ensure([], () => r(require('@/components/index')), 'demo') },
{ path: '/about', name: 'about', component: r => require.ensure([], () => r(require('@/components/about')), 'demo-01') }
// r就是resolve
const list = r => require.ensure([], () => r(require('../components/list/list')), 'list');
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router = new Router({undefined
routes: [
{undefined
path: '/list/blog',
component: list,
name: 'blog'
}
]
})
(8)第三方插件的按需引入
(9)优化无限列表性能
(10)服务端渲染 SSR or 预渲染
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
- 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
- 更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
- 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
- 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
(11)dom事件流
事件捕获阶段:从根节点到子元素节点,从上往下走
处于目标阶段:在目标节点触发事件
冒泡阶段:从目标阶段往根节点走,从下往上走,一般的addEventlistener()还有事件委托(事件代理)就是利用的是冒泡阶段的事件流
2.webpack层面
webpack压缩图片
js
通常用webpack打包时,会根据webpack.config.js 中url-loader中设置的limit大小来对图片进行处理,小于limit的图片转化成base64格式,其余的不做操作。对于比较大的图片我们可以用image-webpack-loader 来压缩图片。
npm install image-webpack-loader --save-dev
在 webpack.config.js 中配置
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',// 压缩图片
options: {
bypassOnDebug: true,
}
}
]
}
webpack.config.js中定义入口和输出
const path = require('path');
module.exports = {
entry:{
app:'./src/index.js'
},
output:{
filename:'bundle.js',
path:path.resolve(_dirname,'dist')
}
}
webpack的属性 loader
webpack处理样式文件
webpack打包原理
三、加载大量图片优化:雪碧图 减少http请求
大量图片可以使用预加载 懒加载 和缩略图
预加载:css position 属性 优先图片 增加服务器压力
.imga1{ background:url(http://domain.tld/image-01.png) no-repeat -9999px -9999px;}
懒加载 定时器 页面滚动触发事件 减少服务器压力
四、key 的作用
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。具体有无 key 的 diff 过程,可以查看作者写的另一篇详解虚拟 DOM 的文章《深入剖析:Vue核心之虚拟DOM》
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
五、虚拟 DOM 实现原理?
虚拟 DOM 的实现原理主要包括以下 3 部分:
用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
diff 算法 — 比较两棵虚拟 DOM 树的差异;
pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
虚拟 DOM 的优缺点
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象添加响应式属性
defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法
Proxy 与 Object.defineProperty 优劣对比
Proxy 的优势如下:
Proxy 可以直接监听对象而非属性;
Proxy 可以直接监听数组的变化;
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
六、Vue 框架怎么实现对象和数组的监听?
如果被问到 Vue 怎么实现数据双向绑定,大家肯定都会回答 通过 Object.defineProperty() 对数据进行劫持,但是 Object.defineProperty() 只能对属性进行数据劫持,不能对整个对象进行劫持,同理无法对数组进行劫持,但是我们在使用 Vue 框架中都知道,Vue 能检测到对象和数组(部分方法的操作)的变化,那它是怎么实现的呢?我们查看相关代码如下:
通过以上 Vue 源码部分查看,我们就能知道 Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
七、vue双向绑定原理
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
八、路由
1.路由的几种模式
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':
https://www.word.com#search
1.url会出现"#"符号
2.hash值不包括在http请求中,他是交由前端路由处理,改变hash值时不会刷新页面,也不会向服务器发送请求
3.hash值得改变会触发hashChange事件
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
1.整个地址重新加载,也可保存历史记录,方便前进和后退
2.依赖h5 api 和后台配置,如果后台没有配置的话页面刷新会报404
2.router的区别
route是路由信息对象,包括path、params、hash、query、fullpath、matched、name等路由信息参数
router是路由实例对象,包括了路由的跳转方法(push、replace),钩子函数等
this.$router.go(n) n可正可负
this.$router.push()
九、父组件可以监听到子组件的生命周期吗?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
十、 vue中某些操作无法监听的解决方案
当你修改数组的长度时,例如:vm.items.length = newLength
解决方法: Array.prototype.splice
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
解决方法: Vue.set
十一、vue中的数据流都是单向的
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
十二、spa单页面的优缺点
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
十三、后台管理权限的递归方法
//递归获取三级节点的id
this.getLeafKeys(role,this.defkeys)
//递归方法
getLeafkeys(node,arr){
if(!node.children){
return arr.push(node.id)
}
node.children.forEach(i=>{
this.getLeafkeys(i,arr)
})
}
十四、图片上传
//input隐藏,状态设置为file,点击头像触发input的click()
<input
ref="headUpRef"
type="file"
accept="image/*"
@change="handleFile"
class="hiddenInput"
/>
<div class="avaPic" style="width: 35px; height: 35px" @click="uploadHeadImg">
<el-avatar :src="userInfo.avatar" fit="cover"></el-avatar>
</div>
// 打开图片上传
uploadHeadImg: function () {
// this.$el.querySelector(".hiddenInput").click();
this.$refs.headUpRef.click();
}
十五、跨域
1.ajax简单请求和非简单请求
只要同时满足以下条件就属于简单请求
(1)、请求方法是以下三种方法之一:GET、POST、HEAD
(2)、Http的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type。
(3)、Content-Type只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
非简单请求
会预检请求 (preflight request),即先预发送OPTIONS的请求
第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求
第一次的预检请求获知服务器是否允许该跨域请求:如果允许,才发起第二次真实的请求;如果不允许,则拦截第二次请求。
Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,在此期间不用发出另一条预检请求
2.jsonp跨域
不管是我们的script标签的src还是img标签的src,或者说link标签的href他们没有被同源策略所限制,比如我们有可能使用一个网络上的图片,就可以请求得到,src或href链接的静态资源,本质上来说也是一个get请求
jsonp就是使用同源策略这一“漏洞”,实现的跨域请求(这也是jsonp跨域只能用get请求的原因所在)
我们要用过利用srcipt标签的src属性来实现,那么我们如何做呢,我们来看一段简单的代码,为了方便,我这里使用jQuery:
$('#btn').click(function(){
var frame = document.createElement('script');
frame.src = 'http://localhost:3000/article-list?name=leo&age=30&callback=func';
$('body').append(frame);
});
可以看到,让我们点击按钮的时候,创建了一个script标签(即会发送一个get请求到src指向的地址,注意:这里必须使用scipt标签,否则返回的数据不会被当作js执行),callback参数就是核心所在,callback定义好的方法名字(前后端一致)能接受从接口请求回来的数据
function func(res){
alert(res.message+res.name+'你已经'+res.age+'岁了');
}
后台中这样实现
router.get('/article-list', (req, res) => {
console.log(req.query, '123');
let data = {
message: 'success!',
name: req.query.name,
age: req.query.age
}
data = JSON.stringify(data)
res.end('func(' + data + ')');
});
3.Vue里的proxyTable解决跨域,api接口管理
在config的index.js文件下
proxyTable: {
// 测试环境才用api代理
'/api': {
target: 'http://10.18.204.43:4396',// 目标服务器地址
pathRewrite: {'^/api': ''},// pathRewrite 来重写地址,将前缀 '/api' 转为 '/'。
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
secure: false, // 如果是https接口,需要配置这个参数
}
}
详细参数:
target:要使用url模块解析的url字符串
forward:要使用url模块解析的url字符串
agent:要传递给http(s).request的对象(请参阅Node的https代理和http代理对象)
ssl:要传递给https.createServer()的对象
ws:true / false,是否代理websockets
xfwd:true / false,添加x-forward标头
secure:true / false,是否验证SSL Certs
toProxy:true / false,传递绝对URL作为路径(对代理代理很有用)
prependPath:true / false,默认值:true - 指定是否要将目标的路径添加到代理路径
ignorePath:true / false,默认值:false - 指定是否要忽略传入请求的代理路径(注意:如果需要,您必须附加/手动)。
localAddress:要为传出连接绑定的本地接口字符串
changeOrigin:true / false,默认值:false - 将主机标头的原点更改为目标URL
在main.js文件下
axios.defaults.baseURL = '/api'
4.nginx代理接口跨域
正向代理代理的是客户端,反向代理对象是服务器
1.正向代理服务器位于客户端和服务器之间,为了从服务器获取数据,客户端要向代理服务器发送一个请求,并指定目标服务器,代理服务器将目标服务器返回的数据转交给客户端。这里客户端需要要进行一些正向代理的设置的
2.反向代理:
1.反向代理,客户端对代理是无感知的,客户端不需要任何配置就可以访问,客户端将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址
2.简单来说就是以nginx代理服务器作为一个中转服务器,客户端发送的请求先到nginx服务器上(因为nginx服务器上的域名端口等可以自己配置,所以就解决了跨域问题),然后代理服务器会转发给真正的内部服务器上获取数据后,再将数据返回给客户端
例如:
我本地的前端地址:[http://localhost:8081/chart/demo.html](http://localhost:8081/chart/demo.html)
后端api地址:[http://localhost:8080/api/chart](http://localhost:8080/api/chart)
我访问本地的前端路径,此时通过8081端口,被nginx服务器监听到。
这时执行html中的ajax请求,访问后端api,具体ajax如下图
$.ajax({
type : "post",
//暂时用同步请求,异步有bug无法显示
async : true, //异步请求(同步请求将会锁住浏览器,用户其他操作必须等待请求完成才可以执行)
url : "/api/chart", //请求发送到TestServlet处
data : {},
dataType : "json", //返回数据形式为json
success : function(result) {
console.log(result);
//请求成功时执行该函数内容,result即为服务器返回的json对象
if (result) {
for(var i=0;i<result.length;i++){
nums.push(result[i]); //挨个取出销量并填入销量数组
}
}
}
})
nginx配置监听客户端的请求
nginx反向代理的代码配置:
nginx.conf 配置文件如下:
server {
undefined
listen 8081; //监听8081端口
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
undefined
root /Users/xx/project; //设置根路径
index index.html index.html;
}
location /api { //匹配url中带有api的,并转发到http://localhost:8080/api
rewrite ^/api/(.*)$ /$1 break; //利用正则进行匹配
proxy_pass http://localhost:8080; //转发的参数设定
}
4.document.domain+iframe 跨域
十六、请求发送和回应的全局封装
axios.interceptors.request.use(config => {
//在最后必须return config
config.headers.Authorization = sessionStorage.getItem('Authorization')
return config
})
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
if (response.status === 500) {
MessageBox.alert('请求失败,请联系管理员')
}
if (response.data.code === 403) {
MessageBox.alert('无权限访问')
}
if (response.data.error_code === 1003) {
MessageBox.alert('令牌超时').then(() => {
// 退出登陆
sessionStorage.removeItem('userInfo')
sessionStorage.removeItem('user')
sessionStorage.removeItem('Authorization')
window.location.reload()
this.$router.push('/login')
})
}
return response;
});
十七、vuex
十八、守卫
1.全局守卫
beforeEach((to,from,next)=>{调用next来resolve这个钩子})
beforeResolve((to,from,newxt=>{}))
afterEach((to,from)=>{}),
2.路由独享守卫
beforeEnter((to,from,newxt=>{}))
3.组件内部守卫
beforeRouteEnter((to, from, next) => {next可以传回调,回调里面可以用vm访问实例} ), 内部没有this,因为路由还没confirm
beforeRouteUpdate((to, from, next) => {}),可以拿到this
beforeRouteLeave((to, from, next) => {})
十九、vue生命周期实际运用
creatd 或者 beforeMount 一般可以做初始化数据的获取,掉接口获取数据等等
mounted 可以操作dom节点 通过$ref属性可以直接获取到dom节点
boforeDestory或者destory这里可以做善后工作,比如清空定时器、清除非指令绑定的事件等等
[url=https://imgtu.com/i/HjTgpt][img]https://s4.ax1x.com/2022/02/21/HjTgpt.png[/img][/url]
二十、vue常见的通信方案
父子组件通信: props; $parent/$children; provide/inject; $ref; $attrs/$listeners
兄弟组件通信: EventBus; Vuex
跨级通信: EventBus; Vuex; provide/inject; $attrs/$listeners
二十一、v-model实现原理
在原生表单元素中:
<input v-model="inputValue">
相当于
<input v-bind:value="inputValue" v-on:input="inputValue = $event.target.value">
在自定义组件中:
<my-component v-model="inputValue"></my-component>
相当于
<my-component v-bind:value="inputValue" v-on:input="inputValue = argument[0]"></my-component>
这个时候,inputValue接受的值就是input事件的回调函数的第一个参数,所以在自定义组件中,要实现数据绑定,还需要$emit去触发input的事件。
this.$emit('input', value)