async/await
使用注意事项
- 如果在function中使用了 await,则 function 必须被 async修饰
- 在async方法中,
第一个await之前的代码会同步执行(去执行 执行栈中的代码)
,await之后的代码会异步执行
console.log('A')
async function getAllFile(){
console.log('B')
const r1 = await thenFs.readFile('./files/1.txt','utf8')
const r2 = await thenFs.readFile('./files/2.txt','utf8')
const r3 = await thenFs.readFile('./files/3.txt','utf8')
console.log(r1,r2,r3)
console.log('D')
}
getAllFile()
console.log('C')
/*
// 输出顺序
A
B
C
111 222 333
D
*/
EventLoop
同步任务和异步任务
为了防止某个耗时任务
导致程序假死
的问题,JavaScript把待执行的任务分为了两类:
同步任务
(synchronous)
- 又叫做
非耗时任务
,指的是在主线程上排队执行的那些任务 - 只有前一个任务执行完毕,才能执行后一个任务
- 异步任务(asynchronous)
- 又叫做
耗时任务
, 异步任务由JavaScript委托给
宿主环境 - 当异步任务执行完成后,或
通知JavaScript主线程
执行异步任务的回调函数
基本概念
JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行
。这个过程是循环不断的,所以整个的运行机制又称为EventLoop
(事件循环)
Webpack
打包慢的原因
- 从0构建依赖
- 磁盘读取对应的文件到内存,webpack开始加载
- 再用对应的loader进行处理
- 将处理完的内容,输出到磁盘指定的目录
I/O过程十分缓慢
问题解决
- webpack-dev-server
起一个开发服务器,缓存一些已经打包过的内容,只重新打包修改的文件,最终运行在内存中给浏览器使用(检测文件变更,不输出到dist)
webpack 开发服务器,把代码运行在
内存
中,自动更新
,实时
返回给浏览器显示
webpack-dev-server 使用
devServer: {
// contentBase: __dirname, -- 请注意,这种写法已弃用
static: {
directory: path.join(__dirname, "/dist"),
},
// port: "8088", // 设置端口号为8088
hot: true, // 文件修改后实时刷新
historyApiFallback: true, //不跳转
},
mode:'development'
babel配置
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, // 不匹配这些文件夹下的文件
use: {
loader: 'babel-loader', // 使用这个loader 处理js文件啊
// options: { // 加载器选项
// presets: ['@babel/preset-env'] /// 预设:@babel/preset-env 降级规则-按照这里的规则降级我们的js语法 写到.babelrc中即可
// }
}
}
<!-- .babelrc 文件设置降级规则-->
{
"presets": ["env", "react"]
}
webpack总结回顾
- Webpack 有什么用?
打包整合压缩转译你的代码
- Webpack 本质?
第三方的包,需要下载到当前工程
- Webpakck 基础使用
- 初始化package.json
- npm i -D webpack webcpak-cli
- 自定义命令
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev": "webpack-dev-server --open --config webpack.dev.js"
},
- Webpack 默认入口和出口
// entry: path.join(__dirname, "./src/index.js"), // 入口文件
entry: {
index: path.join(__dirname, "./src/index.js"),
// two: path.join(__dirname,"./src/two.js")
},
output: {
path: path.join(__dirname, "./dist"), //打包后的文件存放的地方
// publicPath:'./dist/',//否则不刷新
filename: "[name].js" //打包后输出文件的文件名
},
-
编写代码是开发环境 – 运行的都是打包后的代码
-
webpack-插件:更多功能
// 插件配置
plugins: [
new webpack.BannerPlugin('董志伟 22/4/3'),
new HtmlWebpackPlugin({
template: path.join(__dirname, "/src/index.template.html") //告诉webpack 使用此插件时,以我们自己得html文件作为模板,去生成 ./dist/html 文件(自动引入打包好的文件)
}),
new webpack.HotModuleReplacementPlugin()
]
- webpack-加载器:识别处理更多不同类型文件
原本只能识别js文件
VUE
v-for 操作DOM
循环出新的虚拟DOM结构,和旧的虚拟DOM结构对比,尝试复用标签就地更新内容
渲染树构建(renderTree)
渲染树是dom树结合css树形成的一个新的树。当renderTree构建完毕后,浏览器会根据它去绘制页面。
特点
:
- 渲染树每个节点都有自己的样式。
- 不包含隐藏节点(
display: none
)、不需要绘制的节点(html/head/style/title),这些节点在renderTree形成之前就会被剔除。 visibility: hidden
相对应的节点是包含在渲染树上的,因为它影响布局(layout)。- 渲染树上的每一个节点都会被当成一个盒子,具有内容填充、边距、边框、位置、大小及其它样式。
虚拟DOM是什么
本质就是一个JS对象,保存DOM关键信息
虚拟DOM好处
提高DOM更新的性能,不频繁操作真实DOM,在内存中找到变化部分,再更新真实DOM(打补丁)
diff算法如何比较新旧虚拟DOM
- 同级比较
- 根元素变化,删除重新建立整个DOM树
- 根元素未变,属性改变,DOM复用,只更新属性
v-for设置key
讨论
数据项
和DOM元素
的问题
-
无key则就地更新(复用相同类型的标签,造成渲染错误 – 数据项对应错误的元素)
-
有key属性,基于key的来比较新新旧虚拟DOM,移除key不存在元素
- 用索引设置key的问题: 新增数据项及其之后的数据项 对应的key:index对比于旧的虚拟DOM树都发生了变化,都需要重新渲染,渲染效率低
- 用其它唯一标识做key,数据项对应的key不发生改变,则不用重新渲染,只需要重新渲染新增的数据
v-for 通过 key 值来提升渲染效率的。
Vue过滤器
转换格式,过滤器就是一个函数,传入值
返回处理后的值
- 过滤器只能用在,
插值表达式和v-bind动态属性里面
- 字符串翻转,“输入hello, world”,输出“dlrow ,olleh“
- 字母转大写,输入”hello“,输出”HELLO“
<p :title="msg | toUp | reverse("参数")">鼠标长时间停留</p>
- 定义过滤器
/*
方式1:全局 - 过滤器
任意的.vue 文件内“直接”使用
语法:Vue.filter("过滤器名",(值) => { return "返回处理后的值"})
*/
Vue.filter("reverse",val => val.split("").reverse().jion(""))
// --------------------------------------------------------------
/*
方式2: 局部 -过滤器
只能在当前vue文件使用
*/
export default{
data(){
return {
}
},
filters:{
reverse(val){
return val.split("").reverse().jion("")
}
}
}
- 使用
// 语法:{{值 | 过滤器名字}}
计算属性
有缓存减少计算
- 完整语法
// 给计算属性赋值 -- 需要setter
computed:{
"计算属性名"(){
return "值"
},
"计算属性名":{
// 给计算属性赋值触发set方法
set(值){
},
// 使用计算属性的值触发get方法
get(){
return "值"
}
}
}
数组 every
函数
查找数组里“不符合”条件,直接返回false
可以判断某些元素的属性是否同时满足某一条件,有一个不满足返回false
// 全选按钮和单选按钮的相互影响实现
computed:{
isAll:{
set(val){
// 全选框选中 状态传入
this.arr.forEach(obj => obj.c = val)
},
get(){
// 判断是否多选按钮是否全选中
return this.arr.every(obj => obj.c === true)
}
}
}
侦听
watch
// 目标:侦听name值的改变
watch:{
name(newVal,oldVal){
// 变量名对应值改变 这里自动触发
console.log(newVal,oldVal)
}
}
- 深度侦听
watch:{
"要侦听的属性名":{
immediate:true, // 立即执行
deep:true, // 深度侦听复杂类型内部的变化 必须
handler(newVal,oldVal){
}
}
}
小结
1. 触发v-for更新监测
能改变 原数组 的 数组方法
push() / pop() /shift() /unshift() / splice() / sort() /reverse()..
想要影响v-for更新,解决方案:
- 覆盖原数组
- this.$set() - 只改变某一个值
2. 虚拟DOM
JS对象 - 保存DOM关键信息
提高性能,真实DOM属性太多,没办法快速的找到谁变化了
template => 虚拟DOM => 真实DOM
3. 具体如何把虚拟DOM替换到真实DOM上的?
diff算法:(比较新旧DOM的差异)
(1): 新旧DOM - 同级比较
- 根标签改变 -> (决定)删除整个DOM重新创建 -> 更新真实DOM
- 根标签未变,属性变了 -> 复用此标签,只更新属性 ->更新真实DOM
- 跟标签未变,属性未变,子/内容改变
- 无Key -> (比较新旧DOM差异)-> 尽可能复用标签(就地更新)
- 有Key -> (以Key来比较新旧DOM的差异) -> 创建新的标签到真实DOM
组件通信
EventBus
- 语法
- src/EventBus/index.js - 创建空白Vue对象并导出
import Vue form 'vue' // 导出空白对象 export default new Vue()
- 在要
接收
值的组件(List.vue)import eventBus from "../EventBus"; export default { created(){ eventBus.$on('事件名',函数体) } }
- 在要
传递
值的组件(MyProduct.vue)import eventBus from "../EventBus"; export default{ methods:{ kanFn(){ eventBus.$emit('事件名',函数体) } } }
- src/EventBus/index.js - 创建空白Vue对象并导出
Vue生命周期-钩子函数
Vue框架内置函数,随着
组件
的生命周期阶段,自动执行
- 作用:特定的时间点,执行特定的操作
- 场景:组件创建完毕后,可以在created 生命周期函数中发起Ajax请求,从而初始化data数据
- 分类:4大阶段,8个方法
1. 初始化阶段
方法: brforeCreate / created
-
new vue()
– Vue实例化(组件也是一个小的Vue实例) -
Init Event & Lifecycle
– 初始化事件和生命周期函数 -
b e f o r C r e a t e \color{red}{beforCreate} beforCreate – 生命周期钩子函数被执行
(无data)
-
Init injections & reactivity
– Vue内部添加data和methods等 -
c r e a t e d \color{red}{created} created – 生命周期钩子函数被执行,实例创建
(有data)
-
接下来是
编译模板阶段
– 开始分析 -
Has el option?
– 是否有el选项 --检查要挂到那里
- 没有,调用
$mount()
方法 - 有,继续
检查template
选项 (template里面是模板语法-v-for… 会被编译为虚拟DOM)
- 没有,调用
2. 挂载
方法:beforeMount / mounted
-
template选项检查
- 有 --编译template
返回render渲染函数
- 无 –
编译el选项对应标签
作为template(要渲染的模板)
- 有 --编译template
-
虚拟DOM
挂载
成真实DOM之前
-
b e f o r e M o u n t \color{red}{beforeMount} beforeMount – 生命周期钩子函数被执行
-
Create vm.$el and repalce "el" with it
– 把虚拟DOM和渲染的数据一并挂到真实DOM上 -
真实DOM
挂载完毕
-
m o u n t e d \color{red}{mounted} mounted --生命周期钩子函数被执行(获取DOM)
3. 更新
方法:beforeUpdate / updated
-
当data里的数据改变,更新DOM之前
-
b e f o r e U p d a t e \color{red}{beforeUpdate} beforeUpdate – 生命周期钩子函数被执行
-
Virtual DOM re-render and patch
– 虚拟DOM重新渲染,打补丁到真实DOM -
u p d a t e d \color{red}{updated} updated – 生命周期钩子函数被执行
-
当有data数据被改变 – 重复这个循环
4. 销毁
方法:beforeDestroy / destroy
-
当
$destroy()
被调用 – 比如组件DOM被移除 (例v-if) -
b e f o r e D e s t r o y \color{red}{beforeDestroy} beforeDestroy – 生命周期钩子函数被执行
-
拆卸
数据监视器、子组件和事件侦听器 -
实例销毁后,最后触发一个钩子函数
-
d e s t r o y e d \color{red}{destroyed} destroyed --生命周期钩子函数被执行
axios使用
http://www.axios-js.com/
特点:
- 支持客户端发送Ajax请求
- 支持服务端Node.js 发送请求
- 支持Promise相关用法
- 支持请求和响应的拦截器功能
- 自动转换JSON数据
axios({
method:'请求方式', // get post
url:'请求地址',
data:{
// xxx:xx 拼接到 请求体 的参数,POST请求的参数
},
params:{
//xxx:xx 拼接到 请求行 的参数,GET请求的参数
}
}).then(res =>{
console.log(res.data) // 后台返回的结果
}).catch(err =>{
console.log(err) // 后台报错返回
})
ajax => 一种前端异步请求后端的技术,委托浏览器的网络线程(XMLHttpRequest)
axios => 基于原生
ajax + Promise
技术封装通用于前后端的请求库
axios全局配置
配置基础地址,统一管理
- 可以在官网看到axios的很多默认配置 http://www.axios-js.com/
import axios from 'axios'
axios.defaults.baseURL = 'http://127.0.0.1:8088'
- 修改请求url/ 以后的请求都不用带前缀基地址了 – 运行时,axios的baseURL会自动拼在前面
axios({
url:"/api/getbooks",
method:"get"
}).then(res =>{
console.log(res);
})
$refs和 $nextTick使用
$refs可以获取标签和组件
// 获取组件并调用其方法
mounted(){
this.$refs.de.fn()
}
$nextTick()
vue更新DOM是异步的,方法内部执行到修改data 但非同步更新DOM,如果要获取DOM更新后的数据,使用$nextTick()
$nextTick()返回Promise对象,可以使用语法糖async / await
<p ref="myP">{{count}}<p/>
<button @click="getCount">点击count+1 并马上输出p标签中的数据</button>
export default{
data(){
return {
count:0
}
},
methods:{
getCount(){
this.count++; // Vue检测数据更新,开启一个DOM更新队列(异步任务)
// console.log(this.$refs.myP.innerHTML) // 只能获取到更新之前的内容
// 可以更新的钩子函数内实现,但为了代码逻辑性使用$nextTick()
this.$nextTick(()=>{
console.log("DOM更新后触发$nextTick函数")
console.log(this.$refs.myp.innerHTML)
})
},
async getCountAsync(){
this.count++;
await this.$nextTick();
console.log(this.$refs.myp.innerHTML)
}
},
// 钩子函数
mounted(){},
}
组件name属性使用
组件name可用作注册组件名字
- 组件定义name属性和值
export default{ name:"ComNameHaHa" //注册时使用自定义的名字 }
- 注册组件可用上面name的值
<ComNameHaHa/> import ComName from './components/ComName' components:{ [ComName.name]:ComName //Key是变量,必须用[]包起来 }
组件
动态组件
:is =“变量组件名”
<component :is="comName"></component>
import UseName from '../components/UserName'
export default{
data(){
return {
comName:"UserName"
}
},
components:{
UseName
}
}
组件缓存(避免切换时被销毁)
Vue内置的keep-alive组件 包起来要频繁切换的组件
<keep-alive>
<!-- Vue 内置的组件component,可用动态显示组件 -->
<component :is="comName"></component>
</keep-alive>
组件缓存后激活(切换回)和非激活(未切换回)
activated()
--激活时触发
deactivated()
--失去激活状态触发
组件插槽
组件中有一个标签
使用组件时,组件标签中间的内容会显示到
具名插槽
一个组件内有多处需要传入外部标签的位置
语法:
- slot使用name属性区分名字
<!-- Pannel组件内部 -->
<div class="title">
<slot name="title"></slot>
</div>
<div class="container">
<slot name="container"></slot>
</div>
- template配合
v-slot:name名字
来分发对应标签
<template v-slot:title></template>
<template v-slot:container></template>
插槽作用域
使用插槽时,想用到子组件内的变量
- 子组件,在slot上绑定属性和子组件内的值
- 使用组件,传入自定义标签,用template和v-slot=“自定义变量名”
- scope变量名自动绑定slot上所有属性和值
scope={row:defaultObj}
<!-- Pannle.vue -->
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
<slot name="header" :row="defaultObj">{{defaultObj.defaultOne}}</slot>
</div>
<Pannel>
<template v-slot:header="scope">
{{scope.row.defaultTwo}}
</template>
</Pannel>
自定义指令
获取标签,拓展额外功能
- 全局注册-语法
Vue.directive("指令名",{
"inserted"(el){
// 可以对el标签拓展额外功能
}
})
- 局部注册-语法
directives:{
"指令名字":{
inserted(el){
// 对el进行操作
}
}
}
Vue-Router
单页富文本(SPA):所有功能在
一个html页面
上实现,局部刷新
前端路由作用:实现业务场景切换
- 优点:
- 整体
不刷新页面
,用户体验更好 - 数据传递容易,开发
效率高
- 整体
- 缺点:
- 开发成本高(需要学习专门知识)
- 首次加载速度慢,不利于SEO
组件分类
src/views
文件夹下的页面组件
- 页面展示 - 配合路由使用src/components/
下的复用组件
- 展示数据/用于复用
使用
// 1.引入第三方包
import VueRouter from "vue-router"
// 2.注册全局组件
Vue.use(VueRouter)
// -------------------------------------
// 3.添加规则数组
//统一管理路由名称
RouterNames = {
Home = "Home",
List = "List",
Detail = "Detail"
}
const routers = [
{
path: '/',
name: RouterNames.Home,
component: HomeView,
// 辅助属性 可自定义完成一些功能
meta: {
depth:1
}
},
{
path: '/list',
name: RouterNames.List,
component: List,
meta: {
depth:2
}
},
{
path: '/detail/:id',
name: RouterNames.Detail,
component: Detail,
meta: {
depth:3
}
}
]
// ----------------------------------------
// 4.生成路由对象
const router = new VueRouter({
routers, // routers是固定key(传入规则数组)
mode:"history" // # -- hash路由(以后上线需要服务器端支持,否则找的是文件夹)
})
// 5.路由对象注入到vue实例中,this可以访问$route和 $router
new Vue({
router,
render:h=>h(App),
}).$mount('#app')
// 6.设置挂载点 导航页面显示的位置
<router-view/>
声明式导航_基础使用
目标:可用组件router-link 来替代a标签
- vue-router 提供了一个全局组件
- router-link 实质上最终会渲染成
a
链接 to属性等价于提供href
属性(to无需#
) - router-link 提供了声明式
导航高亮
的功能(自带类名
)
<router-link to="/地址">点击跳转</router-link>
声明式导航_跳转传值
1.不带参数
<router-link :to="{name:'home'}" />
<router-link :to="{path:'/home'}" />
//router-link中链接如果是'/'开始就是从跟路由开始,如果开始不带'/',则从当前路由开始
2.带参数
<router-link :to="{name:'home',params:{id:1}}" />
//params传参数(类似post)
//路由里面配置path:"/home/:id" 或者 path:"/home:id"
//冒号用params取参,用"/:"拼接
//不配置path,刷新页面id会保留
//html取参 $route.params.id
//script 取参 this.$route.params.id
//params是path的一部分,所以params和name一起使用,不能和path一起使用
<router-link :to="{name:'home',query:{id:1}}" /> 或者
<router-link :to="{path:'home',query:{id:1}}" />
//query传参数(类似get,url后面会显示参数)
//路由可不配置 因为带参数了
//html 取参 $route.query.id
// script 取参 this.$route.query.id
重定向
redirect
const routes = [
{
path:"/",
redirect:"/find"
},
{
path:"/find",
component:Find
}
]
路由未命中_404
找不到路径给提示页面
path:"*"
路由按顺序查找,放最后
const routes = [
{
path:"/",
redirect:"/find"
},
{
path:"/find",
component:Find
},
...
...
{
path:"*",
component:NotFound
}
]
编程式导航_基础使用
目标:用JS代码来进行跳转
语法:path或者name任选一个
this.$router.push({
path:"路由路径", // 都去 router/index.js 定义
name:"路由名" // 在网页地址栏看不见
})
编程式导航_跳转传参
目标:用JS跳转路由,传参
语法:query或者params任选一个
this.$router.push({
path:"路由路径",
name:"路由名",
query:{
"参数名":值
}
params:{
"参数名":值
})
路由嵌套和守卫
路由嵌套
在现有的一级路由下,再嵌套二级路由
children
属性
const routes = [
{
path:"/",
redirect:"/find"
},
{
path:"/find",
component:Find,
children:[
{
path:"ranking",
component:Ranking
}
]
}
]
路由守卫
正如其名 ! vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航 、有多种机会植入路 由导航过程中, 全局的, 单个路由独享的, 或者组件级的
判断登录状态
是与路由相关的函数
完整的导航解析流程
- 导航被触发
- 在失活的组件里调用离开守卫
- 调用全局的 beforeEach 守卫
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)
- 在路由配置里调用 beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve 守卫 (2.5+)
- 导航被确认
- 调用全局的 afterEach 钩子
- 触发 DOM 更新
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数
// 全局
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
router.afterEach((to, from) => {
// ...
})
// 路由独享
const router = new VueRouter({
routes : [
{
path : '/foo ',
component : Foo,
beforeEnter : (to, from, next) => {
// ...
}
}
]
})
// 组件内
const Foo = {
template : 、...、,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 this
// 因为当守卫执行前组件实例还没被创建
},
beforeRouteupdate (to, from, next) {
// 在当前路由改变 但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id 在 /foo/1 和 /foo/2 之间跳转的时候 // 由于会渲染同样的 Foo 组件,因此组件实例会被复用,而这个钩子就会在这个情况一被调用。
// 可以访问组件实例 "this"
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 、this、
}
}
next必须调用
next()
: 进行管道中的一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed(确认)next(false)
: 中断当前的导航 。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮)、那么 URL 地址会重置到 from 路由对应的地址next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航 。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true 、name: ‘home’ 之类的选项,以及任何用在 router-link 的 to prop 或 router.push 中的选项。
h : '/foo ',
component : Foo,
beforeEnter : (to, from, next) => {
// …
}
}
]
})
// 组件内
const Foo = {
template : 、…、,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 this
// 因为当守卫执行前组件实例还没被创建
},
beforeRouteupdate (to, from, next) {
// 在当前路由改变 但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id 在 /foo/1 和 /foo/2 之间跳转的时候 // 由于会渲染同样的 Foo 组件,因此组件实例会被复用,而这个钩子就会在这个情况一被调用。
// 可以访问组件实例 “this”
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 、this、
}
}
next必须调用
- `next()`: 进行管道中的一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed(确认)
- `next(false)`: 中断当前的导航 。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮)、那么 URL 地址会重置到 from 路由对应的地址
- `next('/')` 或者` next({ path: '/' })`: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航 。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true 、name: 'home' 之类的选项,以及任何用在 router-link 的 to prop 或 router.push 中的选项。
- `next(error)`: (2.4.0+) 如果传入 next 的参数是:个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。