Vue框架
Vue基础
Vue框架是当前前端最火的框架,这里为了后期配合springBoot框架完成一个完整项目的搭建
Vue
Vue是一套构建用户界面的渐进式JavaScript框架【渐进式: 可以自底向上逐层应用,简单的应用就只需要小巧的核心库,复杂的可以引入各式各样的Vue插件】
Vue的优点 :
- 采用组件化的模式,提高代码的复用率,让代码更好维护,比如一个图片的切换的部分就可以将其变成一个vue的文件
- 声明式编码,让编码人员无需注解操作DOM,提高开发的效率
//命令式编码
let htmlStr = '';
persons.forEach(p => {
htmlStr += `<li>${p.id} - ${p.name} - ${p.age}</li>`;
});
//获取list元素
let list = document.getElementById('list');
//修改内容,手动操作DOM
list.innerHTML = htmlstr;
对应的,如果使用声明式编码
<ul id = 'list'>
<li v-for="p in persons">
{{p.id} - {p.name} - {p.age}}
</li>
</ul>
- 虚拟DOM,采用Diff算法进行优化,提高复用 ---- 如果原来的数据生成一个列表,现在的列表多了一些数据,那么vue就采用的虚拟DOM,数据先转化为虚拟的DOM,再转化为真实的DOM
Vue前置内容
ES6模块化
模块化就是将js文件分解为各个功能不同的js文件,每一个js文件就是一个独立的模块,每一个模块就对应界面上的一个部分,但是模块化管理,很多的数据要进行共享,就需要遵守一个规范,现在的标准的规范就是ES6
在node.js中遵循的是CommonJS的模块化规范,其中:
-
导入其他模块中使用require()方法
-
模块对外共享成员使用module.exports对象
模块化的好处就是模块化和规范化,降低沟通的成本,方便各模块的相互调用; 比如像导航栏就可以单独弄成一个小的模块,这样就可以复用
在ES6模块化规范之前,还有其他的各种各样的模块,AMD和CMD等,但是这些小的规范都是局限的,而ES6模块化的出现就解决了杂乱,直接统一进行管理。
ES6模块化规范是浏览器端和服务器端 的通用的模块化开发规范。在其中定义了:
- 每一个js文件都是一个独立的模块
- 导入其他的模块的成员使用
import
关键字 - 向外共享模块成员使用
export
关键字
ES6模块化主要包含3中用法:
- 默认导出与默认导入
- 按需导出与按需导入
- 直接导入并执行模块中的代码
在项目中打开package.json,加入type:module
{
"type": "module",
"name": "vue01",
"version": "1.0.0",
"description": "first project",
"author": "Cfeng",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js"
},
"dependencies": {
"vue": "^2.5.2",
"vue-router": "^3.0.1"
},
导入和导出操作的都是JSON对象,导入导出可以是默认的也就是找不到固定的export的JSON,就会直接导入默认的,所以导入的时候可以是任何的名称
默认导出【暴露】和默认导入
默认导出就是将js中的成员导出供其他的js文件的使用
export default 默认导出的成员 //默认导出的成员各种都可以,将他们放到{}中
要注意每一个模块只能允许使用唯一的一次的默认导出export default,否则就会报错
这里可以简单示范一下
//这里just作为ES6测试,ES6的出现是必然的趋势,模块化的管理方便js文件的复用,降低耦合度
//默认导出、暴露
let num1 = 2022;
function show(){
alert("hello,es6");
}
export default { //向外共享的数据,这样其他的模块就可以接收到
num1,
show
}
export default {
show
}
想要检查是否成功运行,就是用node的命令,node XX
就可以执行文件
PS D:\Vueprogramm\vue01> node Export.js
file:///D:/Vueprogramm/vue01/Export.js:15
export default {
SyntaxError: Identifier '.default' has already been declared
at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:115:18)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:289:14)
at async link (node:internal/modules/esm/module_job:70:21)
这里就发现了只能使用一次默认导出,因为默认导入就是接收默认导出的,如果多个默认导出,就不知道具体要接收那个的数据【数据是可能出现不同的变量的值的】
这里编写一个简单的Export.js文件来进行模拟
//这里just作为ES6测试,ES6的出现是必然的趋势,模块化的管理方便js文件的复用,降低耦合度
//默认导出、暴露
let num1 = 10;
function show(){
alert("hello,es6");
}
export default { //向外共享的数据,这样其他的模块就可以接收到
num1,
show
}
默认导入就是将已经共享了的数据成员给引入使用,默认导入的语法
import 接收名称 from '模块标识符' //这里就是从执行的共享的文件中接收指定名称的方法或者成员
这里的模块名称是一个字符串,使用单引号。默认导入使用任意的名称都可以,只要合法即可;不能以数字开头
这里编写一个Imprort文件来进行模拟接收的一方
//Import文件模拟的是另外一个需要使用另外一个模块的数据
//从Export文件中接收数据,首先得到方法
import num1 from './Export.js' //需要注意这里引入的时候这是前台的路径,写成Export.js会是识别为资源路径
console.log(num1);
这里直接使用node的命令来进行运行js文件; 这里的num1接收的是放入的JSON的数据,不只是其中某一个数据; 因为默认导出的时候没有名称,所以接收的时候可以为任意的合法的名称;同时因为默认导出没有指定名称,所以这里就只能有一个default,不能有多个默认的default
PS D:\Vueprogramm\vue01> node Import.js
{ num1: 2022, show: [Function: show] }
需要注意的是要使用ES6的模块化规范,就必须要修改JavaScript的配置,在根结点中要加入type: 'module'
;
按需导入和按需导出
按需导出的语法就是export 导出的成员
//这里就修改一下上面的导入导出的数据
//按需导出就是简单在声明变量的时候加上一个export关键字即可,导入的时候接收的就是导出的成员的JSON格式
export let num1 = 2022;
export function show(){ //这里就是将这两个变量进行了按需导出
alert("hello,es6");
}
按需导入就是import {成员的名称} from '模块名称'
//模块名称就是导入的位置 —>这里的位置为前台路径; 想要导入多个值也容易,就是在大括号中放入多个成员,成员之间使用,来进行分割【在HBuilderX中写好路径之后,前面的成员就可以识别
//Import文件模拟的是另外一个需要使用另外一个模块的数据
//从Export文件中接收数据,首先得到方法
import {num1,show} from './Export.js'
console.log(num1);//因为已经导入了成员,就可以直接进行使用
这里运行得到的就是一个具体的
PS D:\Vueprogramm\vue01> node Import.js
2022
- 每一个js文件【模块】中可以使用多次按需导出,但是只能使用一次默认导出
- 按需导入的成员的名称必须和按需导出的名称保持一致
- 按需导入可以和默认导入一起使用
//默认导入的时候名称是任意的,指代的就是之前默认导出的那个JSON对象
import info,{num1 as year,show} from './Export.js' //info就是默认导出的空的JSON对象
PS D:\Vueprogramm\vue01> node Import.js
{}
- 按需导入的时候可以使用as进行重命名
import {num1 as year,show} from './Export.js'
console.log(year);//因为已经导入了成员,就可以直接进行使用
使用as重命名和之前的Mysql中取别名是类似的
直接导入并执行模块中的代码
如果只是像单纯地执行某个模块中的代码,但是不需要得到模块向外共享的成员,这个时候就可以直接导入并执行模块代码
import '模块路径'
这里可以简单演示一下
//被导入的模块的代码Export.js
//直接导入就是不需要得到被导入模块的数据,只是单纯执行模块的代码,就和之前在html中链入JavaScript代码类似
let year = 2022;
console.log(year + " 新年快乐呀"); //这里在HBuilderx中如果不小心按ins键,可能会改变光标为下横线,这个时候再按一次即可
//导入的模块Import.js
//直接导入就类似之前的html的链入,只是简单的执行代码,而不需要使用数据
console.log("今年是哪一年呢?");
import './Export.js';
执行的结果为
PS D:\Vueprogramm\vue01> node Import.js
2022 新年快乐呀
今年是哪一年呢?
这里可以看出来导入的模块就是最先执行,不管是在之前还是之后进行导入,最先执行import语句
安装node,vue
这里我就简单在HBuilderX中的节点中加入即可,否则就会报错 : Cannot use import statement outside a module,这里就引入外部的js文件的时候就不用写text/javaScrit
这里还是有问题,因为ES6模块化,这里就在HBuilerX中配置一下node方便直接运行js文件,而不需要再链入html中在下载node之前,这里解释几个名词:
webpack
: 主要的用途就是通过CommonJS的语法把所有的浏览器需要发布的静态资源做相关的准备,比如对资源进行打包
vue-cil
: 用户生成的Vue工程的模板,和IDEA中的archetype类似,就是帮助快速建立一个vue的项目,只需要npm install就可以安装;Vue3之后换成了@vue/cil
这里安装node就简单在官网上进行下载: Node.js (nodejs.org)
安装成功之后会显示: C:\Users\OMEY-PC>node -v
v16.14.0
这里的node其实和之前的maven有些类似,安装的时候可以参考maven的过程,也有本地仓库cache和镜像;maven是repository
可以使用npm config ls来查看npm的配置信息 --- ls 就是list的简写
//可以使用npm命令来进行修改
修改prefix的值:npm config set prefix 【全局仓库地址】
修改cache的值:npm config set cache【全局缓存地址】
//简单一点就是直接在文件中进行修改就是C盘用户的.npmrc
registry=http://registry.npm.taobao.org
prefix=D:\nodejs\node_global
cache=D:\nodejs\node_cache
在记事本中输入即可,第一个是配置镜像,和maven相同可以加快下载的速度
//使用npm list -global查看本地仓库的信息
C:\Users\OMEY-PC>npm list -global
D:\nodejs\node_global
`-- (empty)
//检查镜像站是否成功
C:\Users\OMEY-PC>npm config get registry
http://registry.npm.taobao.org/
//npm info vue 查看是否能够获得vue
S C:\Windows\system32> Npm info vue
vue@3.2.31 | MIT | deps: 5 | versions: 370
The progressive JavaScript framework for building modern web UI.
https://github.com/vuejs/core/tree/main/packages/vue#readme
dist
.tarball: https://registry.npmmirror.com/vue/-/vue-3.2.31.tgz
.shasum: e0c49924335e9f188352816788a4cca10f817ce6
.integrity: sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
.unpackedSize: 2.5 MB
注意必须要以管理员身份运行cmd窗口
npm install xx -g
就是安装或者更新xx模块到设置的node_global中
这里就更新一下npm模块
PS C:\Windows\system32> npm list -global
D:\nodejs\node_global
`-- npm@8.5.0
这个时候modules的位置就发生了变化,所以需要配置环境变量NODE_PATH :
安装vue
-
安装vue ,使用命令 npm install vue -g
-
安装vue-router,使用命令npm install vue-router -g,安装之后仓库中的dist就是distribution就是发布的产品,也就是需要的文件
-
安装vue-cli 使用命令npm install vue-cli -g 安装vue的脚手架
接下来要正常使用vue,需要配置环境变量,否则就要在当前目录打开cmd窗口【vue.cmd在node_global下面】,在path中加上路径即可
测试是否配置成功,输入命令vue -V
C:\Users\OMEY-PC>vue -V
2.9.6
脚手架vue-cil中的模板包括webpack和webpack-simple,后者简单一点
创建vue项目
vue create
跳转到项目放置的位置,然后vue create xxx
vue create vue01 //创建项目
cd vue01 //进入项目
npm run serve 运行,这个时候访问就localhost就可以运行项目
vue init webpack
这里使用vue的命令来初始化vue01
vue init webpack vue01 //初始化项目
cd vue01
npm install //安装依赖
npm run dev //运行 --- 进入localst:8080/#/
或者
npm run build //直接打开dist文件夹下生成的index.html文件
注意创建项目的时候想要将项目放到哪里,就先将cmd命令跳转到哪里,必须要以管理员身份来运行
PS D:\Vueprogramm> vue init webpack vue01
'git' �����ڲ����ⲿ���Ҳ���ǿ����еij���
�������ļ�
? Project name vue01
? Project description first project
? Author Cfeng
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm
vue-cli · Generated "vue01".
# Installing project dependencies ..
还可以使用可视化的vue的界面,需要3以上的版本
PS C:\Windows\system32> vue ui
🚀 Starting GUI...
🌠 Ready on http://localhost:8000
Vue的可视化界面-- 卸载老版本,安装新的版本 — vue-cil的3版本之后使用的新的,也就是@vue/cil
npm uninstall vue-cli -g
卸载老版本的vue-cli
npm install @vue/cli -g
安装新版本的@vue/cil
在HBuilderX中装载node.js插件
安装了node,之前都是在cmd管理员窗口中运行,在HbuilderX中在工具中配置node即可,引入外部下载的node的位置,下载终端即可运行
promise 解决回调地狱
所谓回调地狱就是指多层回调函数相互嵌套,就形成了回调地狱
这里可以以嵌套的延时器来进行举例,setTimeout就是设置延时器,延时Xms后执行前面的功能函数; 这里的功能函数使用的是Lambda表达式的写法,在java中是->,而JavaScript中为=> ;
//下面的实例代码就是回调地狱,这是一个嵌套的延时器,代码耦合度太高,难以维护;大量的冗余代码相互嵌套
setTimeout(() => {
console.log('延时1秒后输出')
setTimeout(() => {
console.log('再延时2秒后输出')
setTimeout(() =>{
console.log('再延时3秒后输出')
}, 3000)
},2000)
}, 1000)
//运行的结果
PS D:\Vueprogramm\vue01> node Import.js
延时1秒后输出
再延时2秒后输出
再延时3秒后输出
为了解决回调地狱的问题,在ES6中新增了Promise的概念
Promise对象用于表示一个异步操作的最终是否完成以及其结果值,异步方法并不会立即返回最终的值,而是返回一个promise,以便未来某个时候交还给使用者 — 也就是异步方法返回的就是Promise,代表的是异步方法的处理结果,不管是否处理成功或者失败
-
Promise是一个构造函数
-
可以创建Promise类的实例, const p = new Promise();
-
new 出来的Promise的实例对象,代表的是一个异步的操作
-
-
Promise.protoType上包含一个.then()的方法【prototype – 原型对象】
可以看到原型对象prototype中包含方法then、catch、finally等方法,所以创建的对象,都可以通过原型链的方式来调用相关的方法。
- then方法用来预先指定成功或者失败的回调函数; 这里的then和之前的ajax中的success和error的功能是相同的,就是可以指定成功或者失败会执行的回调函数;所谓的回调函数就是调用者不是programmer,而是条件发生时会自动进行调用
p.then(成功的回调函数,失败的回调函数)
也就是p.then(result=>{} , error => {}); 调用回调函数的时候,成功的回调函数是必选的,失败的回调函数是可选的【也就是失败的回调函数就是可以不用必须指定的】
回调函数的实例 – 顺序读取文件
使用fs模块 – 回调函数读取文件
这里的例子,一共建立了3个文本文件,first.txt;second.txt;third.txt;3个文本文件是有顺序的;读取文件采用的是node中的fs模块;Node.js 中的 fs 模块是文件操作的封装,它提供了文件读取、写入、更名、删除、遍历目录、链接等 POSIX 文件系统操作。与其它模块不同的是,fs 模块中所有的操作都提供了异步和同步的两个版本,具有 sync 后缀的方法为同步方法,不具有 sync 后缀的方法为异步方法
读取文件的方法 : fs.readFile(filename,[encoding],[callback(error,data)]
callback就是回调函数,err代表是否有错误发生,boolean类型,而data就是文件的内容
这里使用ES6的语法导出的时候,需要注意的是,fs是默认导出,也就是export default; 所以导入的时候要使用默认导入
,不能使用as这个按需导入中的词
//下面的例子就是使用回调回调函数按顺序读取文本文件的内容
//使用fs(file system)这个核心模块就可以操作文件 引入的时候,如果不加./就是根据名称引入核心模块,加上./就是相对的路径
import fs from 'fs'
fs.readFile('./testfile/first.txt','utf8',(err1,data1) => {
if(err1) return console.log(err1) //读取文件1失败
console.log(data1) //读取文件1成功
//读取文件2
fs.readFile('./testfile/second.txt','utf8',(err2,data2) => {
if(err2) return console.log(err2) //读取文件2失败
console.log(data2);
fs.readFile('./testfile/third.txt','utf8',(err3,data3) => {
if(err3) return console.log(err3)
console.log(data3)
})
})
})
这里可以验证这种传统的嵌套回调函数,也就是回调地狱的结果
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
今天是2022年的农历正月十五,值此佳节之际 -- 2
祝大家元宵节快乐!-- 3
执行的结果是正确的,但是发现上面的代码可读性和可维护性太差了,回调嵌套太多层了
再次优化,使用then-fs读取文件的内容
使用then-fs读取文件内容【promise】
由于node.js官方提供的fs模块只是支持使用回调函数的方式读取文件,不支持使用promise的方式;所以需要先安装相关的插件–then-fs这第三方的包,支持使用promise
npm install then-fs
【install 可以简写为i】,这里只是在这个项目中加入这个依赖,所以不需要-g;-g是下载到本地的仓库
接下来就可以使用promise的方式来优化回调地狱
但是下面的代码,不能保证文件读取的顺序,因为thenfs的操作时异步的
//使用promise的方式来进行文件的读取
//首先还是使用默认导入的方式导入then-fs模块,因为使用的是thenfs这个对象来进行文件操作
import thenfs from 'then-fs' //导包就是从项目的node-modules中导入【不加相对路径的时候】,加上相对路径就可以导入其他的模块的内容
//之前提到过.then中失败的回调函数是可选的
//需要特别注意的是,这里的文件操作是异步的,也就是3者的执行顺序是没有关系的
thenfs.readFile("./testfile/first.txt",'utf8').then(data1 => console.log(data1),err1 => console.log(err1.message))
thenfs.readFile("./testfile/second.txt",'utf8').then(data2 => console.log(data2),err2 => console.log(err2.message))
thenfs.readFile("./testfile/third.txt",'utf8').then(data3 => console.log(data3),err3 => console.log(err3.message))
执行的结果为
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
今天是2022年的农历正月十五,值此佳节之际 -- 2
祝大家元宵节快乐!-- 3
优化这里的执行顺序可以依赖.then方法的特性
如果上一个.then方法返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。通过.then()方法的链式调用,就解决了回调地狱的问题
thenfs的readFile方法的返回值就是一个Promise类型的对象
//Promise支持链式调用,解决回调地狱的问题,thenfs的返回值就是一个Promise类型的对象
//主要就是要将Promise对象进行返回,但是只要有一个失败了,后面的都会失败,下面就是假设都是成功
import thenfs from 'then-fs'
thenfs.readFile('./testfile/first.txt','utf8')//返回值就是一个promise类型的对象,所以可以使用then方法
.then(data1 => {
console.log(data1)
return thenfs.readFile('./testfile/second.txt','utf8') //将读取的第二个文件后的Promise对象返回
}) //失败的回调函数可以省略
.then(data2 => {
console.log(data2)
return thenfs.readFile('./testfile/third.txt','utf8')
})
.then(data3 => {
console.log(data3)
})
//这里就是将文件读取返回Promise对象放到了返回值之中
运行的结果和上面正确的普通的回调地狱的时类似的【思路也是类似的,就是读取成功之后读取下一个,但是少了很多冗余的代码;这里的链式的写法比之前的fs模块的回调地狱的嵌套的写法要好得多,也就是在前一个回调函数中读取下一个文件,但是这里只是使用了读取产生的Promise对象
通过.catch捕获错误,.finally进行强制执行
在Promise的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法来进行捕获和处理
//比如这里的文件一换一个路径
thenfs.readFile('./testfile/first1.txt','utf8')
这个时候直接运行,什么都不会输出,因为没有指定错误时的callback函数
PS D:\Vueprogramm\vue01> node Import.js
PS D:\Vueprogramm\vue01>
这里我们就可以使用Promise的原型对象的.catch方法来进行错误的捕获处理
//这里链式操作,只要前面的错误了,后面的就都不会执行,因为只有执行成功之后才会返回一个Promise对象
import thenfs from 'then-fs'
thenfs.readFile('./testfile/first1.txt','utf8')//返回值就是一个promise类型的对象,所以可以使用then方法
.then(data1 => {
console.log(data1)
return thenfs.readFile('./testfile/second.txt','utf8') //将读取的第二个文件后的Promise对象返回
}) //失败的回调函数可以省略
.then(data2 => {
console.log(data2)
return thenfs.readFile('./testfile/third.txt','utf8')
})
.then(data3 => {
console.log(data3)
})
.catch(err => {
console.log(err.message)
})
.finally(() => {
console.log("我是finally,和java类似,都会强制执行")
})
----------运行的结果-----------
PS D:\Vueprogramm\vue01> node Import.js
ENOENT: no such file or directory, open 'D:\Vueprogramm\vue01\testfile\first1.txt'
我是finally,和java类似,都会强制执行
加了异常的捕获处理之后,就可以看到异常no such file or directory, open ,这里和java的异常处理类似,因为这里相当于域是上面的所有的代码,所以里面发生了异常,下面的2,3都不会执行,这里想要后面的执行,就哪里发生异常就在哪里进行捕获
//这里链式操作,只要前面的错误了,后面的就都不会执行,因为只有执行成功之后才会返回一个Promise对象
import thenfs from 'then-fs'
thenfs.readFile('./testfile/first1.txt','utf8')//返回值就是一个promise类型的对象,所以可以使用then方法
.catch(err => {
console.log(err.message)
})
.then(data1 => {
console.log(data1)
return thenfs.readFile('./testfile/second.txt','utf8') //将读取的第二个文件后的Promise对象返回
}) //失败的回调函数可以省略
.then(data2 => {
console.log(data2)
return thenfs.readFile('./testfile/third.txt','utf8')
})
.then(data3 => {
console.log(data3)
})
------------------------------
PS D:\Vueprogramm\vue01> node Import.js
ENOENT: no such file or directory, open 'D:\Vueprogramm\vue01\testfile\first1.txt'
undefined ---> //因为这里读取文件失败,所以打印的结果是undefined
今天是2022年的农历正月十五,值此佳节之际 -- 2 //后面的两次的执行的结果都是可以正常读取的
祝大家元宵节快乐!-- 3
PS D:\Vueprogramm\vue01>
后面的代码正常执行,和java的异常处理是类似的
Promise的静态方法 — Promise.all()
这个方法会发起并行的Promise的异步操作,等所有的异步操作全部执行结束之后才会执行下一步的.then方法,.all的参数就是Promise对象的集合 数组中Promise实例的顺序,就是最终结果的顺序, 因为执行的结果都存放好了,不是开始,而是结束
//这里链式操作,只要前面的错误了,后面的就都不会执行,因为只有执行成功之后才会返回一个Promise对象
import thenfs from 'then-fs'
//建立一个数组,来进行Promise结果的存储
const promiseArr = [
thenfs.readFile('./testfile/first.txt','utf8'),
thenfs.readFile('./testfile/second.txt','utf8'),
thenfs.readFile('./testfile/third.txt','utf8')
]
Promise.all(promiseArr) //等待所有的异步操作全部执行完毕
.then((data1,data2,data3) => {
console.log(data1 + "~" + data2 + "~" + data3)
})
.catch(err => {
console.log(err.message)
})
.finally(() => {
console.log("hello,你是否开心")
})
-----------------------------
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
,今天是2022年的农历正月十五,值此佳节之际 -- 2
,祝大家元宵节快乐!-- 3
~undefined~undefined
hello,你是否开心
--------------直接输出的结果 就是一个结果的数组,将文件的读取结果放入了一个数组之中
[
'今年是2022年,壬寅虎年 -- 1\n',
'今天是2022年的农历正月十五,值此佳节之际 -- 2\n',
'祝大家元宵节快乐!-- 3\n'
]
这里的data1就是上面的所有的异步对象的处理结果,后面两个参数并没有传入,所以是undefined
Promise的静态方法 — Promise.race()
这个方法和上面的方法不同,上面是all,需要所有的异步操作都执行完毕才会执行下一步的操作,但是race【赛跑】,就是发起的并行的异步操作,只要任何一个异步操作完成,就立即执行下一步的.then的操作(赛跑机制)
//这里链式操作,只要前面的错误了,后面的就都不会执行,因为只有执行成功之后才会返回一个Promise对象
import thenfs from 'then-fs'
//建立一个数组,来进行Promise结果的存储
const promiseArr = [
thenfs.readFile('./testfile/first.txt','utf8'),
thenfs.readFile('./testfile/second.txt','utf8'),
thenfs.readFile('./testfile/third.txt','utf8')
]
Promise.race(promiseArr) //等待所有的异步操作全部执行完毕
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err.message)
})
----这个时候打印的结果就是最先执行结束的异步操作的结果------------
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
基于Promise封装读文件的方法
之前都是使用的then-fs包来进行文件的读取,其实可以不加入依赖,自己手动使用Promise来进行文件的读取方法的编写
但是方法封装也是有要求的
- 方法的名称必须定义为getFile
- 方法接收一个形参fpath,表示要读取文件的路径
- 方法的返回值必须为Promise的实例对象 【 这里的普通的Promise创建的对象可能是读文件的异步操作,也可能是ajax的异步操作】
获取.then的两个实参 : 通过这个方法指定成功或者失败的回调函数,可以在function的形参中进行接收;可以调用resolve和reject来进行处理【这两个回调函数在Promise中
同时使用两个回调函数resolve和reject来进行处理,读取失败就调用reject,读取成功就调用resolve
所以这里可以简单写一个读取文件方法
//就是使用的还是原装的fs模块的内容,返回的就是一个读取文件的Promise对象
//自己封装读取文件的方法,方法名要求为getFile,方法有一个形参fpath代表路径
//必须有Promise类型的返回值
import fs from 'fs'
function getFile(fpath){
//这里只是创建了一个形式上的异步操作,可能是ajax或者文件,要变成具体的异步操作,需要传递一个function
//将具体的异步操作定义到function函数内部
return new Promise(function(resolve,reject){ //两个回调函数对应的就是处理的结果
//使用node本身的fs的文件操作模块表示为读文件的异步操作
fs.readFile(fpath,'utf8',(err,data) => { //resolve和reject就是两个可以直接使用的函数
if(err) return reject(err) //读取失败,就调用失败的回调函数
return resolve(data) //读取成功,就执行成功的回调函数resolve
})
})
}
//这里书写函数的时候不能使用XX = function(){} 而是使用 function XX(){} 这和原生的js是由区别的,不然下面就识别不到这个函数【根据前面的关键字进行识别】
需要注意的是,这里的reject和resolve都是代指的回调函数,这里不需要定义方法体,在Promise的then方法中可以对两个方法进行编写,这里可以使用promise的.then方法来执行失败或者成功的回调函数
//测试上面的自定义文件promise函数
getFile('./testfile/first.txt').then((data)=> {console.log(data)},(err)=> console.log(err.message))
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
------------------修改为一个不存在的文件---------------------------
PS D:\Vueprogramm\vue01> node Import.js
ENOENT: no such file or directory, open 'D:\Vueprogramm\vue01\testfile\firs.txt'
说明自定义Promise读取文件函数是可以成功执行的;这里的关键就是引入fs模块,同时要返回一个文件操作的Promise对象;同时要注意创建的时候加入回调函数的参数
async/await简化promise
async和await就是ES8引入的新语法,用来简化Promise的异步操作,在语法出现之前,只能通过链式的.then来处理Promise的异步操作
await在方法的内部进行使用,并且在该方法的前面必须加上async修饰 ---- 要实现Promise的异步特点,当一个方法的返回值是一个Promise对象的时候,在前面加上await修饰,方法的返回值就不是Promise异步对象,而是完整的内容; 使用await,就需要在最前面使用async修饰【代表异步操作】
const r1 = await thenfs.readFile('./','utf8') //这里返回的对象不是Promise实例对象,而是读取文件的内容
这里可以验证一下使用await前后的对象的类型 : 使用前是Promise对象的类型【Promise代表的是异步操作执行的结果】 — 因为不是专业的前端,所以这里就不深入了
function readAllFile(fpath1,fpath2,fpath3,fpath4,fpath5){
const data1 = thenfs.readFile(fpath1,'utf8')
console.log(data1) //这里可以验证一下修饰前后的区别
}
//调用验证
readAllFile('./testfile/first.txt')
---------------------首先就是不使用await关键字--------------
PS D:\Vueprogramm\vue01> node Import.js
Promise { _40: 0, _65: 0, _55: null, _72: null }
//之后改变一下,使用await进行修饰【 注意await和async一定要搭配使用】 --- async代表操作是异步的
async function readAllFile(fpath1,fpath2,fpath3,fpath4,fpath5){
const data1 = await thenfs.readFile(fpath1,'utf8')
------------------使用await关键字-----------
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
这里一定要记得在包裹await的方法前面加上async关键字,这样Promise返回类型就变成了文件的内容了,然后直接打印输出即可,这里输出的顺序就是console的顺序; 这样就不用担心.then的链式操作的缺点了
//使用await和async来简化.then的链式操作
//定义一个方法 : readAllFile 【和之前的Promise的静态方法.all和.race那里区分开来】
import thenfs from 'then-fs'
async function readAllFile(fpath1,fpath2,fpath3,fpath4,fpath5){
const dataArr = [
await thenfs.readFile(fpath1,'utf8'),
await thenfs.readFile(fpath2,'utf8'),
await thenfs.readFile(fpath3,'utf8'),
]
for(var i = 0; i < dataArr.length; i++) {
console.log(dataArr[i])
}
}
//调用验证 【上面的函数的参数没用到的就自动废弃】
readAllFile('./testfile/first.txt','./testfile/second.txt','./testfile/third.txt')
----------------------------------执行结果----------------------
PS D:\Vueprogramm\vue01> node Import.js
今年是2022年,壬寅虎年 -- 1
今天是2022年的农历正月十五,值此佳节之际 -- 2
祝大家元宵节快乐!-- 3
使用async和await相比之前的Promise的链式.then操作和Promise的静态方法all要简化
- 如果在function内部使用到await,并且一定要对function进行async进行修饰
- 在async方法中,第一个await之前的代码会同步执行,await之后的代码会异步执行 【 后面的都会异步执行,不管那一行是否有await关键字】
//使用await和async来简化.then的链式操作
//定义一个方法 : readAllFile 【和之前的Promise的静态方法.all和.race那里区分开来】
import thenfs from 'then-fs'
console.log("你好,echo")
async function readAllFile(fpath1,fpath2,fpath3,fpath4,fpath5){ //这里相当于就是一个异步的线程
console.log("第一个await之前相当于还是主线程的部分【同步】,await出现后就是异步线程了;在js中,存在主线程和异步线程时,永远是主线程先执行")
const dataArr = [
await thenfs.readFile(fpath1,'utf8'),
await thenfs.readFile(fpath2,'utf8'),
await thenfs.readFile(fpath3,'utf8'),
]
for(var i = 0; i < dataArr.length; i++) {
console.log(dataArr[i])
}
}
//调用验证
readAllFile('./testfile/first.txt','./testfile/second.txt','./testfile/third.txt')
console.log("你好,我是Alice")
------------------------执行结果--------------------
PS D:\Vueprogramm\vue01> node Import.js
你好,echo
第一个await之前相当于还是主线程的部分【同步】,await出现后就是异步线程了;在js中,存在主线程和异步线程时,永远是主线程先执行
你好,我是Alice
今年是2022年,壬寅虎年 -- 1
今天是2022年的农历正月十五,值此佳节之际 -- 2
祝大家元宵节快乐!-- 3
在js中,当出现异步线程的时候,主线程先执行; 这里就是当识别到await关键字的时候,执行异步操作,在这之前都是同步的操作,会先跳出函数执行主线程的操作,也就是输出Alice
⚠ : JS是一门单线程执行的编程语言,并且,在JavaScript异步任务要等同步任务执行完成之后才会执行
— 这和java的多线程还是有点区别的
EventLoop
JavaScript是一门单线程执行的语言,和java的多线程语言是不相同的;同一时间只能做一件事【所以说JS中的异步和java的异步不同,js的异步是指的异步任务】
所以JavaScript中如果有多个任务,像上面的文件读取就是多个任务;这样就形成了一个任务的等待队列;但是JS单线程执行的问题: 如果前一个任务非常耗时间,后续的任务就不得不一直等待,导致程序假死
为了防止这个问题: JS把任务分为两类 : 同步任务synchronous和异步任务asynchronous
同步任务【JavaScript主线程执行】
- 非耗时任务,指的是主线程上排队执行的任务;主线程的同步任务会优先异步任务执行
- 只有前一个任务执行完毕,才能执行后一个任务【同步执行】,按照代码的先后顺序
异步任务 【宿主环境执行】
- 耗时任务,比如上面的读取文件,异步任务是JavaScript委托给宿主环境进行执行的【js的执行环境 —浏览器、node等】
- 当异步任务执行完成之后,会通知JavaScript主线程执行异步任务的回调函数 ; 比如上面的异步任务就是3个文件的读取,当任务完成之后,通知主线程执行readAllFile这个回调函数 【所以当时的顺序是……】
同步任务和异步任务的执行过程 :
JavaScript主线程执行栈执行完就会出栈;宿主环境主要负责执行异步任务, 第三个部分就是任务消息队列;当执行栈中的同步任务执行完之后,会主动去拉去下一条消息
JavaScript的主线程的同步任务先执行;在宿主环境中执行的异步任务,谁先执行完毕,就把对应的回调函数放到消息队列中等待被拉取【先进先出】,当主线程栈中的任务执行完毕后,会主动拉去消息队列的任务进行执行,不断重复,直到执行完毕 ----- 所以整个的这种运行机制称为EventLoop ,事件循环 清空之后拉去任务,请空之后再拉,一直重复
这里分析一个面试题目
import thenfs from 'then-fs'
console.log('A')
thenfs.readFile('./','utf8').then(data => {console.log('B')})
setTimeout(() => {console.log('C')},0) //延时器
console.log('D')
这里分析可以发现,thenfs和setTimout为异步任务,同步任务先执行,所以就是AD,定时器任务先执行结束,所以输出C,最后输出B
宏任务与微任务
JavaScript把异步任务进行了进一步的划分,异步任务又分成了宏任务和微任务
宏任务 macrostack
异步Ajax请求,setTimeout,setInerval 延时器、定时器,文件操作等
微任务microstack
Promise.then, .catch, .finally ; process.nextTick 等
宏任务和微任务的执行顺序 : 每执行一个宏任务之后,会做出一个判断当前异步任务中是否又微任务,如果又微任务,就执行所有的微任务,执行完毕之后,执行下一个宏任务
每一个宏任务执行完毕之后,都会检查是否存在等待执行待执行的微任务;同步任务执行完毕之后也会检查执行微任务
- 实例: 分析下面代码的执行顺序
setTimeout(function(){ //异步任务
console.log('1')
})
new Promise(function (resolve) { //同步任务
console.log('2')
resolve()
}).then(function(){ //微任务
console.log('3')
})
console.log('4') //同步任务
首先new Promise对象属于同步任务,所以优先执行的是2,4;之后执行异步任务,先执行微任务队列的3,之后执行宏任务队列中的1
注意嵌套的时候,比如将new Promise放到延时器中,应该是整个宏任务的一部分,随着宏任务的执行而执行
API接口实例
基于MySQL数据库 + Express对外提供用户列表的接口服务 : 第三方包express和mysql8 ; ES6模块化 ; Promise; async/await
主要的实现步骤 :
- 搭建项目的基本结构
- 创建基本的服务器
- 创建db数据库操作模块
- 创建user_ctrl业务模块
- 创建user_router路由模块
所以首先就是要启用ES6的模块化支持 : package.json中加入module; 之后就是安装第三方的依赖包【npm i express@版本】 — 管理员身份
导入之后项目的node_modules中就有express和mysql了
创建服务器 app.js
这里使用的命令是nodemon 来进行运行; 需要先安装nodemon包到本机的全局地址,npm install -g nodemon ; 这样就可以正常运行到服务器
import express from 'express' //这里app.js直接放在项目下,直接默认导入
const app = express(); //创建一个服务器实例
app.listen(8070, ()=>{
console.log("server running at Cfeng.com")
})
//使用nodemon 命令运行
//listen EACCES: permission denied 0.0.0.0:80 允许被阻止,也就是80号端口被占用了,所以换一个端口号
------------------------执行结果--------------------
PS D:\Vueprogramm\vue01> nodemon app.js
[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
server running at Cfeng.com
创建db数据库操作模块
首先要使用第三包mysql,然后创建连接池,默认导出一个支持Promise API的pool
import mysql from 'mysql2'
//创建数据库连接池
const pool = mysql.createPool({ //和之前在spring中配置连接池类似
host: '127.0.0.1',
port:3306,
database:"cfengbase",
user: 'cfeng',
password:'**********'
})
//默认导出一个支持Promise API的pool
export default pool.promise()
创建user_ctrl模块
这里就要使用上面的db操作模块的默认导出的对象,并且将方法getAllUser导出供外界使用
import db from './db.js'
//获取用户所有的列表的数据
export async function getAllUser(req,res) { //在路由中使用,所以有req和res
//db.query()函数的返回值为Promise的实例对象,可以使用async/await进行简化
const [rows] = await db.query('SELECT stuno,stuname,stuclass FROM student')
res.send({
status: 0,
message:'获取用户成功',
data: rows, //从查询结果中解构成为一个数据
})
}
创建user_router模块
需要使用express模块
import express from 'express'
//从user_control模块中导入getAllUser函数
import {getAllUser} from './user_contrl.js'
//创建路由对象
const router = new express.Router()
//把路由挂载在服务器上 ,上面的路径就是/user为前缀
router.get('/user',getAllUser)
//使用Es6默认导出,共享数据router
export default router
使用try–catch捕获异常
上面的查询数据库会发生异常,所以需要进行处理,处理的方式和java相同
export async function getAllUser(req,res) { //在路由中使用,所以有req和res
//db.query()函数的返回值为Promise的实例对象,可以使用async/await进行简化
try{
const [rows] = await db.query('SELECT stuno,stuname,stuclass FROM student')
res.send({
status: 0,
message:'获取用户成功',
data: rows, //从查询结果中解构成为一个数据
})
}catch(e){
res.send({status:1,message:'获取用户列表失败',desc:e.message})
}
}
处理的方式相同就不再赘述
接下来就会正式进入Vue3.x的分享🎉