这是我在春招期间学习的时候总结的vue笔记,如果需要word文档可以单独私聊我,直接在csdn私聊就行
Vue
作者:爱笑的橘子
一.单页面应用的缺点
1.优点
页面里的内容改变不需要重新加载整个页面
2.缺点
首屏加载慢
二.MVVM响应式原理(数据双向绑定)
1.Vue响应式原理主要通过以下三点来实现
数据劫持: vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
阐述一下你所理解的MVVM响应式原理
vue是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时,发布消息给依赖收集器(dep中的subs),去通知(notify)观察者,做出对应的回调函数,去更新视图
MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据model变更的双向绑定效果。
通过数据劫持监听数据变化
通过模板编译进行数据渲染
通过发布订阅模式实现视图与数据的同步
//数据劫持
var a = {}
var name = ‘’;
Object.defineProperty(a, ‘name’, {
set: function (value) { // 修改数据时执行
name = value;
console.log(‘名字:’ + value);
},
get: function () { //读取数据时执行
return ‘《’ + name + ‘》’
}
})
a.name = ‘李爽’;
console.log(a.name)
Vue2实现数据劫持是利用ES5的 Object.defineProperty , 利用它会为对象添加get/set方法,从而监听属性的读取与修改。
三.生命周期
vue生命周期共分为四个阶段
1.实例创建
beforeCreate --创建前
触发的行为:vue实例的挂载元素el:#app和数据对象data都为undefined,还未初始化。
在此阶段可以做的事情:加loading事件
created --创建后
触发的行为:vue实例的数据对象data有了,el还没有
在此阶段可以做的事情:解决loading,请求ajax数据为mounted渲染做准备
2.DOM渲染
beforeMount --渲染前
触发的行为:vue实例的el和data都初始化了,但还是虚拟的dom节点,具体的data.filter还未替换
在此阶段可以做的事情:。。。
mounted --渲染后
触发的行为:vue实例挂载完成,data.filter成功渲染
在此阶段可以做的事情:配合路由钩子使用
3.数据更新
beforeUpdate --更新前
触发的行为:data更新时触发
在此阶段可以做的事情:。。。
updated —更新后
触发的行为:data更新时触发
在此阶段可以做的事情:数据更新时,做一些处理(此处也可以用watch进行观测)
4.销毁实例
beforeDestroy —销毁前
触发的行为:组件销毁时触发
在此阶段可以做的事情:可向用户询问是否销毁
destroyed —销毁后
触发的行为:组件销毁时触发,vue实例解除了事件监听以及和dom的绑定(无响应了),但DOM节点依旧存在
在此阶段可以做的事情:组件销毁时进行提示
5.父子组件生命周期
父子组件的生命周期是一个嵌套的过程
渲染的过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父beforeUpdate->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
四.nextTick
1.created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
2.在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
五.computed和watch的区别
不要在 computed 或 watch 中,去修改所监听的数据的值,尤其是 computed;如果这样做,可能导致一个无线循环的触发。
1.Computed
使用:复杂逻辑,需要重复使用计算属性,多个数据的变化影响一个数据
必须要有return,不支持异步
假如此时有一个需求:对message进行反转并展示到模板中
{{ message.split("").reverse().join("") }}
直接在模板中操作,过多的逻辑运算会让模板变得重且难以维护,而且这种转化无法复用,
定义一个计算属性:reverseMessage,其值为一个函数并返回我们需要的结果。之后在模板中就可以像使用message一样使用reverseMessage。
用methods也能实现,但是在这种情况下计算属性相较于methods是有很大优势的,这个优势就是计算属性存在缓存。
我们为什么需要缓存?
假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
如果使用methods实现前面的需求,当message的反转结果有多个地方在使用,对应的methods函数会被调用多次,函数内部的逻辑也需要执行多次;而计算属性因为存在缓存,只要message数据未发生变化,则多次访问计算属性对应的函数只会执行一次。
{{ message }}
{{ reverseMessage }}
通过声明 immediate 选项为 true,可以立即执行
通过使用 deep: true 进行深入观察,这时,我们监听 obj,会把 obj 下面的属性层层遍历,都加上监听事件,这样做,性能开销也会变大
data: {
obj: {
a: ‘123’
}
},
watch: {
obj: {
handler () {
console.log(‘obj.a changed’)
},
immediate: true
// deep: true
}
}
}
如何优化?只监听obj.a
data: {
obj: {
a: ‘123’
}
},
watch: {
‘obj.a’: {
handler () {
console.log(‘obj.a changed’)
},
immediate: true
// deep: true
}
}
}
计数器:{{counter}}
firstName:
lastName:
fullName: {{fullName}}
异步操作
在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
Ask a yes/no question:
{{ answer }}
props:{
toz1:String, //类型
},
2.子传父
子组件设置一个KaTeX parse error: Expected '}', got 'EOF' at end of input: … this.emit(‘getz1’,this.toff)
}
@getz1=‘getFromZ1’
getFromZ1(val){
this.fromZ1=val
}
3.任何组件
新建一个全局的空vue对象,利用emit发送,on接收
Vue.prototype.$bus=new Vue()
toz2(){
this.
b
u
s
.
bus.
bus.emit(‘xiongdi’,this.z1)
}
this.
b
u
s
.
bus.
bus.on(‘xiongdi’,(val)=>{
this.fromZ2=val
})
4.Vuex
5.嵌套组件
使用场景:中间子组件做一些操作
a
t
t
r
s
父
组
件
在
第
一
层
子
组
件
中
设
置
的
所
有
v
−
b
i
n
d
/
:
和
t
i
t
l
e
,
在
子
组
件
用
attrs 父组件在第一层子组件中设置的所有v-bind/:和title,在子组件用
attrs父组件在第一层子组件中设置的所有v−bind/:和title,在子组件用attrs接收(只能接收没有被props接收的值)(返回的是一个对象),后面的组件用v-bind把attrs传个下一个子组件
第一层子组件
<z1 :toz1=‘zz1’ :toz2=‘zz2’ :toz3=‘zz3’ title=“www” @getz1=‘getFromZ1’>
props:{
toz1:String, //类型
}
console.log(this.$attrs) //{toz2,toz3,title}
第二层子组件
props:{
toz2:String
}
console.log(this.$attrs) //{toz3,title}
l
i
s
t
e
n
e
r
s
接
收
父
组
件
非
原
生
的
事
件
(
原
生
事
件
:
c
l
i
c
k
等
)
c
o
n
s
o
l
e
.
l
o
g
(
t
h
i
s
.
listeners接收父组件非原生的事件(原生事件:click等) console.log(this.
listeners接收父组件非原生的事件(原生事件:click等)console.log(this.listeners) //{getz1}
6.provide / inject
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来获取变量
provider和inject的绑定是不可响应的,父组件的name改变,子组件的name不变,还是原来的值,可以用vue.observable(让一个对象可响应)来解决
provide: {
name: “李爽”,
},
//以上不可响应,可以用下面的方式解决
// provide() {
// this.theme = Vue.observable({
// color: “blue”
// });
// return {
// theme: this.theme
// };
// }, //接收来自子组件的值
inject: [‘name’]
console.log(this.name) //李爽
7.
r
e
f
s
、
refs、
refs、parent、
c
h
i
l
d
r
e
n
r
e
f
的
用
法
获
取
d
o
m
信
息
c
o
n
s
o
l
e
.
l
o
g
(
t
h
i
s
.
children ref的用法 获取dom信息 console.log(this.
childrenref的用法获取dom信息console.log(this.refs.h1) //获取dom信息
我是父组件
获取子组件实例:在子组件标签中定义ref,用$refs获取
ref=“abc”
mounted() {
console.log(this.KaTeX parse error: Expected 'EOF', got '}' at position 25: …; //获取子组件实例 }̲ 父组件中 conso…children); //获取子组件实例
子组件中
mounted(){
console.log(this.$parent) //获取父组件实例
}
详细过程
我是父组件
接收到子组件的值:{{fromZ1}}我是子组件1
接收到了父组件的值:{{ toz1 }}接收到了兄弟组件z2的值:{{fromZ2}}
我是子组件2
七.路由
1.hash模式
vue-router默认是hash模式
#后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash 值的变化,会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作
2.history模式
切换到history模式:router/index.js
mode:‘history’
back():后退
forward():前进
go():接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back()。
history.pushState(state,title,url)
参数
state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
案例步骤说明
1.当前网址是example.com/1.html
2.执行代码
var stateObj = { foo: ‘bar’ };
history.pushState(stateObj, ‘page 2’, ‘2.html’);
3.浏览器动作
浏览器地址栏立刻显示example.com/2.html,但并不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的最新记录
4.点击一次倒退按钮
url将显示1.html,内容不变
触发 popstate 事件
不允许输入跨域的网址
history.replaceState()
replaceState()是用来修改当前的历史记录(history实体),而不是创建一个新的历史记录
所以,当执行完history.replaceState()后,点击返回按钮照样会返回上一个一面。
应用场景:当需要更新一个state对象或者当前history实体时,可以用replaceState()来实现
3.区别:
前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应路径的路由处理,则会返回404错误,要想解决这一问题需要后端配合,将不存在的路径重定向到入口文件
4.路由的钩子函数
全局守卫
router.beforeEach 全局前置守卫 进入路由之前
router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
router.afterEach 全局后置钩子 进入路由之后
// main.js 入口文件
import router from ‘./router’; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log(‘afterEach 全局后置钩子’);
});
to,from,next 这三个参数:
to和from是将要进入和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。
next:Function 这个参数是个函数,且必须调用,否则不能进入路由(页面空白)。
next() 进入该路由。
next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
我们可以这样跳转:next(‘path地址’)或者next({path:’’})或者next({name:’’})
登录
以下是错误示范:当我们跳转到login之后,因为此时还是未登录状态,所以会一直跳转到login然后死循环,页面一直是空白的
router.beforeEach((to, from, next) => {
if(登陆条件){
next()
}else{
next({ name: ‘login’ });
}
});
正确
router.beforeEach((to, from, next) => {
if (to.name !== ‘Login’ && !登录条件) next({ name: ‘Login’ })
// 如果用户未能验证身份,则 next
会被调用两次
next()
})
路由独享守卫
// index.js路由文件
const router = new VueRouter({
routes: [
{
path: ‘/foo’,
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// …
}
}
]
})
路由组件内的守卫
beforeRouteEnter 进入路由前
在路由独享守卫后调用 不!能!获取组件实例 this
,组件实例还没被创建
beforeRouteUpdate (2.2) 路由复用同一个组件时
可以获取组件实例this,举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候
beforeRouteLeave 离开当前路由时
可以获取组件实例this,导航离开该组件的对应路由时调用,我们用它来禁止用户离开,比如还未保存草稿,或者在用户离开前,将setInterval销毁,防止离开之后,定时器还在调用。
5.路由钩子执行顺序
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
6.路由传值
作用:渲染路由匹配到的组件,可以在嵌套路由时使用
<button @click=“this.
r
o
u
t
e
r
.
p
u
s
h
(
n
a
m
e
:
′
h
o
m
e
′
,
p
a
r
a
m
s
:
i
d
:
′
1
′
)
"
>
<
/
b
u
t
t
o
n
>
<
b
u
t
t
o
n
@
c
l
i
c
k
=
"
t
h
i
s
.
router.push({name:'home',params: {id:'1'}})"></button> <button @click="this.
router.push(name:′home′,params:id:′1′)"></button><button@click="this.router.push({path:’/home’,query: {id:‘1’}})”>
this.
r
o
u
t
e
r
.
r
e
p
l
a
c
e
(
)
(
用
法
同
p
u
s
h
)
t
h
i
s
.
router.replace() (用法同push) this.
router.replace()(用法同push)this.router.go(n)向前或者向后跳转n个页面,n可为正整数或负整数
路由配置
path: “/home/:id”
path: “/home:id”
八.Keep-alive
作用:缓存组件,避免重新渲染
1.Activated
第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用
mounted=> activated 进入缓存组件 =>组件销毁destroyed/或离开缓存deactivated => activated 进入当前缓存组件 => 执行 beforeRouteEnter回调
2.deactivated
组件被停用(离开路由)时调用
组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
3.问题
一个组件被缓存了,再次进入的时候
beforeCreate,created,beforeMount,mounted,beforeDestroy,destroyed都不会触发。
九.将路由导航、keep-alive、和组件生命周期钩子结合起来的触发顺序
假设是从a组件离开,第一次进入b组件
beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
beforeEnter: 路由独享守卫
beforeRouteEnter: 路由组件的组件进入路由前钩子。
beforeResolve:路由全局解析守卫
afterEach:路由全局后置钩子
beforeCreate:组件生命周期,不能访问this。
created:组件生命周期,可以访问this,不能访问dom。
beforeMount:组件生命周期
deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
mounted:访问/操作dom。
activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
执行beforeRouteEnter回调函数next。
十.虚拟Dom
1.template模板转换成视图的过程
模板>执行渲染函数>生成虚拟dom>diff算法>真实dom
在Vue的底层实现上,Vue将模板编译成虚拟DOM渲染函数。结合Vue自带的响应系统,在状态改变时,Vue能够智能地计算出重新渲染组件的最小代价并应到DOM操作上
渲染函数:渲染函数是用来生成Virtual DOM的
VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点
diff算法:虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新
2.虚拟Dom是什么
Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树
作用:
虚拟DOM的最终目标是将虚拟节点渲染到视图上
其实虚拟DOM在Vue.js主要做了两件事:
提供与真实DOM节点所对应的虚拟节点vnode
将虚拟节点vnode和旧虚拟节点oldVnode进行对比,然后更新视图
优点:
1.跨平台:虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关
2.操作 DOM 慢,js运行效率高。我们可以将DOM对比操作放在JS层,提高效率
3.提升渲染性能
缺点:
1.无法进行极致优化:虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
2.首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML(可以读取/替换指定dom元素内的内容)替换慢。
3.diff算法
diff 算法包括几个步骤:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把所记录的差异应用到所构建的真正的DOM树上,视图就更新了
十一.笔记
Vue和jquery不能一起用,jq代码在mounted周期内运行,此时vue已经渲染完毕,jq才可以找到相应的dom
如果需要jq更新dom,则在mouned周期内不被允许,需要在updated进行操作
也可以直接把script标签放在vue所在标签的后面
key属性可以用来提升v-for渲染的效率
在最外层加v-loading,在beforeCreate把v-loading打开,在Created关闭
@input输入框改变就触发
十二.Axios
传参数
十三.Git
Git add .
Npm run commit
browser-sync start --server --files “css/*.css, *.html”