前置知识
一、WebPack
实际开发中并不需要手动配置webpack
1、概念
webpack是前端项目工程化的具体解决方案
主要功能:提供了友好的前端模块化开发支持、以及代码压缩混淆、处理浏览器端javaScript的兼容性、性能优化等强大的功能
好处:提高开发效率和项目可维护性
2、创建webpack项目
-
新建项目空目录需要英文路径,并允许npm init -y 命令,初始化包管理配置文件 package.json
-
新建src源代码目录
-
新建src->index.html首页和src->index.js脚本文件
-
运行npm install jquery -S命令,安装jQuery
-
通过ES6模块化的方式导入jQuery
在项目中配置webpack
- 安装webpack 命令npm install webpack@5.42.1 webpack-cli@4.7.2 -D
- 在项目根目录中创建webpack.config.js的webpack配置文件并初始化如下配置
module.exports={
mode:'development'//mode用来指定构建模式,可选值有development和production,项目上线时得改成production
}
- 在package.json的scripts节点下,新增dev脚本如下:
"scripts":{
"dev":"webpack"//script节点下的脚本,dev属性名字可以自定义,其值为脚本名,可以通过npm run执行脚本,例如npm run dev
}
- 在终端中运行npm run dev 命令,启动webpack进行项目的打包构建
成功后会出现一个dist文件夹里面有main.js文件,其代码并没有被压缩
- 在html中用script导入main.js文件夹
- 之后便可以运行代码了(每次更改代码后需要nmp run dev)
webpack代码压缩
将webpack.config.js中的mode改为production(项目上线时才为production)
3、webpack.config.js文件
在执行npm run dev后,运行webpack脚本前会读取这个文件
在webpack 4.x和5.x的版本中,有如下默认约定
- 默认的打包入口文件为src->index.js
- 默认的输出文件路径为dist->main.js
注意:可以在webpack.config.js中修改打包的默认约定
entry和output
在webpack.config.js配置文件中通过entry节点指定打包的入口,通过output节点指定打包的出口
const path=require('path')//导入path模块
module.exports={//向外导出一个webpack的配置对象
entry:path.join(__dirname,'./src/index.js'),//打包入口文件的路径
output:{
path:path.join(__dirname,'./dist'),//输出文件的存放路径(指定生成文件要存放的目录)
filename:'bundle.js'//输出文件的名称(指定生成文件的名称)
}
mode:'development'
}
4、webpack插件
注意:下面两个插件以理解为主,之后会用脚手架自动配置
通过安装和配置第三方的插件,可以拓展webpack的能力,从而让webpack用起来更加方便,最常用的webpack插件有如下两个:
-
webpack-dev-server
-
类似于node.js阶段用到的nodemon工具
-
每当修改了源代码,webpack会自动进行项目的打包和构建
-
安装命令npm install webpack-dev-server -D
-
//package.json配置修改 "scripts":{ "dev":"webpack serve" }
-
再次运行npm run dev (此时生成的main.js会放到内存中而不是物理磁盘中)
-
若出现Unable to load ‘@webpack-cli/serve’ command这种错误可以先运行npm install webpack-cli -D 再执行npm run dev
-
修改代码后终端中会反应,但是网页中没反应
-
cannot get错误可以修改为下面的devSercer中的static为"./‘’
-
const path=require('path')//导入path模块 module.exports={ mode:'development', entry:path.join(__dirname,'./src/index.js'),//打包入口文件的路径 output:{ path:path.join(__dirname,'./dist'),//输出文件的存放路径(指定生成文件要存放的目录) filename:'main.js'//输出文件的名称(指定生成文件的名称) }, devServer: { static: "./" } }
-
在html中加载和引用生成在内存中的main.js
-
<script src="/main.js"></script>
-
之后打开http://localhost:8080/点击网页中的src即可看到效果
-
-
html-webpack-plugin
-
webpack中的HTML插件(类似模板引擎插件)
-
可以通过此插件自封之index.html页面的内容
-
安装此插件后可以直接进入index首页(是复制出来的在内存中的html文件)
-
会自动注入内存中的main.js到html(我们就不必手动引入文件)
-
安装命令npm install html-webpack-plugin -D
-
//导入插件 const HtmlPlugin=require('html-webpack-plugin') //创建实例对象 const htmlPlugin=new HtmlPlugin({ template:'./src/index.html',//原文件存放路径 filename:'./index.html'//指定生成文件存放路径 }) module.exports={ mode:'development', plugins:[htmlPlugin],//通过plugins节点,使htmlPlugin插件生效 }
-
5、devServer节点
在webpack.config.js配置文件中,可以通过devServer节点对webpack-dev-server插件进行更多的配置
devServer{
open:true,//首次打包成功后自动打开浏览器
port:8080,//修改端口号
host:"127.0.0.1",//指定运行的主机地址
}
6、loader
在实际开发中,webpack默认只能处理.js后缀文件,其它的非js后缀文件模块处理不了,需要loader加载器才可以正常打包
loader加载器作用:协助webpack打包处理特定的文件模块,比如:
- css-loader可以打包处理.css相关文件
- less-loader可以打包处理.less相关文件
- babel-loader可以打包处理webpack无法处理的高级js语法
在webpack中一切皆模块,都可以通过es6语法在js中导入模块包括css文件
import "./index.css"
因为webpack无法处理css等模块,所以需要配置loader
配置css的loader
-
运行npm i style-loader@3.0.0 css-loader@5.2.6 -D命令,安装处理css文件的loader
-
在webpack.config.js的module->rules数组中,添加loader规则如下
-
module:{ rules:[{test:/\.css$/,use:["style.loader",'css.loader']}] }
-
其中test表示匹配的文件类型,use表示队友要调用的loader
注意:
- use数组中指定的loader顺序是固定的
- 多个loader的调用顺序是:从后往前调用
配置url-loader
-
运行npm i url-loader@4.1.1 filr-loader@6.2.0 -D命令
-
在webpack.config.js中
-
rules:[{test:'/\.jpg|png|gif$/',use:'url-loader?limit=22229'}]
?之后是loader的参属项
-
limit用来指定图片的大小单位是字节
-
只有<=limit大小的图片才会被转为base64格式的图片
配置babel-loader
-
webpack有时无法处理高级的js语法,需要babel-loader来处理
-
npm i babel-loader@8.2.2 @babel/core@7.14.6 @babel/plugin-proposal-decorators@7.14.5 -D
-
{test:/\.js$/,use:'babel-loader',exclude:/node_modules/}
-
在项目根目录下,创建名为babel.config.js的配置文件,定义babel的配置项如下
-
module.exports={ plugins:[['@babel/plugin-proposal-decorators',{legacy:true}]] }
7、base64图片
base64图片可以防止发起一些不必要的网络请求
8、打包发布
在package.json文件的scripts节点下,新增build命令如下
"scripts":{
"div":"webpack serve",
"build":"webpack --mode production"
}
–mode是一个参数项,用来指定webpack的运行模式,production表示生产环境,会对打包生产的文件进行代码压缩和性能优化
build中只用webpack去掉serve会将生成文件到磁盘而不是内存
- npm run build (打包发布项目)
注意:通过–mode指定的参数项,会覆盖webpack.config.js中的model选项来打包
9、优化图片和js文件路径
优化js文件路径
output:{
path:path.join(__dirname,'dist'),
//生成的文件名,为其放入js文件夹中方便分辨和归类
filename:'js/main.js'
}
优化图片路径
webpack.config.js中的url-loader配置项
//outputPath修改图片打包路径
{
test:/\.jpg|png|gif$/,
use:'url-loader?limit=470&outputPath=images'
}
10、自动清理dist目录下的旧文件
安装clean-webpack-plugin插件
-
npm install clean-webpack-plugin -D
-
//导入 const {CleanWebpackPlugin}=require{'clean-webpack-plugin'}; moudel.export={ ///........ plugins:[htmlPlugin,new CleanWebpackPlugin()] //..... }
11、Source Map解决报错位置不匹配
浏览器报错的行号是生成文件里的行号
因此需要配置SourceMap
SourceMap就是一个信息文件,存储着位置信息,也就是说,该文件中存储着压缩混淆后的代码所对应的转换前的位置
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。
-
在webpack.config.js中添加如下配置即可
-
module.exports={ devtool:'eval-source-map', }
发布项目前要把sourcemap关闭,防止源代码被泄露
在实际发布的时候建议把值改为:nosources-source-map,即照顾了调试代码的体验又具有安全性
VUE2.0
工程化的前端开发
- 模块化(js模块化、css模块化、资源模块化)
- 组件化(现有UI结构、样式、行为的复用)
- 规范化(目录结构的划分、编码规范化、接口规范化、文档规范化、Git分支管理)
- 自动化(自动化构建、自动部署、自动化测试)
一、Vue简介
1.介绍
vue是一套用于构建用户界面的前端框架
框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能
vue的指令、组件、路由、Vuex、vue组件库
2.vue的特性
- 数据驱动视图
好处:当页面数据变化时,页面会自动重新渲染
注意:数据驱动视图是单向的数据绑定
- 双向数据绑定
在填充表单时,双向数据绑定可以辅助开发者在不操作DOM的前提下,自动把用户填充的内容同步到数据源中
好处:开发者不再需要手动操作DOM元素,来获取表单元素最新的值
二、MVVM
MVVM是vue实现数据驱动视图和双向数据绑定的核心原理。MVVM指的是Model、View和ViewModel,它把每个HTML页面拆分成了这三个部分:
在MVVM概念中:
- Model表示当前页面渲染时所依赖的数据源
- View表示当前页面所渲染的DOM结构
- ViewModel表示vue的实例,它是MVVM的核心
三、vue的基本使用
1.基本使用步骤
①导入vue.js的script脚本文件
②在页面中声明一个将要被vue所控制的DOM区域
③创建vm实例对象(vue实例对象)
<body>
<div id="app">
{{username}}}
</div>
<script src="./lib/vue-2.6.12.js"></script>
<script>
//创建vue实例对象
const vm=new Vue({
//el属性表示当前vm实例要控制那个区域,接收的值是一个选择器
el:'#app',
//data对象就是要渲染的数据
data:{
username:'zhangsan'
}
})
</script>
</body>
四、vue的指令
1.指令的概念
指令是vue为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构
vue中的指令按照不同的用途可以分为6大类:
- 内容渲染指令 v-text,{{}},v-html
- 属性绑定指令 v-bind (😃
- 事件绑定指令 v-on (@)
- 双向绑定指令 v-model
- 条件渲染指令 v-if,v-else,v-else-if,v-show
- 列表渲染指令 v-for
2.内容渲染指令
内容渲染指令用来辅助开发者渲染DOM元素的文本内容,常用的内容渲染指令有如下三个:
- v-text
- {{}}
- v-html
v-text
- 缺点:会覆盖标签原本内容,所以几乎不用
//把username对应的值渲染到p标签
<p v-text="username">
</p>
//把gender对应的值,渲染到p标签中
//默认的文本“性别”会被gender的值覆盖
<p v-text="gender">
性别
</p>
{{}}
插值表达式(Mustache)
<p>
姓名:{{username}}
性别:{{gender}}
</p>
-
只是占位符,不会覆盖原有内容
-
不会渲染html标签
-
只能用于内容节点,不能用于属性值
v-html
- 可以渲染html标签
//info:"<h4>hello world!</h4>"
<p v-html="info">
</p>
3、属性绑定指令
v-bind:
<div id="app">
<input type="text" v-bind:placeholder="tips">
<img v-bind:src="photo">
</div>
<script>
const vm=new Vuew({
el:'#app',
data:{
tips:"请输入用户名",
photo:'http://image.....'
}
})
</script>
需要动态改变的属性就在属性前写 v-bind:
可以简写为’ : ’
之后属性值就填data中的变量名
4、Javascript表达式
在vue提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持javascript表达式的运算
{{number+1}}
{{ok?'Yes':'No'}}
{{message.split('').reverse().join('')}}
<div v-bind:id="'list-'+id"></div>
v-bind属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,否则会被认为是data中的变量
5、事件绑定指令
v-on:
- 可以简写为@
- 事件处理函数传参可以用()传参
<div id="app">
<h3>
count值为:{{count}}
</h3>
<button v-on:click="add(1)">
+1
</button>
</div>
<script>
const vm=new({
le:"#app",
data:{
count:0
},
//methods中定义事件处理函数
methods:{
add(n){
//这里的this相当于vm
//html中可以用()传参,不传参可以不加小括号
this.count+=n
}
}
})
</script>
@click,@input,常见的原生事件都可以绑定
$event
- 绑定事件时如果没有传参,则函数接收的第一个参数为e也就是事件
- 若传参了,则需要传入实参$event然后对应接收的形参就是e
<div id="app">
<h3>
count值为:{{count}}
</h3>
<button v-on:click="add(1,$event)">
+1
</button>
</div>
<script>
const vm=new({
le:"#app",
data:{
count:0
},
//methods中定义事件处理函数
methods:{
add(n,e){
this.count+=n
console.log(e)
if(this.count%2===0){
//偶数
e.target.style.backgroundColor="red"
}
else{
e.target.style.backgrounColor=''
}
}
}
})
</script>
事件修饰符
①**.prevent**
起到e.preventDefault作用
加在事件绑定之后
<a href="src" @click.prevent="show"></a>
②**.stop**
阻止事件冒泡
起到e.stopPropagation的作用
按键修饰符
在监听键盘事件时,我们经常需要判断详细的按键,此时可以为键盘相关事件添加按钮修饰符
<div id="app">
<input type="text" @keyup.esc="clearInput">
</div>
<script>
const vm=new({
le:"#app",
data:{
},
//methods中定义事件处理函数
methods:{
clearInput(e){
console.log("触发esc键")
e.target.value=""
}
}e
})
</script>
6、双向数据绑定指令
结合表单使用
v-model
将表单中的value替换成v-model即可
<div id="app">
<input type="text" v-model="username">
<p>
用户的名字:{{username}}
</p>
</div>
<script>
const vm=new({
le:"#app",
data:{
username:'zhangsan'
},
//methods中定义事件处理函数
methods:{
clearInput(e){
console.log("触发esc键")
e.target.value=""
}
}e
})
</script>
v-model是双向绑定,而v-bind是单向绑定
且使用v-model后input表单的value属性就没有意义了
只有表单元素才能使用v-model
input\textarea\select\
v-model的修饰符
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户的输入值转为数值类型 | <input v-model.number=“age”/> |
.trim | 自动过滤用户输入的首尾空白字符 | <input v-model.trim=“msg”/> |
.lazy | 在”change“时而非”input“时更新,即不会每次输入一个字符就同步而是回车后同步 | <input v-model.lazy=“msg”/> |
7、条件渲染指令
用来辅助开发者按需控制DOM的显示和隐藏。条件渲染指令有如下两个分别是
- v-if
- 动态增删元素,刚进入页面时某些元素默认不展示,且后期可能不需展示时if性能更佳
- v-else和v-else-if可以配合v-if使用
- v-show
- 利用display来控制显示,频繁改变时性能更佳
<div id="app">
<p v-if="flag">
这是被v-if控制的元素
</p>
<p v-show="flag">
这是被v-show控制的元素
</p>
</div>
<script>
const vm=new({
le:"#app",
data:{
flag:true
},
//methods中定义事件处理函数
methods:{
}
})
</script>
8、列表渲染指令
v-for
需要以item in list的形式使用
需要索引的时候可以(item,index) in list
- 一定需要绑定key属性,尽量用唯一的id来绑定且必须是字符串或者数字,一定不要用index来绑定(index的值不具有标识性)
<div id="app">
<li v-for="(item,index) in list"
key="item.id">
索引:{{index}} id:{{item.id}} 姓名:{{item.name}}
</li>
</div>
<script>
const vm=new({
le:"#app",
data:{
list:[
{id:1,name:'zs'},
{id:2,name:'ls'}
]
},
//methods中定义事件处理函数
methods:{
}
})
</script>
五、过滤器
注意:过滤器在vue3中已经被删除,这里只要会一点基础就行
过滤器(Filters)是vue为开发者提供的功能,常用于文本的格式化,过滤器可以用在两个地方:插值表达式和v-bind属性绑定
5.1基本用法
过滤器应该被添加在javascript表达式的尾部,由”管道符“进行调用,
<div id="app">
<!--对message的值进行格式化-->
<p>
{{message|capi}}
</p>
<!--在v-bind中通过管道符调用formatId过滤器,对rawId的值进行格式化-->
<div v-bind:id="rawId | formatid"></div>
</div>
<script>
const vm=new Vue({
el:'#app',
data:{
message:'hello vue.js'
}
//过滤器本质是一个函数,需要被定义到filter节点之下
filter:{
capi(val){//val永远是管道符前面的值
const first=val.charAt(0).toUpperCase()//取第一个字符并变大写
const other=val.slice(1)//从1开始截取字符串
return first+other
}
}
})
</script>
5.2私有过滤器和全局过滤器
1、私有过滤器
filter中的过滤器为私有过滤器
只有当前vm控制的标签区域才能使用私有的过滤器
2、全局过滤器
一般都定义全局过滤器
当全局与私有过滤器名冲突,则按就近原则取私有过滤器
//在vm对象外按下面形式定义
Vue.filter('过滤器名',(val)=>{
//处理程序
return 结果
})
定义格式化时间的过滤器
Vue.filter('dataformat',function(time){
//1、对time进行格式化处理得到YYYY-MM-DD HH:mm:ss格式
//2、把格式化的结果return出去
//这里使用dayjs库来导入时间
//直接调用dayjs()得到的是当前时间
//dayjs(给定的时间)得到指定的日期
const timestr=dayjs().format('YYYY-MM-DD HH:mm:ss')
})
5.3连续调用多个过滤器
{{time|format|xxx|yyy|zzz}}
将值不断的往后传,最后一个return的值就是结果
5.4filter可以通过小括号传参
但是传入的参数在接收时会从形参第二位开始接收,第一位默认是管道符前的值
六、侦听器
1、什么是watch侦听器
允许开发者监视数据变化,从而针对数据变化做特定的操作,监听的是data里数据值的变化
2、基本用法
<div id="app">
<input v-model="username" type="text">
</div>
<script>
const vm=new Vue({
el:'#app',
data:{username:''},
watch:{//定义侦听器,
//1、函数形式侦听器,用相关数据名作为方法名
username(newVal,oldVal){
console.log(newVal,oldVal)
}
//2、对象格式的侦听器,相关数据名作对象名
username:{
handler(newVal,oldVal){//handler是固定写法
}
}
}
})
</script>
典型应用场景:判断用户名是否被占用,若占用则清除并警告
3、immediate选项
-
函数形式的侦听器不会在初次进入页面时触发
-
对象格式的侦听器可以通过immediate选项,让侦听器自动触发
watch:{
username:{//对象格式的侦听器
handler(newVal,oldVal){//handler是固定写法
},
immediate:true //true表示初次进入就触发,默认为false
}
}
4、deep选项-深度侦听
对象里的数据不会被函数形式的侦听器所侦听
可以使用对象格式,并用deep选项来侦听每一个对象中的数据
data:{
info:{
username:''
}
}
watch:{
info:{
handler(newVal,oldVal){
//..
},
deep:true//开启深度侦听,就能侦听对象中的任何属性的变化
},
'info.username'(){
//也可以用这种方法来侦听具体子属性变化,注意需要单引号
}
}
注意:深度侦听会遍历数据对象的所有嵌套属性,如果是大型数据需要慎用,以免造成性能上的不足
七、计算属性
1、介绍
计算属性的值是通过一系列运算后得到的属性值
这个动态计算出来的属性值可以被模板结构或methods方法使用
动态改变背景颜色
<div :style="{backgroundColor:`rgb(${r},${g},${b})`}">
颜色:{{rgb(${r},${g},${b})}}
</div>
这种用字符串拼接的形式来动态改变虽然实现了效果,但是当其中一个rgb改成rgba时其余的rgb也得改,这就非常没效率,因此需要计算属性
data:{
r:0,g:0,b:0
},
computed:{//存放所有计算属性
//计算属性需要定义为方法形式
//在vm对象中methods里可以用例如this.rgb调用
//html模板中可以直接调用,
//依赖的数据源变化也会引起计算属性的变化
rgb(){
return `rgb(${this.r},${this.g},${this.b})`
}
}
//实际应用
<div v-bind:style="{backgroundColor:rgb}">{{rgb}}</div>
补充:axios
axios是一个专注于网络请求的库
npm -i axios -S
基本语法
import axios from 'axios'
axios({
method:'请求的类型',
url:'请求的url地址',
//Get参数,按需
params:{}
//post请求体参数,按需
data:{}
//调用axios返回的是promise对象,可以用.then
}).then(result)=>{
//.then用来指定请求成功后的回调函数
//形参中的result是请求成功后的结果
}
axios在请求到数据之后,给真正的数据做了一层外包装
data属性里面才是正真的数据
async和await,以及对返回结果解构赋值得到真正数据
解构赋值时在变量名后面用:可以更改变量名
例如
{data:res}=result
则得到结果的变量名为res
axios.get
const {data:res}=axios.get("url地址",
{
params:{请求体数据}
})
axios.post
const {data:res}=axios.post('url',{post请求体数据})
八、vue-cli
1、单页面应用程序
**单页面应用程序(SPA)**顾名思义指一个web网站中只有唯一一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成
2、vue-cli
vue-cli是vue.js开发的标准工具。简化了程序员基于webpack创建工程化的vue项目的过程
3、安装和使用
vue-cli是npm上的一个全局包,使用npm install命令,即可方便的把它安装到自己的电脑上:
npm install -g @vue/cli
基于vue-cli快速生成工程化的Vue项目:
vue create 项目名称
建议选择最后一项,可以自定义安装需要的东西
然后选择2.x即vue2.0
- 创键完成项目后cd 到更目录
- 然后npm run serve
4、src目录
-
assets文件夹存放静态资源文件,比如图片、css样式表
-
components文件夹存放程序员封装的可复用组件
-
main.js是项目的入口文件,整个项目的运行需要先运行main.js
-
App.vue是项目的根组件,可以清空后重写
5、vue项目的运行流程
在工程化项目中,vue要做的事情很单纯,通过main.js把App.vue渲染到index.html的指定区域中
6、main.js
//导入Vue这个包,得到Vue构造函数
import Vue from 'vue'
//导入App.vue根组件,将来要把App.vue中的模板结构渲染到HTML页面中
import App from './App.vue'
Vue.config.productionTip = false
//创键Vue实例对象
new Vue({
//将render函数指定的组件渲染到html页面中
render: h => h(App),
}).$mount('#app')
在index.html中id为app的div在渲染时会被App.vue中的标签替换掉,其相当于成了一个占位符(render指定的结构会替换掉el指定的结构,这里的el用$mount方法替代,两种方法作用一样)
- APP.vue用来编写带渲染的模板结构
- index.html中需要预留一个el区域(el可以用$mount()方法代替)
- main.js把App.vue渲染到了index.html所预留的区域中
九、组件的基本使用
声明一个模板结构,比如Test.vue
//Test.vue
<template>
<div>
<h3>
这是一个自定义的模板
</h3>
</div>
</template>
修改main.js
//main.js
import Vue from 'vue'
import Test from './Test.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(Test),
}).$mount('#app')
1、组件化开发
根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护
2、vue中的组件化开发
vue是一个支持组件化开发的
vue中规定:组件的后缀名是.vue之前接触到的App.vue本质上就是一个Vue组件。
3、Vue组件的三个组成部分
每个.vue组件都由三部分组成:
- template->组件的 模板结构
- script->组件的 JS行为
- style->组件的 样式
vue中分别对应template,script,style标签,一般按照此顺序写,且script中必须要有export defualt{} js代码写在括号中
注意:template中只能有一个根div
<template>
<div>
自定义模板
<h1>
用户:{{username}}
</h1>
<button @click="changeName">
修改用户名
</button>
</div>
</template>
<script>
//固定写法,默认导出
export default{
//组件中的data必须是一个函数,return一个数据对象
data(){
return {
username:'zs'
}
},
//组件中定义方法
methods:{
changeName(){
this.username="ls"
}
},
//当前组件中的侦听器
watch:{
},
computed:{
//计算属性
},
filter:{
//过滤器
}
}
</script>
<style>
div {
background:'red';
}
</style>
4、组件之间的父子关系
组件被封装好后彼此之间是相互独立的,不存在父子关系
在使用组件时,根据彼此之间的嵌套关系,形成了父子关系、兄弟关系
4.1使用组件的三个步骤
- 1、在当前组件/根组件中使用import语法导入需要的组件
import xxx from '@components/xxx.vue'
- 2、在当前组件/根组件中的script中的components节点注册组件
export default{
components:{
xxx
}
}
- 3、在当前组件/根组件中以标签形式使用刚才注册的组件
<div id="app">
<xxx></xxx>
<div>
4.2通过components注册的是私有子组件
即被注册的组件在哪个组件中注册则只能在该组件中使用
4.3注册全局组件
在main.js中通过Vue.component()方法,
import xxx from '@/components/xxx.vue'
Vue.component("注册名称",xxx)//注册名称可以起任意名,但建议以大写开头
5、组件的props
props是组件的自定义属性,在封装通用组件的时候,合理的使用props可以极大提高组件的复用性
export default{
//props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
props:[
'自定义属性1',
'自定义属性2',
...
],
}
通过调用组件并给自定义属性赋值可以传值给props
<xxx a="9"></xxx>
Tip:结合v-bind可以使传入的数值为数值型而不是原来的字符串型(实质是使引号中的内容变为js代码)
<xxx :a="9"></xxx>
props的数据可以直接在html模板结构中被使用
5.1props中的数据只读,不能修改
可以将从props读取的数据传入到data的变量中
比如props中有个变量init,data中有个变量count则可以这样赋值
data(){
return{
count:this.init
}
}
如此可以修改data中的值而不必修改props的值尚且其值也并不能被修改。
5.2props的default默认值
可以设置props的默认值
语法:
export default{
props:{
a:{
//用default属性定义属性的默认值
default:0
}
}
}
5.3props的type值类型
在声明自定义属性值时,可以通过type来定义属性的值类型
export default{
props:{
init:{
//default定义属性的默认值
default:0,
//用type属性定义属性的值类型
//如果传递过来的值不符合此类型,则会在终端报错
type:Number
}
}
}
5.4props的required必填项
export default{
props:{
init:{
//default定义属性的默认值
default:0,
//用type属性定义属性的值类型
//如果传递过来的值不符合此类型,则会在终端报错
type:Number,
//required:true表示该自定义属性必须填写
required:true
}
}
}
6、组件之间的样式冲突问题
默认情况下,写在vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
- 单页面应用程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的
- 每个组件中的样式,都会影响整个index.html页面中的DOM元素
6.1解决方法
style标签的scoped属性
只要给当前组件的style标签加上scoped属性就可以解决
<style scoped>
</style>
6.2使用deep修改子组件中的样式
加上deep可以修改子组件中样式
/deep/ h5{
color:red;
}
当使用第三方组件库的时候可以使用该方法在父组件中修改引入的组件的样式
十、组件的生命周期
1、生命周期&生命周期函数
生命周期:是指一个组件从创键->运行->销毁的整个阶段,强调的是一个时间段
生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自当按次序执行
2、创键阶段生命周期函数
-
beforeCreate()函数调用时。组件的props/data/methods尚未创键,都处于不可用状态,所以此刻该函数并不能做什么事情,因此一般不用
-
created()函数调用时,组件的props/data/methods已经初始化,都处于可用阶段,但是组件的模板结构尚未生成,此函数十分重要,可在此阶段发起ajax请求,来获取数据并转存到data
- beforeMount()函数,将要把内存中编译好的HTML结构渲染到浏览器中。此时浏览器中还没有当前组件的DOM结构,此方法一般不会用
- **mounted()**函数,此刻已经把内存中的HTML结构成功渲染到了浏览器中,此时已经包含了当前组件的dom结构,可以最早操作DOM元素
3、运行阶段生命周期函数
- beforeUpdate()函数,只有数据变化的时候才会触发。此时UI结构还是没有更新数据前的UI结构
- updated()函数,此时已经完成了最新的DOM结构的渲染
4、销毁阶段生命周期函数
- beforeDestroy()函数
- destroyed()函数
十一、组件之间的数据共享
1、组件之间的关系
两种最常见的关系
- 父子关系
- 兄弟关系
2、父子组件数据共享
2.1父向子传递
props自定义属性
//父组件
<Son :msg="message" :user="userinfo"></Son>
data(){
return{
message:'hello vue.js',
userinfo:{name:'zs',age:20}
}
}
//子组件
<template>
<div>
<h5>
Son组件
</h5>
<p>
父组件传递过来的msg值为:{{msg}}
</p>
<p>
父组件传递过来的user值为:{{user}}
</p>
</div>
</template>
props:['msg','user']
2.2子向父传递
子组件向父组件共享数据使用自定义事件
//子组件
export default {
data(){
return {count:0}
},
methods:{
add(){
this.count+=1
//修改数据时通过$emit()触发自定义事件
this.$emit('numchange',this.count)
}
}
}
//父组件
<son @numchange="getNewCount"></
son>
export default{
data(){
return {countFromson:0}
},
methods:{
getNewCount(val){
this.countFromSon=val
}
}
}
3、兄弟组件数据共享
EventBus
1、定义一个eventBus.js文件,导出一个空的Vue实例
//eventBus.js
import Vue from 'vue'
//向外共享Vue的实例
export default bus=new Vue()
2、发送方用emit发送
//兄弟组件A(数据发送方)
import bus from './eventBus.js'
export default{
data(){
return {
msg:'hello'
}
},
methods:{
sendMsg(){
bus.$emit('share',this.msg)
}
}
}
3、接收方用on接收
//兄弟组件B(数据接收方)
import bus from './eventBus.js'
export default{
data(){
return {
msgFrombrother:''
}
},
created(){//可以在created生命周期函数中调用
bus.$on('share',val=>{
this.msgFromLeft=val
})
}
}
十二、ref引用DOM元素
ref用来辅助开发者在不依赖于jQuery的情况下获取DOM元素或组件的引用。
每个vue的组件实例上,都包含一个** r e f s ∗ ∗ 对象,里面存储着对应的 D O M 元素或组件的引用。默认情况下,组件的 ∗ ∗ refs**对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的** refs∗∗对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的∗∗refs**指向一个空对象。
<template>
<h1 ref="myh1">
组件
</h1>
<button @click="show">
点我变红
</button>
</template>
<script>
export default{
data(){
return {
}
},
methods:{
show(){
//首先需要在标签上取ref的名字
console.log(this.$refs.myh1)//获取对应名字的dom并打印
this.$refs.myh1.style.color="red"
}
}
}
</script>
十三、ref引用组件
<template>
{{count}}
<xxxx ref="myComponent"/>
<button @click="show">
</button>
</template>
<script>
import xxxx from "@components/xxxx.vue"
export default{
data(){
return {
}
}
components:{
xxxx
}
methods:{
show(){
console.log(this.$refs.myComponent)
this.$refs.myComponent.该组件方法/属性
//得到该组件中可以直接使用该组件方法和属性
}
}
}
</script>
tips: this.$nextTick(callback)方法
延迟到DOM更新后调用callback
this.$nextTick(()=>{
//....
})
有利于处理因为DOM没更新导致的undifined
十四、数组中的方法
foreach循环
some循环
every循环
reduce的基本使用
十五、动态组件
1、component标签
vue提供了一个内置的组件,专门用来实现动态组件的渲染,相当于一个占位符
is中填写要渲染的子组件名,可以用:绑定来实现动态组件
<template>
<div>
<component is="组件名"></component>
</div>
</template>
<template>
<div>
<component :is="comName"></component>
</div>
</template>
<script>
import Cart from"./components/Cart/Cart.vue"
export default{
data(){
return {
comName:'Cart'
}
},
components:{
Cart
}
}
</script>
注意:component切换组件时原先组件会被销毁
2、使用keep-alilve保持状态
防止切换时销毁组件,会将组件缓存
<keep-alive>
<component :is="comName"></component>
</keep-alive>
3、keep-alive对应的生命周期函数
当组件被缓存时,会自动触发组件的deactivated生命周期函数。
当组件被激活时,会自动触发组件的activated生命周期函数
组件第一次激活时会触发created函数,但之后的激活不会再触发
4、keep-alive的include属性
include属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间用逗号分隔
<keep-alive include="component1,component2">
<component :is="comName"></component>
</keep-alive>
5、keep-alive的exclude
exclude用来指定:名称匹配的组件不会被缓存,用法与include一样
注意:它不能与include同时使用
十六、插槽
1、介绍
插槽是vue为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的,希望由用户指定的部分定义为插槽
//com组件
<template>
<div>
<!--声明一个插槽区域-->
<slot></slot>
</div>
</template>
<template>
<div class="app">
<com>
<p>
这是在com组件的内容区域,声明的p标签
</p>
</com>
</div>
</template>
2、v-slot指令
vue规定每一个插槽都有一个name名称
如果省略了name属性则会有默认名称叫做default
默认情况下在使用组件的时候提供的内容都会填充到default的插槽中
//com组件
<template>
<div>
<!--声明一个插槽区域-->
<slot name="default"></slot>
</div>
</template>
可以指定内容放入名为何值得插槽,但需要template标签,template标签是虚拟的元素不会被渲染
<template>
<div class="app">
<com>
<template v-slot:default>
<p>
将p标签放入com组件的名为default的插槽中
</p>
</template>
</com>
</div>
</template>
3、v-slot的简写形式以及插槽的后备内容
v-slot: 可以简写为#
<template #default>
<template>
<div>
<slot>这是这个插槽的默认内容,如果使用组件时没有用插槽则会展示这段内容,官方将此称为后备内容</slot>
</div>
</template>
4、具名插槽的定义和使用
带name名字的插槽就是具名插槽
<template>
<div class="article-container">
<div class="header-box">
<slot name="header"></slot>
</div>
<div class="content-box">
<slot name="content"></slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default{
name:'Article'
}
</script>
<style lang="less" scoped>
</style>
<template>
<div class="app-container">
<Article>
<tamplate #header>
<h3>
一首诗
</h3>
</tamplate>
<tamplate #content>
<div>
这是一首诗
</div>
</tamplate>
<tamplate #footer>
<div>
这是脚注
</div>
</tamplate>
</Article>
</div>
</template>
5、作用域插槽
可以在slot中通过自定义属性来传值,并在使用插槽时在template中的插槽名称后面跟上=”名称“来接收一个装有自定义属性值的对象,名称可以随意。
这种提供属性对应值的用法叫做作用域插槽
<template>
<div class="article-container">
<div class="header-box">
<slot name="header" msg="hello vue"></slot>
</div>
<div class="content-box">
<slot name="content"></slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default{
name:'Article'
}
</script>
<style lang="less" scoped>
</style>
<template>
<div class="app-container">
<Article>
<template #header="scope">
<div>
{{scope.msg}}
</div>
</template>
</Article>
</div>
</template>
6、作用域插槽的解构赋值
<template>
<div class="article-container">
<div class="header-box">
<slot name="header" msg="hello vue" user="userinfo"></slot>
</div>
<div class="content-box">
<slot name="content"></slot>
</div>
<div class="footer-box">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default{
name:'Article'
}
</script>
<style lang="less" scoped>
</style>
<template>
<div class="app-container">
<Article>
<template #header="{msg,user}">
<div>
{{msg}}{{user}}
</div>
</template>
</Article>
</div>
</template>
十七、自定义指令
1、介绍
vue官方提供了很多常用的指令,除此之外vue还允许开发者自定义指令
2、自定义指令的分类
- 私有自定义指令
- 全局自定义指令
3、私有自定义指令
- 每个vue组件中,可以在directives节点下声明私有自定义指令
<template>
<div>
<h1 v-color>
变红
</h1>
</div>
</template>
<script>
export default{
directives:{
color:{
//为绑定到的HTML元素设置红色的文字 //当指令绑定到元素后会立即触发bind函数,指令需要以v-开头
bind(el){
//形参中的el时绑定了此指令的、原生的DOM对象,是固定写法
el.style.color='red'
}
}
}
}
</script>
- 使用binding.value获取指令绑定的值
<template>
<div>
<h1 v-color=“color”>
变颜色
</h1>
<h1 v-color="'red'">
红色
</h1>
</div>
</template>
<script>
export default{
data(){
return{
color:'blue'
}
},
directives:{
color:{
bind(el){
el.style.color=binding.value
}
}
}
}
</script>
4、update函数
bind只会在第一次绑定到元素上的时候触发,当DOM结构更新后不会触发,需要update函数
<template>
<div>
<h1 v-color=“color”>
变颜色
</h1>
<h1 v-color="'red'">
红色
</h1>
</div>
</template>
<script>
export default{
data(){
return{
color:'blue'
}
},
directives:{
color:{
bind(el){
el.style.color=binding.value
},
update(el){
//DOM更新时触发
el.style.color=binding.value
}
}
}
}
</script>
bind只会在第一次绑定时生效,update只会在第一次不生效
- 函数简写
当bind和update函数中的逻辑完全相同时,则对象格式的自定义指令可以简写成函数格式
directives:{
color(el,binding){//简写
el.style.color=bing.value
}
}
5、全局自定义指令
全局自定义指令需要通过Vue.directive()进行声明
//参数1:字符串,表示全局自定义指令的名字
//参数2:对象,用于接收指令的参数值,可以简写成一个function
Vue.directive('color',function(el,bindng){
el.style.color=binding.value
})
十八、eslint
用于约束代码风格
略(以后要用再学~~)vscode直接装个eslint插件,公司项目里一般都配置好了eslint格式,直接用一建修复就可自动改变代码格式
十九、axios的Vue更方便使用
//main.js
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
//1、全局配置axios的请求根路径
axios.defaults.baseURL="请求根路径"
//2、将axios挂载到vue的原型,这样就不用每次使用都导入axios,而是调用this.$http
Vue.prototype.$http=axios
//
new Vue({
render:h=>h(App)
}).$mount(#app)
const {data:res}=await this.$http.get('/api/get')
axios挂载到原型的缺点:无法实现api接口的复用
优化:分离axios并封装
创键一个utils工具文件夹,新建reques.js文件再里面写入如下代码
//封装axios
import axios from 'axios'
const http=axios.create({
//通用请求的地址前缀
baseURL:'/api',
timeout:10000,//超时时间ms
})
//请求拦截器
http.interceptors.request.use(function(config){
//在发送前处理代码
return config
},function(error){
//对请求错误时处理代码
return Promise.reject(error)
});
//响应拦截器
http.interceptors.response.use(function(response){
//对响应数据处理代码
return response;
},function(error){
//响应错误时的处理代码
return Promise.reject(error)
})
export default http
之后创建一个api文件夹,其中创建一个index.js文件或者其它你喜欢的命名的js文件,在里面导入http并用它创建各种api请求并用export导出
最后你就可以在需要使用api请求的文件中导入相应的请求函数并使用
//将api请求都独立封装在此文件
import http from '../utils/request.js'
//定义接口
//请求首页表格数据
export const getData=()=>{
return http.get('/home/getData')
}
//请求首页折线图数据
export const getGraphData=()=>{
return http.get('/home/getGraph')
}
//请求首页柱状图数据
export const getGrap_1Data=()=>{
return http.get('/home/getActive')
}
import {getData} from '../api'
二十、路由
1、介绍
路由(router)就是对应关系,这里是Hash地址和组件之间的对应关系
2、SPA与前端路由
SPA单页面应用程序
3、前端路由的工作方式
- 用户点击了页面的路由链接
- 导致了URL地址栏的Hash值发生了变化
- 前端路由监听到了Hash地址的变化
- 前端路由把当前Hash地址对应的组件渲染到了浏览器中
4、路由的基本使用
vue-router
vue-router是vue.js官方给出的路由解决方案。它只能结合vue项目进行使用,能够轻松的管理SPA项目中组件的切换
vue-router的官方文档地址是:https://router.vuejs.org/zh/
1、安装vue-router并配置
- 安装vue-router包
在vue2项目中 npm i vue-router@3.5.2 -S
- 创键路由模块
在src源代码目录下,新建router/index.js路由模块,并初始化如下代码
import Vue from 'vue'
import VueRouter from 'vue-router'
//调用Vue.use()函数 把VueRouter安装为Vue的插件
Vue.use(VueRouter)
//创键路由的实例对象
const router =new VueRouter()
//向外共享路由的实例对象
export default router
- 导入并挂载路由模块
//main.js
//导入路由模块(模块化导入时若只有一层路径,则默认加载路径下index.js的文件)
import router from'./router'
new Vue({
render:h=>h(App),
//挂载 router(属性名和变量名相同可以es6简写)
router
}).$mount('#app')
- 声明路由链接和占位符(使用路由)
//App.vue
<template>
<div class="app-container">
<a href="#/home">首页</a>
<a href="#/movie">电影</a>
<a href="#/about">关于</a>
//只要在项目中安装和配置了vue-router就可以使用router-view组件了,作用就是个给组件的占位符
<router-view ></router-view>
</div>
</template>
<script>
</script>
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
//导入需要的组件
import Home from "./components/Home"
import Movie from "./components/Movie"
import About from "./components/About"
//调用Vue.use()函数 把VueRouter安装为Vue的插件
Vue.use(VueRouter)
//创键路由的实例对象
const router =new VueRouter({
//routes是一个数组,作用是定义“hash地址”与“组件”之间的对应关系
routes:[
{path:'/home',component:Home},
{path:'./movie',component:Movie},
{path:'./about',component:About},
]
})
//向外共享路由的实例对象
export default router
2、router-link替代a链接
可以用a但是不建议使用a标签
//App.vue
<template>
<div class="app-container">
//当安装了vue-router后就可以使用router-link来替代普通的a链接
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
//只要在项目中安装和配置了vue-router就可以使用router-view组件了,作用就是个给router-link指向的组件的占位符
<router-view ></router-view>
</div>
</template>
<script>
</script>
5、路由重定向
路由重定向指:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面,通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由地重定向
const router=new VueRouter({
routes:[
{path:'/',redirect:'/home'},
{path:'/home',component:Home}
]
})
二十一、嵌套路由
通过路由实现组件的嵌套展示,叫做嵌套路由
//about.vue
<template>
<div class="about-container">
<!--子路由链接-->
<router-link to="/about/tab1"></router-link>
<router-link to="/about/tab2"></router-link>
<!--子路由占位符-->
<router-view></router-view>
</div>
</template>
1、通过children属性声明子路由规则
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Tab1 from './components/Tab/Tab.vue'
const router=new VueRouter({
routes:[
{
path:'/about',
component:About,
children:[//子路由不能斜线开头
{path:'tab1',component:Tab1}
]
}
]
})
2、默认子路由
如果children数组中某个路由规则的path值为空字符串,则这条路由规则叫做“默认子路由”
children:{
{path:'',component:Tab1}//默认展示Tab1,router-link中也不需要/tab1
{path:'tab2',component:Tab2}
}
二十二、动态路由匹配
动态路由
指把Hash地址中的可变部分定义为参数项,从而提高路由规则的复用性
在vue-router中使用 : 来定义路由的参数项,参数项名可任取
{path:'/movie/:id',component:Movie}
那如何根据id来展示Movie组件中的对应内容呢
在组件中this.$route.params.id 可以拿到对应参数项
为路由规则开启props传参
props:true
{path:'/movie/:id',component:Movie,props:true}
<h3>Movie组件--{{id}}</h3>
<script>
export default{
props:['id']
}
</script>
相比第一种传参这种传参更加方便,推荐使用
拓展:query和fullPath
query
<router-link to="/movie/2?name=zs&age=20"></router-link>
在?后面的参数叫做查询参数,需要用this.$route.query来访问查询参数
fullpath
fullpath即包含了路径又包含了参数,是完整的地址
而path则只包含路径
二十三、编程式导航跳转
1、声明式导航&编程式导航
在浏览器中,点击链接实现导航的方式叫做声明式导航。例如:
- 普通网页中点击a链接,vue中点击router-link
在浏览器中,调用API方法实现的导航叫做编程式导航。例如:
- 普通网页中调用 location.href 跳转到新页面的方式
2、Vue-router中常用的编程式导航API
-
this.$router.push(‘hash地址’)
- 跳转到指定的hash地址,并增加一条历史记录
-
this.$router.replace(‘hash地址’)
- 跳转到指定的hash地址,并替换掉当前的历史记录
-
this.$router.go(数值n)
- 在浏览历史中前进后退,负数表示后退,-1表示后退一页面,1表示前进一页面
- $router.back()后退到上个一页面
- $router.forward()前进到下一个页面
在template行内使用编程式导航时需要省略this
扩展:路由传参的三种形式
-
字符串形式:
this.$router.push('/search' +'?k='+参数)
-
模板字符串
this.$router.push(`/search?=${参数}`)
- 对象
this.$router.push({name:'search',params:{变量名:动态参数},query:{变量名:查询参数}})
3、导航守卫
控制路由访问权限
全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制
const router=new VueRouter({
})
//调用路由实例对象的beforEach方法即可声明全局前置守卫
//每次发送路由导航跳转的时候,都会自动触发fn这个回调函数
router.beforeEach(fn)
守卫回调函数 的三个形参
router.beforEach((to,from,next)=>{
//to是将要访问的路由的信息对象
//from是将要离开的路由的信息对象
//next是一个函数,调用next()表示放行,允许这次路由导航
console.log(to,from)
next()//放行
})
next三种调用方式
- 当前用户拥有后台主页的访问权限,直接next()放行
- 当前用户没有后台主页的访问权限,强制跳转到登录页面:next(‘/login’)
- 当前用户没有后台主页的访问权限,不允许跳转到后台主页,停留在当前页面:next(false)
router.beforEach((to,from,next)=>{
if(to.path==='/main'){
const token=localStorage.getItem('token')
if(token){//token存在即已经登录
next()
}else{//没有登陆则强制跳转到登录页
next('/login')
}
}else{
next()
}
})
二十四、初始化
1、创键并梳理项目结构
可以不要linter 他是esline(会严格限制代码格式规范,错误格式会报错并阻止程序运行…非常痛苦)
相比之前多了个views文件夹
该文件夹是用于存放组件的与components相同,但是要通过路由来切换的组件存放到views中,否则存放在components中
2、Vant组件库
移动端的vue组件库
3、element-UI
pc端的vue组件库
二十五、ES6模块化与异步编程
ES6模块化
es6模块化规定:
- 每个js文件都是一个单独的模块
- 导入其它模块成员使用import关键字
- 向外共享模块成员使用export关键字
默认导出语法:
export default 默认导出的成员
每个模块中只允许一个默认导出
默认导入:
import 接收名称 from ‘模块标识符’
默认导入的接收名称可以任意,但是需要合法的命名
按需导出
export 导出成员
按需导入:
import {导出成员名称1 as 重命名,导出成员名称2,…} from ‘模块标识符’
Promise
1、回调地狱
多层回调函数的相互嵌套,就形成了回调地狱
回调地狱缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,可读性变差
Promise解决回调地狱
2、promise的基本概念
promise是一个构造函数
- 可以创键Promise的实例对象 const p =new Promise()
- new 出来的Promise实例对象代表一个异步操作
promise.prototype上的then()方法
- 每一次new Promise()构造函数得到的实例对象
- 都可以通过原型链的方式访问到then()方法
then()方法用来预先指定成功和失败的回调函数
- p.then(成功的回调函数,失败的回调函数)
- p.then(result=>{},error=>{})
- 成功的回调函数必选,失败的可选
Promise是异步的,但是**这里的异步是指他的.then()和.catch()方法,Promise本身还是同步的,**所以当执行一个函数时,会先执行new Promise()的同步方法,再执行回调resolve或者是reject。
3、基于回调函数按顺序读取文件内容
这里就形成了一个回调地狱
4、基于then-fs读取文件内容
npm install then-fs
上述代码无法保证文件读取顺序,需要进一步改进
升级改进=》
第一个then又返回了一个新的promise实例,就可以继续then,采用链式编程的方式按顺序读取文件内容
5、catch()方法捕获错误
在链式操作中如果发生了错误就可以使用Promise.prototype.catch方法对链式操作中所有产生的错误进行捕获和处理
如果不希望错误导致后续的then停止执行则可以将catch提前
6、Promise.all()方法
会发起并行的Promise异步操作,等所有的异步操作(等待机制)全部结束后才会执行下一步的.then操作
读取的顺序就是数组里实例的顺序
7、Promise.race()方法
会发起并线的Promise异步操作,只要任何一个异步操作完成就立即执行下一步的then操作
因此这里then得到的result是promise执行最快的实例的结果
async/await
async/await是ES8引入的新语法,用来简化Promise异步操作,在这之前开发者只能通过链式then的方法处理Promise异步操作。
.then虽然解决了回调地狱的问题但是代码依旧冗余且阅读性差
基本使用方式
await用来修饰promise对象,加了await后只有当promise执行完成后才会返回值再继续执行函数中下面的代码,只有async修饰的函数才能使用await
async function getAllFile(){
const r1=await thenFs.readFile('./files/1.txt','utf8')
const r2=await thenFs.readFile('./files/2.txt','utf8')
const r3=await thenFs.readFile('./files/2.txt','utf8')
}
注意事项
- 如果function中使用了await,则function必须被async修饰
- 在async方法中,第一个await之前的代码会同步执行,await之后的代码会异步执行,如下
上面代码中,先输出A B遇到await后其后面的代码就被视为异步执行,所以先输出C之后等异步完成后输出r1 r2 r3 D
EventLoop
1、同步任务&异步任务
为了防止某个耗时任务导致程序假死,JavaScript把待执行的任务分成了两类:
- 同步任务
-
非耗时任务,值得是主线程上排队执行的任务
-
只有前一个任务执行完毕,才能执行后一个任务
-
- 异步任务
- 耗时任务,异步任务由js委托给宿主环境进行执行
- 当异步任务执行完成后,会通知js主线程执行异步任务的回调函数
2、同步/异步的执行过程
- 同步任务由JS主线程按次序执行
- 异步任务委托给宿主环境执行
- 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
- js主线程的执行栈被清空后会读取任务队列中的回调函数,按次序执行
- JS主线程不断执行上一个步骤
3、EventLoop基本概念
JS主线程从任务队列中读取异步任务的回调函数,放到执行栈中依次执行,这个过程是循环不断地鹅,这种运行机制称为EventLoop(事件循环)
延时器只有0ms因此会立刻放入到任务队列中
宏任务&微任务
js把异步任务做了进一步划分,异步任务分为两类:
-
宏任务
- 异步AJAX请求
- setTimeout\setInterval(定时器)
- 文件操作
- 其它宏任务
-
微任务
- Promise.then.catch.finally
- process.nextTick
- 其它微任务
注意:promise本身是同步的,then是异步的
为什么微任务先执行?
因为当我们的主线程的代码执行完毕之后,在Event Loop执行之前,首先会尝试DOM渲染,这个时候,微任务是在DOM渲染之前执行,DOM渲染完成了之后,会执行宏任务,这也就是文章开头那段代码的解释,微任务要比宏任务更早执行!!! 那么我们的最终图示如下
VUEX
一、概述
1.1 组件共享数据方式
父向子:props
子向父:自定义事件
兄弟组件共享:eventbus
1.2 Vuex
是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享
1.3什么样的数据适合存储到Vuex中
一般只有组件之间共享的数据才有必要存储到Vuex中,对于组件中的私有数据,依旧存储在组件自身的data中即可
二、Vuex的基本使用
2.1安装Vuex的依赖包
npm install vuex --save
2.2导入
import Vuex form 'vuex'
Vue.use(Vuex)
2.3创键store对象
const store=new Vuex.Store({
//state中存放的就是全局共享的数据
state:{count:0}
})
2.4将store挂载到vue
new Vue({
el:'#app',
render:h=>h(app),
router,
store
})
三、Vuex核心概念
3.1State
state提供唯一的公共数据源,所有共享的数据都要统一放到Store的state中进行储存
const store=new Vuex.Store({
state:{
data1:0
}
})
组件中访问state中数据
- 法一
this.$store.state.数据名
- 法二
//从vuex中导入mapState函数
import {mapState} from 'vuex'
//通过导入的mapState函数将当前组件需要的全局数据映射为当前组件的computed计算属性
computed:{
...mapState(['数据名1','数据名2'])
}
注意:这里是导入computed中,若在data中则不会更新数据和视图
3.2Mutations
用于修改store中的数据
- 只能通过Mutation变更state中的数据
- mutation中不能执行异步操作
const store= new Vuex.Store({
state:{
count:0
},
mutations:{
add(state){
state.count++
}
}
})
调用mutations函数
法一
- commit调用mutation中的函数
methods:{
handle(){
this.$store.commit('add')
}
}
- 向mutaions中的函数传递参数
//定义motation
const store= new Vuex.Stroe({
state:{
count:0
},
mutations:{
addN(state,step){
state.count+=step
}
}
})
//组件
methods:{
hanle(){
this.$store.commit('addN',3)
}
}
法二
//导入mapMutations函数
import {mapMutations} from 'vuex'
//将指定的mutations函数映射为当前组件的methods函数
methods:{
...mapMutations(['add','addN']),
fn1(){
this.add()
this.addN(3)
}
}
3.3Actions
action用于处理异步任务,异步任务必须通过action执行,而不能使用mutation,但在action中还是要通过触发mutation的方式来间接更改数据
const store=new Vuex.Store({
//省略代码
mutations:{
add(state){
state.count++
}
},
actions:{
addAsync(context){
setTimeout(()=>{
context.commit('add')
},1000)
}
}
})
组件使用action
法一
methods:{
this.$store.dispatch('addAsync'[,'这里可以传参'])
}
法二
import {mapActions} from 'vuex'
methods:{
...mapActions(['addAsync','addAsyncN'])
}
3.4Getter
用于对Store中的数据进行加工处理形成新的数据
- getter可以对Store中已有的数据加工处理后形成新的数据,类似Vue的计算属性,不会更改原有数据
- Store中的数据变化会导致getter中的数据变化
const store=new Vuex.Store({
state:{
count:0
},
getters:{
showNum:state=>{
return state.count+1
}
}
})
组件中使用getter
法一
this.$store.getters.名称
法二
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['showNum'])
}
VUE2响应式原理
何为响应式?即当数据变化后,视图相应自动更新
-
Vue内部使用Object.defineproperty()将data中对象的每一个成员都转换为getter/setter形式(数据劫持)
-
当你访问对象的属性时getter会自动执行相应方法
-
当你修改对象的属性时setter会自动执行相应方法
-
getter会提醒watcher记录render用到了哪些属性(依赖收集),日后这些属性更新时也就是相应的setter执行后使得watcher去重新执行render函数进行重新渲染(发布订阅)