代码重构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XO56tAfI-1589971833570)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1589010837908.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65kEqSAI-1589971833643)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1589022076208.png)]
项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1x5vBuLB-1589971833647)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588955408055.png)]
*单页面应用首屏加载慢
- Vue-router懒加载
Vue-router懒加载就是按需加载组件,只有当路由被访问时才会加载对应的组件,而不是在加载首页的时候就加载,项目越大,对首屏加载的速度提升得越明显。
- 使用CDN加速
在做项目时,我们会用到很多库,采用cdn加载可以加快加载速度。
- 异步加载组件
- 服务端渲染
服务端渲染还能对seo优化起到作用,有利于搜索引擎抓取更多有用的信息(如果页面纯前端渲染,搜索引擎抓取到的就只是空页面)
*单页面多页面区别
单页面的优点:
1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小
2,前后端分离
3,页面效果会比较炫酷(比如切换页面内容时的专场动画)
单页面缺点:
1,不利于seo
2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)
3,初次加载时耗时多
4,页面复杂度提高很多
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fayFxEn5-1589971833651)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588213585241.png)]
*单页面实现前进后退
1.前进:
history.forward();
history.go(1);
2.后退:
history.back();
history.go(-1);
3.获取记录个数:
history.length:
webpack
打包流程及原理
treeshaking
多页打包
优化
跨域
*为什么AJAX不能跨域访问?
在网页中写了一段js代码,使用ajax向淘宝发起登陆请求,因为很多数人都访问过淘宝,所以电脑中存有淘宝的cookie,不需要输入账号密码直接就自动登录了,然后小黑在ajax回调函数中解析了淘宝返回的数据,得到了很多人的隐私信息。
*同源策略
同源政策:不是同协议 同域名 同端口 的网页无法相互访问。
用form表单提交到不同源的网页是被允许的,因为 form 提交到另一个域名之后,原页面的脚本无法获取新页面中的内容,所以浏览器认为这是安全的。
而 AJAX 是可以读取响应内容的,因此浏览器不能允许你这样做。如果你细心的话你会发现,其实请求已经发送出去了,你只是拿不到响应而已。
所以浏览器这个策略的本质是,一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。
同源策略限制内容有:
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
JSONP
对script的资源引用没有同源限制,通过动态插入一个script标签,当资源加载到页面后会立即执行的原理实现跨域
允许用户传递一个callback或者开始就定义一个回调方法,参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
只支持GET请求而不支持POST等其它类型的HTTP请求,它只支持跨域HTTP请求
缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I1uK21IR-1589971833653)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588932515140.png)]
nginx反向代理
搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DLmqxnWv-1589971833654)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588953094846.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WwsgukF-1589971833656)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588953112719.png)]
CORS
该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin;浏览器判断该相应头中是否包含Origin的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。
CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,需要两方配合
跨域请求不仅解决了ajax也解决了cookie,要在ajax请求里加上
jquery:xhrFields: {withCredentials: true}, crossDomain: true
js: xhr.withCredentials = true
xhr.withCredentials 有什么用?
跨域请求是否提供凭据信息(cookie、HTTP认证及客户端SSL证明等)
也可以简单的理解为,当前请求为跨域类型时是否在请求中协带cookie。
当配置了xhr.withCredentials = true时,必须在后端增加 response 头信息Access-Control-Allow-Origin,且必须指定域名,而不能指定为*。
且res.setHeader(“Access-Control-Allow-withCredentials”,“true”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YRk9dWgW-1589971833657)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588932661968.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HptXS5Uj-1589971833658)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588932688537.png)]
1) 简单请求
只要同时满足以下两大条件,就属于简单请求
条件1:使用下列方法之一:
- GET
- HEAD
- POST
条件2:Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
2) 复杂请求
不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
"预检"请求的HTTP头信息:
"预检"请求用的请求方法是OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个源。
除了Origin
字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header
。
上面的HTTP回应中,关键的是Access-Control-Allow-Origin
字段,表示http://api.bob.com
可以请求数据。该字段也可以设为星号,表示同意任意跨源求。
Access-Control-Allow-Origin: *
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest
对象的onerror
回调函数捕获。
控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com.Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器回应的其他CORS相关字段如下。
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HbL66aCv-1589971833660)(C:\Users\wanglei\AppData\Roaming\Typora\typora-user-images\1588951781081.png)]
图片懒加载
1、offsetLeft
假设 obj 为某个 HTML 控件。
obj.offsetTop 指 obj 距离上方或上层控件的位置,整型,单位像素。
obj.offsetLeft 指 obj 距离左方或上层控件的位置,整型,单位像素。
obj.offsetWidth 指 obj 控件自身的宽度,整型,单位像素。
obj.offsetHeight 指 obj 控件自身的高度,整型,单位像素。
一、offsetTop 返回的是数字,而 style.top 返回的是字符串,除了数字外还带有单位:px。
二、offsetTop 只读,而 style.top 可读写。
三、如果没有给 HTML 元素指定过 top 样式,则 style.top 返回的是空字符串。
offsetLeft 与 style.left、offsetWidth 与 style.width、offsetHeight 与 style.height 也是同样道理。
2、clientHeight 就是透过浏览器看内容的这个区域高度。
scrollHeight 则是网页内容实际高度。数字
clientWidth、offsetWidth 和 scrollWidth 的解释与上面相同,只是把高度换成宽度即可。
clientWidth = width + padding
clientHeight = height + padding
offsetWidth = width + padding + border
offsetHeight = width + padding + border
3、scrollTop:已滚动过去的高度 “卷起高度” onscoll函数
4、clientTop:clientTop可以返回div的上边框的大小,其值为一个整数,没有单位。
移动端适配
在不同尺寸的手机设备上,页面“相对性的达到合理的展示(自适应)”或者“保持统一效果的等比缩放(看起来差不多)”;
1)viewport(scale=1/dpr)
2)rem
3)flex
4)vm/vh
<meta name="viewport" content="width=device-width, initial-scale=1">
Vue
*Vue实例里面的data属性为什么用函数返回?
当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
*jQuery与Vue的区别是什么?
JQuery和Vue的主要区别是JQuery主要是通过选择器来选取DOM,对其进行赋值,取值,事件绑定等操作,数据和页面是混合在一起的;Vue则是通过Vue对象将数据和视图完全分割开来,对数据进行操作,不再需要引用相应的DOM对象,实现了MVVM。
*生命周期
创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
(1)、什么是vue生命周期
答: Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
(2)、vue生命周期的作用是什么
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
(3)、vue生命周期总共有几个阶段
答:可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后
(4)、第一次页面加载会触发哪几个钩子
答:第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
(5)、DOM 渲染在 哪个周期中就已经完成
答:DOM 渲染在 mounted 中就已经完成了。
(6)、简单描述每个周期具体适合哪些场景
答:生命周期钩子的一些使用方法:
beforecreate : 可以在这加个loading事件,在加载实例时触发
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted : 挂载元素,获取到DOM节点
updated : 如果对数据统一处理,在这里写上相应函数
beforeDestroy : 可以做一个确认停止事件的确认框
nextTick : 更新数据后立即操作dom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YmRucxtD-1589971833663)(G:\html实例\笔记\lifecycle.png)]
在 MVC 模式中我们使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。
*MVVM
Model 代表数据模型,用纯JavaScript对象表示,数据和业务逻辑都是在Model层中定义。
View 代表UI 视图,负责对数据的展示,将数据模型转化成UI 展现出来。
ViewModel 负责监听Model中数据的改变并控制视图的更新,处理用户交互操作,是一个同步View 和 Model的对象。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
*双向绑定Object.defineProperty()
采用数据劫持结合发布者-订阅者模式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者。
1.监听器Observer,用来劫持并监听model所有属性,如果有变动就通知订阅者。
2.订阅者Watcher,可以收到属性的变化通知 并执行相应的回调函数。
3.解析器Compile,可以扫描和解析每个节点的相关指令,并替换模板数据,初始化视图 以及 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器。
事件监听
computed其实只是纯数据操作,需要返回数据结果。但是watch就可以监测某个数据发生了变更进行一系列的回调操作,不仅仅局限于返回数据,你也可以不返回。
计算属性具有缓存。计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 lastName和firstName都没有发生改变,多次访问 fullName计算属性会立即返回之前的计算结果,而不必再次执行函数。
而侦听器watch是侦听一个特定的值,当该值变化时执行特定的函数。例如分页组件中,我们可以监听当前页码,当页码变化时执行对应的获取数据的函数。
过滤
*自定义指令
vue.directive(‘xxx’,{bind:function(el){…}},{update:function(){…}})
*key
用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
key的作用主要是为了高效的更新虚拟DOM
Vue的路由实现:hash模式 和 history模式
**hash模式:**在浏览器中符号“#”,#/hello称之为hash,用window.location.hash读取;
hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
**history模式:**history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
mode: 'history',
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
二、404 错误
1、hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.abc.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误;
2、history模式下,前端的url必须和实际向后端发起请求的url 一致,如http://www.abc.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。
组件通信
-
父传子用 props传递(异步,有可能数据还没获取到就渲染了,所以监听)
父亲: <son :message="msg"></son> data(){ return{ msg:'i am father' } } 儿子: props:['message'], <div>{ {message}}</div>
-
子传父用$emit传递
<input @click="sendMsg" type="button" value="给父组件传递值"> <script> export default { data () { return { //将msg传递给父组件 msg: "我是子组件的msg", } }, methods:{ sendMsg(){ //func: 是父组件指定的传数据绑定的函数,this.msg:子组件给父组件传递的数据 this.$emit('func',this.msg) } }
<child @func="getMsgFormSon"></child> import child from './child.vue' export default { data () { return { msgFormSon: "this is msg" } }, components:{ child, }, methods:{ getMsgFormSon(data){ this.msgFormSon = data console.log(this.msgFormSon) } }
-
路由参数中:params 看不见参数 post
this.$router.push({name:'componentB',params:{num:123}}) this.$route.params.num
path:'/hi/:num'
-
路由参数中:query ?num=123 嫩看见 get
A中: this.$router.push({path:'componentB',query:{num:123}}) B中: this.$route.query.num
-
localStorage,sessionStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
window.localStorage.setItem('defaultCity', JSON.stringify(state.city)); if (!defaultCity){ defaultCity = JSON.parse(window.localStorage.getItem('defaultCity')) }
-
e m i t / emit/ emit/on这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
var Event=new Vue(); Event.$emit(事件名,数据); Event.$on(事件名,data => {}); 组件ab var Event = new Vue();//定义一个空的Vue实例 methods: { send() { Event.$emit('data-b', this.age); } } 组件c mounted() {//在模板编译完成后执行 Event.$on('data-a',name => { this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event }) Event.$on('data-b',age => { this.age = age; }) }
*单页应用首屏加载速度慢,白屏时间过长
- 将公用的JS库通过script标签在index.html进行外部引入,减少我们打包出来的js文件的大小,让浏览器并行下载资源文件,提高下载速度
- 在配置路由的时候进行路由的懒加载,在调用到该路由时再加载次路由相对应的js文件
- 加一个首屏loading图或骨架屏,提高用户的体验
- 尽可能使用CSS Sprites和字体图标库
- 图片的懒加载等
Vuex
Vuex是通过全局注入store对象,来实现组件间的状态共享。
(1)vuex是什么?怎么使用?哪种功能场景使用它?
vue框架中状态管理。在main.js引入store,注入。新建一个目录store,…… export 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
(2)vuex有哪几种属性?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
vuex的State特性
A、Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,不可以直接修改里面的数据
B、state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
C、它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
vuex的Getter特性
A、getters 可以对State进行计算操作,它就是Store的计算属性
B、 虽然在组件内也可以做计算属性,但是getters 可以在多组件之间复用
C、 如果一个状态只在一个组件内使用,是可以不用getters
vuex的Mutation特性(定义的方法动态修改Vuex 的 store 中的状态或数据)
Action (异步)类似于 mutation(同步),不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
(3)不用Vuex会带来什么问题?
可维护性会下降,想修改数据要维护三个地方;
可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;
增加耦合,大量的上传派发,会让耦合性大大增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。
小问题
v-show和v-if指令的共同点和不同点
v-show指令是通过修改元素的display的CSS属性让其显示display或者隐藏none;
v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果;
使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
r o u t e 和 route和 route和router的区别
答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。只读
而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。只写 push
vue常用的修饰符?
答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用
什么是vue的计算属性?
答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计