一.箭头函数
1.三种定义函数的方式
比如改第一种方式中aaa那个函数,用箭头函数写就是如下图,因为aaa函数没有参数,所以括号里没东西,而且也没有返回值。
2.参数和返回值的问题
函数有多行代码时就照常写。
当函数中只有一行代码时不仅可以省略return,而且可以把这一行代码的返回值给返回出去。
那么如果说没有返回值的时候呢?还是会执行函数中的代码,而这个函数的结果也可以看到。(图中console.log的返回值是undefined,所以打印函数的值就是undefined)
3.箭头函数中this的使用(重点)
(1)什么时候使用箭头函数?
当我们准备把一个函数作为参数,传到另外一个函数中的时候,这时候使用箭头函数是最多的。
比如说:
以前我们写setTimeout函数会如下图第一种写法,但使用箭头函数之后,我们就会用第二种写法,因为第二种写法更加简洁。(setTimeout本身是一个函数,setTimeout又接收了一个函数( )=>{ } )
作为另外一个函数的参数的时候,我们就会使用箭头函数,但是在使用箭头函数的时候,可能会发生this的指向错误,接下来我们详细看一下。
(2)this的指向
首先我们在两种写法里都打印一下this。
现在我们写个obj对象,里面有个aaa函数,在aaa里面写了两个setTimeout,在它里面各自打印this,一个是普通的函数,一个是箭头函数。如下:
// 箭头函数中的this引用的是最近作用域中的this
const obj = {
aaa() {
// 1
setTimeout(function() {
console.log(this); // Window
})
// 2
setTimeout(()=> {
console.log(this); // obj对象
})
}
obj.aaa();
}
为什么1里面的this就是window,2里面就是obj对象呢?
1里的函数在调用时是通过call函数,而call是把window作为第一个参数传进去,所以this打印出来是window,但箭头函数不是这种调用方式。
2的箭头函数作用域里本身没有this这样一个变量或者关键字,它的this是通过向外一层层查找,直到有this的定义(确实存在一个this)。
上面的2中的this,就是向外找一层是aaa函数作用域,由于一个函数一旦是在对象里定义,那么在函数里面用this就是用的当前对象,所以aaa函数有this,那么2里的箭头函数的this找到的就是aaa函数作用域的this,也就是obj。
(3)练习
const obj ={
aaa() {
setTimeout(function() {
setTimeout(function() {
console.log(this); // window(这种格式都是window)
})
setTimeout(()=> {
console.log(this); // window(往外找一层,还是在第一个setTimeout里面,而这里调用了call方式传进去的方法,call会指定方法中的this为window)
})
})
setTimeout(() => {
setTimeout(function(){
console.log(this); // window
})
setTimeout(()=>{
console.log(this); // obj(往外找一层:在setTimeout(() =>里面,依旧没有this,再往外找一层,在aaa作用域里了,所以指向obj)
})
})
}
}
总结:匿名函数中的this指向window,而箭头函数中的this会往外找,如果外层是匿名函数那this就是window,如果是箭头函数就继续往外找。
并且function函数的this是由调用者决定,谁调用指向谁,回调函数中不太好确定this是谁,箭头函数相当于能确定this,如果要嵌套的话,只要遇到一个function,就由function的this作为最终this。
二.vue-Router
1.什么是路由
内网IP:在某一区域(比如家、公司)使用的ip。
而ip地址必须唯一(防止发送错乱)。
猫有个公网ip,也是个范围,永远不会重复,也是唯一标识。
比如现在通过网络发送一条信息,发出去的就是个公网ip,然后通过猫到路由器的位置,路由连了许多用户,那么怎么确定哪个用户是该收到信息的人呢?路由里存在一个映射表,把内网ip和每台电脑的mac地址(mac地址是网卡的唯一标识)一一对应起来。那么当信息到路由器之后,它会先查询mac地址,再根据mac地址查询到对应的内网ip,再根据内网ip决定信息发送到的用户。
2.前端渲染、后端渲染、SPA及前端路由阶段一:后端渲染(jsp/php),比如现在在浏览器输入一个url,那么就将这个url发到服务器里,服务器拿到地址之后,会先解析,看到底要什么网页,解析完是个淘宝首页(举例),比如它服务器是用java开发的,那么知道了需要的页面之后,会在后台通过jsp这个技术,会直接在这里把网页给写好,这个网页包含html+css+java(java的作用是从数据库中读取数据,并且将它动态的渲染到当前页面中),也就是说网页在服务器中已经展示完全了,就是最终网页,渲染好之后,直接将最终网页传给浏览器,传给浏览器的东西只有html+css(当然html包含一些数据,但最终浏览器上只有html+css)。点击到其他链接也是同理。
总结:后端渲染就是在后端通过一些技术(jsp或php)在服务器渲染好(服务端渲染/后端渲染)
阶段二:前后端分离阶段(后端只提供数据不负责任何界面内容),一般来说有三个角色,浏览器,服务器(分为静态资源服务器和提供API接口的服务器,一般两个服务可能会合并到一个服务器上),还有数据库。
前端向服务器发送ajax请求,服务器把数据给前端,然后前端动态的把数据放到浏览器上渲染。
但我们前端开发的代码(html/css),真正来说,并不是一开始就在浏览器上面,一开始是用户输入网页的url之后,必然需要去静态服务器(前端写的所有代码都放到静态服务器)拿东西(html+css+js代码),那么这三个东西已经到浏览器了,html、css浏览器拿到之后可以直接渲染,但js代码浏览器是需要执行(比如请求接口的代码$.ajax(url)之类的),那么这种接口请求就要向api服务器发起,去拿到api相关的资源,然后api服务器再把接口对应的数据返回到浏览器。那么浏览器会拿到大量的数据,再把数据通过其他的js代码插入创建的标签里面,最终浏览器再讲js相关的东西渲染到整个网页里面。
总结:数据不是在服务器渲染好的,而是通过ajax请求拿到数据之后,自己写的js代码,把网页渲染出来的。(前端渲染:浏览器中显示的网页的大部分内容,都是由前端写的js代码在浏览器中执行,最终渲染出来的网页)
阶段三:单页面复应用阶段,整个网页只有一个html页面。
这种阶段的静态资源服务器和阶段二的不太一样,在前后端分离的阶段,是有很多套html+css+js,每一套分别对应不同的url(也有一些共用的东西,但除开共用的,主要还是成对的放在静态资源服务器里:1.html+css+js->url1; 2.html+css+js->url2)
在单页面复应用里,只有一个html+css+js,它就包含了全部的内容,假如说我们输入了一个网站,它现在所有东西都放在一起,也就是只有一个html、一个css、一个js,现在会从静态服务器里把html+css+js(全部资源)请求到本地浏览器,但是并没有全部执行渲染,那么如何控制页面渲染的是我们想要看的网页呢,就要依靠前端路由,前端路由里配置了一些映射关系,当点击跳转到某个页面的时候,就会生成一个url:home/router1;home/router2,当跳转到对应的路由时,它不会像阶段二中那样,到新页面再去静态服务器请求资源,而前端路由会生成一个url,而生成的url不会像服务器请求其他资源,而只会通过js代码的判断,从那一整套html+css+js里面抽取出来当前这个路由的页面需要显示的一些资源,放到浏览器显示(一个vue组件就是一个页面,在打包的时候全部打包到一个js里面,浏览器出现哪个url,就从全部资源里面找到属于这个网页的组件相关的东西,然后渲染到浏览器上)。
映射关系:SPA页面必须有前端路由做支撑,而前端路由就是用于映射浏览器上面类似home/router1这种url和全部资源的js里面到底要渲染哪个组件的。
谁在管理url和页面的映射关系:前端路由。
前端路由的核心:改变url,但页面不进行整体的刷新。
3.两种修改url且页面不刷新的方法
(1)url的hash
(2)HTML5的history模式:pushState
pushState有很多参数,第一个data对象可以不传,第二个参数title也先不传,第三个参数url是主要要传的。
push是往history栈(先入后出)里面压东西,那么当前显示的路由比如‘/’,就是最新压入的,也就是在栈顶,那么我们运行history.back()之后,就会移除当前栈顶,跳到倒数第二个栈上,也就是回到上一层。
这种返回的结果就是弹栈和入栈,而且会对之前返回的东西进行记录,也就是浏览器前进后退时,不发生刷新的跳转url。
还有一个replaceState,是用新的url替换之前的url,它就不是入栈出栈永远显示栈顶且保留历史记录的操作,但replace不能返回。
另外还有go方式,里面的参数可以填写-1(出栈跳转到上一层),1(跳转下一层),2(跳转下两层)
三.安装和使用vue-Router
1.路由的搭建
挂载到vue实例中
2.路由的使用
运行结果:
四.路由的默认值和修改hash为history模式
hash带#不是很好看
2.router-link的属性补充
简化
再简化
在路由里修改,定义类名统一修改样式,.active还是如上图写到样式里。
五.动态路由
(1)在路由里拼接这个userId
(2)要拼接data里写的userId,不能直接写成to="/user/userId",因为这里会被直接当成字符串使用,而不是一个值,所以我们动态绑定一个user
(3)在界面里拿到userId
使用$route属性,route是哪个路由处于活跃状态,拿到的就是哪个路由对象,可以理解成拿到的是当前组件。
这里注意区分$router,router是调用push、replace方法进行路由的跳转,它的本质是下图中创建的大的路由对象,它是VueRouter实例出来的。
可以把router看成整个路由器,route看成连接路由器的设备。
那我们如何在界面里动态的拿到userId呢?如下图所示:
首先创建user组件,在路由里配置映射关系
然后在进行跳转的动态绑定,拼接上映射里写的东西
最后在跳转到的界面,通过$route.params.xxx方式拿到传过来的拼接的值。
六.路由的懒加载
(1)懒加载做了什么
(2)懒加载所呈现的效果
它所呈现的效果如下图右边的打包文件:路由懒加载多打包出来两个js文件,有两个路由懒加载,所以多打包出来两个js,这两个js不会一开始加载页面就从服务器请求下来,它会等到用这个页面的时候再请求下来。
(3)使用懒加载的三种方式
七.路由的嵌套
在创建好子组件之后,对于路由的配置进行如下操作,
八.路由参数传递
1.路由跳转时,我们希望它会携带一些参数。
在我们最开始显示界面时,会显示我们的App.vue,它是我们的入口,因为配置了默认路径,所以App.vue里面看到的就是我们的首页。
对于我们路由的参数的传递,前面说到的userId,使用$route.params.XXX,就是一种传递方式。
那么我们如何在App.vue里面通过query方式传到profile.vue里面呢?
现在我们来举个例子:
路由的引入,配置和使用都在前面介绍过,上图也有展示,这里不赘述了。
下图中,路由的to属性先进行动态绑定(否则就会成为字符串形式),然后路由的路径和参数被包在一个大对象里。
运行效果:
URL的组成-----协议://主机:端口/路径?查询#片段(片段是hash值)
英文写法-----scheme://host:port/path?query#fragment
那我们如何把query里的东西取出来呢?
在当前组件里直接写 $route.query.XXX即可。
2.不通过router-link的方式写跳转并携带参数
当我们需要携带数据比较多的时候,就可以使用这种方式给profile传递参数。
接下来继续在profile里面通过$route.query.XXX获得从App来的参数。
九.$route 和 $router 的区别
我们在代码里分别打印this.$route和router
结果如下,两个是一样的。任何组件 this. $router拿到的都是一样的,都是new出来的router大对象,所以才能在任何组件里对 $router做push、replace等操作。
再打印一个 $route,
结果如下图,
由图可知this. $route打印出来的是当前活跃的路由对象,就是在router-index.js里面配置的当前组件的路由。
以上就是两个对象的区别。
原理:所有的组件都继承自Vue的原型
在我们挂载router的时候,会先初始化,再经过在原型上定义属性,注册全局组件,就可以再全局使用这两个属性了。
十.vue-Router全局导航守卫
1.为什么使用导航守卫
2.全局守卫的使用方式
3.关于导航守卫的补充
钩子(hook)= 回调
路由独享守卫,组件内的守卫用到的时候自己去搜索一下如何使用。
十一.vue-router-keep-alive
首先我们复习一下生命周期函数的知识
生命周期钩子
beforeCreate,created,beforeMonted,mounted.beforUpdate,update,beforDestroy,destroyed
当在组件之间切换的时候都会请求一些请求过的数据,每次请求都会导致重复渲染影响性能。这些数据可以存到缓存。此时使用keep-alive将组建包裹起来。但这样以上八种生命周期钩子将失效。取而代之的时activate和deactivated(与keep-alive相关联)。
activate:是在被包裹组建被激活的状态下使用的生命周期钩子
deactivated:在被包裹组件停止使用时调用
接下来我们详细说一下生命周期函数的内容,
把所有生命周期打印一遍
import Vue from 'vue'
const app = new Vue({
// el: '#root',
template: '<div>{{text}}</div>',
data: {
text: 0
},
beforeCreate () {
console.log(this, 'breforeCreate')
},
created () {
console.log(this, 'created')
},
beforeMount () {
console.log(this, 'beforeMount')
},
mounted () {
console.log(this, 'mounted')
},
beforeUpdate () {
console.log(this, 'beforeUpdate')
},
updated () {
console.log(this, 'updated')
},
activated () {
console.log(this, 'activated')
},
deactivated () {
console.log(this, 'deactivated')
},
beforeDestroy () {
console.log(this, 'beforeDestroy')
},
destroyed () {
console.log(this, 'destroyed')
}
})
app.$mount('#root')
显示结果
结果依次显示 “breforeCreate” “created” “beforeMount” “mounted”,说明在new Vue()时,这四个方法执行了。
breforeMount 和 mounted
如果把el:'#root'
注释掉,就只显示“breforeCreate” “created” ,因为mount的作用就是把vue组件生成的html内容,挂载到html节点上,所以当我们没有指定el:'#root'
或通过$mount进行,是不会挂载到html节点上的。
breforeCreate 和 created
而 breforeCreate created 在初始化阶段就执行了。
beforeUpdate 和 updated
数据更新时,才会执行。
例如,每一秒钟更改数据,相应的每秒都会执行这两个生命周期
setInterval(() => {
app.text = app.text += 1
}, 1000)
beforeDestroy 和 destroyed
vue实例销毁时执行。
例如,设置一秒钟后销毁,控制台就会显示 “beforeDestroy” 和 “destroyed”
setTimeout(() => {
app.$destroy()
}, 1000)
activated 和 deactivated
和vue中一个原生的组件 keep-alive有关系。
分别打印出不同周期对应的$el
beforeCreate () {
console.log(this.$el, 'breforeCreate')
},
created () {
console.log(this.$el, 'created')
},
beforeMount () {
console.log(this.$el, 'beforeMount')
},
mounted () {
console.log(this.$el, 'mounted')
},
显示结果
undefined "beforeCreate"
undefined "created"
<div id="root"></div> "beforeMount"
<div>0</div> "mounted"
可以看到
beforeCreate 和 created 的 $el
是undefined,所以 beforeCreate 和 created 阶段是不能进行dom操作的,因为拿不到 dom 节点。
beforeMount 时,$el
变成了我们写在 html 中 div 节点。
mounted 时, $el
变成了 template 中的html,说明覆盖了html原来的 div 节点。
mounted之后,我们调用的所有生命周期方法,拿到的节点,都是渲染之后的节点。
所以一般
- 做 dom 相关操作会在 mounted 阶段
- 数据相关操作,可以在 created 或 mounted 阶段
生命周期的调用顺序
beforeCreate created beforeMount mounted 都是一次性的,组件只会调用一次。
beforeMount mounted 在服务端渲染,是不会被调用的,服务端渲染过程中,只会调用 beforeCreate created,因为 mount 是和 dom 操作相关的,服务端根本没有 dom 执行的环境, 所以不会有。
当数据发生变化时,beforeUpdate 和 updated 会调用。
当组件销毁时,beforeDestroy 和 destroyed 会调用。
声明周期中 VUE 实例有哪些区别
在不同的生命周期阶段,this.$el
是不同的,而在 mounted之后,一般不会改动 this.$el
,而是围绕阶段做某些操作,要尽量避免 this.$el
的变动,它会导致一些 vue 错误。
理解生命周期就是理解一张图
1.init,new Vue() 先执行 init 操作,这个操作是默认执行的。
(1)init Events $ Lifecycle,调用 beforeCreate,所以此时,事件OK,但reactive不OK,所以这个阶段不要修改数据 data 中的数据。
(2)init injections $ reactivity,调用 created ,ajax请求获取数据赋值,最早在 created 阶段做。
2.判断 Has “el” option
(1)如果有,执行下一步。
(2)如果没有,等我们调用 vm.$mounted(el)
。
3.判断 Has “template” options
(1)如果有,把 template 解析生一个 render 函数。render 函数会用 template 中的 html 去覆盖 html 中的 div 标签。在使用 .vue
文件进行开发的过程中,是没有 template 的,我们在 .vue
文件中写的 template 都经过了 vue-loader 处理,直接变成了 render 函数,放在vue-loader 解析过的文件中;这样做的好处,把 template 解析成 render 函数,比较耗时,vue-loader 处理后,我们在页面上执行代码时,效率会变高。
(2)如果没有,Compile el’s outerHTML as template,最终还是变成render函数。
4.有了 render 函数之后
(1)beforeMount 执行
5.执行 render 函数
(1)Create vm.$el and replace “el” with it
6.执行 render 函数之后
(1)mounted 执行
7.mounted之后,实例创建完成,后续过程,都是通过外部触发进行的。
8.当数据变化时
(1)beforeUpdate 执行
(2)Virtual DOM re-render and patch
(3)updated 执行
9.当组件销毁时
(1)beforeDestroy 执行
(2)Teardown watchers,child comonents and event listeners
(3)destroyed 执行
render 函数
直接使用 render 函数和使用 template 一样的。
import Vue from 'vue'
const app = new Vue({
// template: '<div>{{text}}</div>',
data: {
text: 0
},
render (h) {
return h('div', {}, this.text) // 参数1,创建的标签;参数2,对象配置;参数3,标签内容
},
})
app.$mount('#root')
render 函数执行时机
render (h) {
console.log('render function invoked')
return h('div', {}, this.text)
},
控制台结果
undefined "beforeCreate"
undefined "created"
<div id="root"></div> "beforeMount"
render function invoked
<div>0</div> "mounted"
在beforeMount 和 mounted 之间执行的
renderError 方法
renderError 方法,只有在开发时,才会被调用,正式打包上线时,不会被调用。帮助我们调试 render 中的错误。renderError 方法,只有在本组件 render 出现错误时,才会被调用;如果是子组件报错,是不会被捕获到的。
当 render 函数报错时,renderError 方法会执行。
render (h) {
throw new TypeError('render error')
// console.log('render function invoked')
// return h('div', {}, this.text)
},
renderError (h, err) {
return h('div', {}, err.stack)
}
errorCaptured 方法
可以用在正式开发环境中,帮助我们搜集线上的错误。如果在根组件使用这个方法,而根组件的子组件报的任何错误都可捕捉到,除非子组件把向上冒泡停止掉。
errorCaptured 方法使用与 renderError 相似,唯一的区别是:errorCaptured 会向上冒泡,并且正式环境可以使用。
总结
生命周期的执行顺序,调用时机,不同时机进行哪些操作,不同生命周期this.$el
的区别。
生命周期内容作者:littlebirdflying
链接:
接下来我们了解keep-alive
课程中当老师使用keep-alive没有生效的时候,他用到了路由导航守卫来解决问题。
因为keep-alive的存在,组件在被创建一次之后,就自动被缓存了,切换到其他页面的时候当前组件不会被destroyed,那么如果我们希望某一个页面被切换之后就被销毁,那么就使用keep-alive的以下属性去做排除。