简单的 JavaScript 入门教程
简介
JavaScript 编程语言允许你在 Web 页面上实现复杂的功能。如果你看到一个网页不仅仅显示静态的信息,而是显示依时间更新的内容,或者交互式地图,或者 2D/3D 动画图像,或者滚动的视频播放器,等等——你基本可以确定,这需要 JavaScript 的参与。
JavaScript 是 动态的弱类型语言 即 变量类型不是在编译时确定的。可将一种类型转换为另一种类型(如:数字 0 转换为字符串 '0')的编程语言。
使用
内部代码
必须使用 <script></script>
标签进行包裹。
<html>
<head></head>
<body></body>
<script>
// JavaScript 代码
</script>
</html>
<script>
可以放在 body
head
标签中。
一个页面可以包含多个 <script>
标签。
会按照定义顺序执行。
以下代码在控制台中会以 b、c、a
的顺序打印字符。
<html>
<head>
<script>
console.log('b')
</script>
</head>
<body></body>
<script>
console.log('c')
</script>
</html>
<script>
console.log('a')
</script>
外部代码
即 使用 .js 文件存放 js 代码。
使用时以 <script>
的 src
属性导入。
src
属性值为 .js 文件的存放路径。
可以是相对路径或绝对路径。
-
相对路径(相对于当前文件的路径)
./
就是当前路径,../
是上级路径,./../
是当前路径的上级路径。 -
绝对路径(从根目录开始的路径)
C:\project\src\index.js
这就是一个绝对路径。
<html>
<head></head>
<body></body>
</html>
<script src='./index.js'></script>
概念
执行上下文
是一个概念,变量或者函数的上下文决定他们可以访问哪些数据,以及他们的行为。每个上下文都有一个关联的变量对象,这个上下文定义的所有变量和函数都存在于这个对象上。
执行上下文的类型:
-
全局上下文
全局上下文指的是最外层的上下文,根据 ECMAScript 实现的宿主环境,表示全局上下文的对象可能不一样。在浏览器中,全局上下文就是 window 对象,因此所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和方法。上下文在其所有代码都执行完毕会被销毁。
-
函数上下文
每个函数调用都有自己的上下文,当代码执行到函数时,函数的上下文会被推到一个上下文栈中,在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返回给之前的执行上下文,程序的执行流就是通过这个上下文栈来控制的。
-
eval/with 代码中的上下文 (eval 和 with 会导致作用域副作用和无法优化,所以基本上不用)
上下文属性
-
变量对象(variable object, VO)
是与执行上下文相关的数据作用域,存储了上下文中定义的变量和函数声明。
-
作用域链 (scope chain) 见下文
-
this 见下文
作用域
Javascript中的作用域说的是变量的可访问性和可见性。也就是说整个程序中哪些部分可以访问这个变量,或者说这个变量都在哪些地方可见。
需要注意的是JavaScript采用的是静态作用域,函数的作用域在函数定义的时候就已经决定。
- 作用域最为重要的一点是安全。变量只能在特定的区域内才能被访问,有了作用域我们就可以避免在程序其它位置意外对某个变量做出修改。
- 作用域也会减轻命名的压力。我们可以在不同的作用域下面定义相同的变量名。
作用域类型
-
全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。
-
函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问。
-
块级作用域
ES6引入了
let
和const
关键字,和var
关键字不同,在大括号中使用let
和const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。
// 全局作用域
// 以下三种方式声明的都是全局变量
const globalVaribale1 = 'a'
let globalVariable2 = 'b'
var globalVariable3 = 'c'
// 函数作用域
function functionField () {
// 只会作用于当前作用域
const functionVaribale = '2'
}
console.log(functionVaribale) // 会提示 undefined
// 局部作用域
if (true) {
const localVariable = '3'
if (true) {
let localVariable = '4'
console.log(localVariable) // 输出 4
}
console.log(localVariable) // 输出 3
}
静态作用域
即 作用域 在 编译阶段确定 而非 执行阶段确定。
使用静态作用域时,仅看源代码就能确定变量作用范围。
动态作用域就不行了。
let number = 42
function printNumber() {
// 无论 当前函数 在哪里执行
// 都只会打印 42
console.log(number)
}
function log() {
let number = 54
printNumber()
}
log() // 42
作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。
// 就像这样
if (true) {
const localVariable = '3'
if (true) {
let localVariable = '4'
// 在当前作用域找到了 localVariable
// 如果注释掉了当前作用域的 localVariable
// 那么就会向上查找 直到查找到变量
console.log(localVariable) // 输出 4
}
console.log(localVariable) // 输出 3
}
this
- this 永远指向一个 对象。
- this的指向完全取决于函数调用的位置
一般而言,谁调用了谁就是 this。
绑定规则
-
默认绑定
即 不匹配其他规则时 默认绑定 this 为 全局对象 window
-
隐式绑定
this 为上下文对象
-
显示绑定
即 通过
call()
、apply()
等方法绑定的 this -
new 绑定
即 通过
new
操作符进行声明的一个全新的对象 this 指向的就是它本身
箭头函数中的 this
箭头函数没有 this,谁调用谁就是 this。
语法
JavaScript 是区分大小写的!
语句后的分号(;)是可选的!
比如 user
和 User
就是两个不一样的变量。
需要注意的是,for 循环等语句中的分号是不能省略的。
// 分号不能省略
for (let i = 0; i < 10; i++) {
// ...
}
// 分号可以省略
const a = 'a';
注释
代码执行过程中注释会自动跳过不执行。
// 单行注释
// 在 // 之后的所有语句都会被注释
// const a = 'a'
/**
多行注释
*/
/**
*
* 这也是多行注释
*
*/
/** 多行注释也可以用于单行注释 */
const a = /** 不会影响在 / 后的语句 */ 'a'
/** /** 不能注释套注释 */ */
命名规范
不强制要求,但是推荐。
常见的命名规范:
-
驼峰命名法(CamelCase)
驼峰命名法应该我们最常见的一个。
这种命名方式使用大小写混合的格式来区别各个单词,并且单词之间不使用空格隔开或者连接字符连接的命名方式。
-
大驼峰
SearchKeyWord
、ChangeStatus
、ToggleUserName
... -
小驼峰
searchKeyWord
、changeStatus
、toggleUserName
...
-
-
蛇形命名法(snake_case)
各个单词之间通过下划线
_
连接search_content
、user_name
、login_user_password
...
统一大小写,不要一会 getUserName
, 一会 SetUserName
。
尽量做到命名即注释,如:
getUserName
获取用户名setUserName
设置用户名createUser
创建用户userStatus
用户状态
尽量做到命名具体,如:
getUserNameLength
获取用户名长度 而不是getLength
获取长度setUserName
设置用户名 而不是setName
设置名称changeLoginStatus
变更登录状态 而不是changeStatus
变更状态
尽量使用英文而不是拼音命名。
根据不同的需求进行变更。
// 在这个循环语句中 如果 i 没有其他任何用途 可以直接使用 i
for (let i = 0; i < 10; i++) {
// do something...
}
// 如果是循环一个二维数组矩阵
// 二维数组:
// [
// [],
// []
// ]
// row 代表的是二维数组的行
// col 代表的是二维数组的列
for (let row = 0; row < 10; row++) {
for (let col = 0; col < 10; col++) {
// do something...
}
}
{
// ...
// 有些时候可能这个变量可能就只会用一次
// 那么就不需要额外声明了
if (true) {
// ...
}
let flag = true
// ...
if (flag) {
// ...
}
// ...
}
类型
JavaScript 是动态类型的语言,因此可以随意转换类型。
let a = ''
a = 0
a = true
a = []
// 不会报错
// 但是不推荐这样
// 尽量做到一个变量一个类型
-
字符串
// 就是由字符组成的一串字符 const variable = '字符串'
-
数字
// 可以是整数 12 或者浮点数 3.14 const variable = 0
-
对象
// 是一个键值对 (key: value) 的变量 // 可以用数字或字符串作为 key // 如果 key 不是合法的 JavaScript 变量名 // 那么必须使用 引号("",'' 都可) 进行包裹 const variable = { b: 'b' } console.log(variable) // { b: 'b' } // 可以使用 [] . 访问属性 variable['a'] = '' variable.a = 'a' console.log(variable) // { b: 'b', a: 'a' }
-
BigInt
// 任意精度的整数 // 可以安全地存储和操作大整数 // 甚至可以超过数字的安全整数限制 const variable = /** 123n */ BigInt(123)
-
数组
// 属于对象的一种 // 是一个存放多个元素的集合 // 数组的索引从 0 开始 // 元素中多个逗号 如下所示 不会报错 而是会生成一个 undefined 元素 // 如果是在最后一位元素后的逗号,则会被忽略 const variable = [2,,4] console.log(variable) // [2, undefined,4] variable[0] = 1 variable[1] = 2 console.log(variable) // [1, 2,4]
-
布尔
const variable = true // true 为 真 可以 // false 为 假 不可以
-
null
// 空值 没有 const variable = null
-
未定义
// 未定义的值 // 用 let、 var 声明但为赋值的变量默认就是 undefined // 可以通过 undefined 判断一个变量是否赋值 const variable = undefined
-
符号
// 必定唯一值 // 常用于 对象的 key const variable = Symbol()
引用类型 和 基础类型
基础类型:字符串、数字、null、undefined、布尔、symbol。
引用类型:除了基础类型以外的类型都是引用类型。
基础类型保存的是值。引用类型保存的是指向值的引用。
// 基础类型
const str = 'a'
const strCopy = str
// 保存的都是具体值 'a'
// 引用类型
const obj = {}
const arr = []
// 保存的是一个指向变量的引用
const copyStr = str
// 变量 str 的值是基础类型
// str 变更时 copyStr 不会变更
const copyArr = arr
// arr 是 数组 是一个引用类型
// 因此 当 arr 的内容发生了变更时,copyArr 会随之变更
// 因为 copyArr 并没有复制 arr 引用的值
// 而是复制了 arr 保存的引用
// 可以理解为 你叫张三,你的网名是李四。张三李四都是你
// 张三被打了,李四也可以说他被打了,因为张三李四都是一个人
深拷贝
// 一般是使用 JSON.parse(JSON.stringify(value))
// 其中 value 是需要进行深拷贝的值
// 一般用于引用类型
// 这是浅拷贝
const arr = []
const copyArr = arr
console.log(arr,copyArr) // [], []
arr[0] = 1
console.log(arr,copyArr) // [1], [1]
// 深拷贝
const arr = []
const copyArr = JSON.parse(JSON.stringify(arr))
console.log(arr,copyArr) // [], []
arr[0] = 1
console.log(arr,copyArr) // [1], []
变量
JavaScript 中 使用 var
、 let
、 const
三种方式声明变量。
变量名只能以 字母 A-z
下划线 _
美元符号 $
开头。
变量名也能加上数字。
合法变量名: Number_hits
,temp99
,$credit
和 _name
。
var variableNumber = 0
let variableString = ''
const variableBoolean = true
var
方式声明的是全局变量 在代码中的任何一个地方都能访问到。(如果没有在函数作用域内的话。)
现在不推荐使用 var
声明变量(具体要根据项目变更)。
let
方式声明的是局部变量 只会作用于当前定义的作用域。
let
和 var
声明的变量都是可变的。可以不赋予初始值。
const
声明的也是局部变量。
但它声明的是常量 即 一旦声明就不会更改的变量。
如果是不会变更的变量 那么推荐使用 const
否则就是 let
。
需要注意 const
声明的变量必须赋予初始值。
如果变更 const
的值就会发生报错。
如果 const
定义的常量是一个 Object
、 Array
之类的引用类型,那么变更属性和元素是可以的。
const a = 'a'
a = 'b' // 报错
const b = {}
b = '' // 报错
b.a = '' // 可以
const c = []
c = '' // 报错
c[0] = 1 // 可以
运算符
// 以下例子中所举变量
const Obj = {
a: 'a'
}
const a = 0
function parent () {
this.a = 'a'
}
const child = new parent()
-
算术运算符
算术运算符以二个数值(字面量或变量)作为操作数,并返回单个数值。
符号 描述 例子 + 加法 1 + 2 = 3 - 减法 2 - 1 = 1 * 乘法 1 * 2 = 2 / 除法 2 / 1 = 2 % 取余 2 % 2 = 0 ** 次方 2 ** 7 = 128 -
一元运算符
一元运算符只有一个操作数。
符号 描述 例子 delete 删除对象 delete Obj.a void 无返回值 const a = void 1 typeof 判定类型 typeof a + 将操作转换为 Number 类型 +'5' - 将操作转换为 Number 类型 -‘5’ ~ 位反转 a = 0; ~a = -1 ! 取反 将真的变成假的 假的变成真的 !true -
关系运算符
算术运算符以二个数值(字面量或变量)作为操作数,并返回单个数值。
符号 描述 例子 in 是否包含属性 'a' in Obj instanceof 用于检测构造函数的 prototype
属性是否在某个实例对象的原型链上child instanceof parent < 小于 1 < 2 > 大于 1 > 2 >= 大于等于 1 >= 2 <= 小于等于 1 <= 2 -
相等运算符
如果相等,操作符返回的是布尔类型的 true,否则是 false。
符号 描述 例子 == 等于 1 == 1 === 严格等于 1 === 1 != 不等于 1 != 2 !== 严格不等于 1 !== 2 -
位移运算符
在二进制的基础上对数字进行移动操作。
符号 描述 例子 << 按位左移 1 << 2 >> 按位右移 1 >> 2 >>> 按位无符号右移 1 >>> 2 -
二进制运算符
二进制运算符将它们的操作数作为 32 个二进制位(0 或 1)的集合,并返回标准的 JavaScript 数值。
符号 描述 例子 & 按位与 1 & 2 | 按位或 1| 2 ^ 按位异或 1 ^ 2 -
逻辑运算符
二进制运算符将它们的操作数作为 32 个二进制位(0 或 1)的集合,并返回标准的 JavaScript 数值。
符号 描述 例子 && 逻辑和 true && true || 逻辑或 true|| false ?? 空值合并运算符,如果 ?? 前面是 null 或 undefined,取后面的默认值。 undefined ?? 0 -
可选链式运算符
如果引用是空值 (
undefined
或null
),可选链运算符将返回undefined
而不是导致错误。符号 描述 例子 ?. 访问属性可选时使用 obj?.a const obj = {} // 访问 obj 的 a 属性 obj.a // 报错 因为没有定义 a 属性 obj?.a // 不会报错 即使没有定义 a 属性
-
条件运算符
符号 描述 例子 条件 ? 为真执行 : 为假执行 简化的if-else块 true ? true : false if (true) { // 真 } else { // 假 } // 可以改变为 true ? /** 真 */ true : /** 假 */ false
-
赋值运算符
以上提及的符号都能添加在赋值运算符前进行运算。
符号 描述 例子 = 赋值 const a = 'a'
流程控制
流程控制就是来控制我们的代码按照什么结构顺序执行。
流程控制主要有三种结构: 顺序结构 分支结构和循环结构。
顺序结构
顺序结构程序中最简单最基本的流程控制。
它没有特定的语法结构,程序会按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
// 每天的内容
console.log("早上出门上学")
console.log("上午上课")
console.log("中午吃饭")
console.log("下午上课")
console.log("下午放学回家")
循环结构
循环可以重复执行某些代码。
在程序中,一组被重复执行的语句被称之为循环体,能否继续重复执行,取决于循环的终止条件,由循环体及循环的终止条件组成的语句,被称为循环语句。
for (let i = 0; i < 10; i++) {
// 只会执行 10 次
// i 从 0 开始
// 只要 i < 10 成立
// 就一直 i++
// 循环体内容...
// 可以使用 break 停止循环
break
for (let j = 0; j < 10; j++) {
// 可以使用 continue 跳过当前循环
countine
}
}
// 死循环
// 如果没有在里面使用 break 停止循环
// 则会一直循环
while (true) {}
do {
// 先执行一次再判断是否符合条件
// 符合就继续循环
} while (true)
for (const item in {}) {
// 遍历对象 key
}
for (const item of []) {
// 遍历数组 元素
}
// 一年 365 天都在上学
for(let day = 0; day < 365; day++){
console.log("早上出门上学")
console.log("上午上课")
console.log("中午吃饭")
console.log("下午上课")
console.log("下午放学回家")
}
分支结构
由上到下执行代码的过程中,根据不同的条件,执行不同的路径代码(执行代码多选一的过程),从而得到不同的结果。
const a = 1
if (a === 1) {
// a 等于 1 时执行
}
else if (a === 2) {
// a 等于 2 时执行
}
else {
// a 不等于 1 和 2 时执行
}
switch (a) {
case 1:
// a 等于 1 时执行
break
case 2:
// a 等于 2 时执行
break
default:
// 都不等于时默认执行
}
for(let day = 0; day < 365; day++){
// 工作日才需要上学
if (isWork) {
console.log("早上出门上学")
console.log("上午上课")
console.log("中午吃饭")
console.log("下午上课")
console.log("下午放学回家")
}
else{
console.log("今天放假不上学")
}
}
错误处理
try {
// 可能会出错的代码
// 使用 throw 抛出错误
// 两种方式都行
throw '错误消息'
throw new Error('错误消息')
}
catch (e) {
// 如果出错 则 自动捕获 错误 e 并传入当前代码块
}
finally {
// 在 try 和 catch 执行完毕后总是执行
}
for(let day = 0; day < 365; day++){
try {
sleep() // 睡觉 如果睡过头了会抛出一个错误
}
catch (e) {
// 睡过头了处理
if (isWork) {
// 工作日需要上学
// 需要赶紧去学校
}
else{
// 休息日
// 接着睡
}
}
finally {
wakeUp() // 最终都要起床
}
}
对象
JavaScript 中万物皆对象。
对象是有着属性,方法的数据集合。
比如说一辆车,它有颜色,大小,速度等属性,有启动,刹车等方法。
所有车都有属性,但是内容会不同,比如车主,车牌等等。所有车都有方法,但是不会同时执行。比如不能同时行驶和停车。
// 对象 车
const Car = {
// 车颜色 红色
color: 'red',
// 车大小 20
size: 20,
// 车的启动方法
start() {
// ...
},
// 车的停止方法
stop() {
// ...
}
}
函数
函数是 JavaScript 中的基本组件之一。一个函数是 JavaScript 过程,即一组执行任务或计算值的语句。要使用一个函数,你必须将其定义在你希望调用它的作用域内。
// 函数以关键字 function 定义
function exampleFunction () {
// ...
}
传参
// arg1, arg2 是形参
// 没有实际值
function exampleFunction (arg1, arg2) {
// 函数返回值
// 函数可以不返回值
return arg1 + arg2
}
// 传入的 1,2 2,3 是实参
exampleFunction(1,2) // 3
exampleFunction(2,3) // 5
参数默认值
// arg1 = 0 给与了 arg1 一个默认值
function exampleFunction (arg1 = 0, arg2) {
// arg2 进行一个判断 如果 arg2 未传入
// 即 arg2 = undefined || 0
// 则 arg2 = 0
arg2 = arg2 || 0
return arg1 + arg2
}
箭头函数
箭头函数常用于回调函数等。const exampleFunction = () => {}
// 箭头函数如果只有一个参数 那么包裹参数的括号可以省略
// 如果只有一个语句可以省略花括号
// 省略后会默认返回执行结果
const numAddOne = a => a + 1
numAddOne(1) // 2
// 需要注意 如果返回的是对象 则需要使用 花括号以及 return 语句
const returnObject = obj => {a:'a'} // 不行 {} 被识别成了代码块
// 可以
const returnObject = obj => {
return {a: 'a'}
}
// 此外,还可以使用圆括号包裹一下
const returnObject => object => ({ a:'a'})
闭包
闭包是 JavaScript 中最强大的特性之一。JavaScript 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。
但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。
此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数的生存周期将比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
闭包可以被理解为这样的场景:
- 你参加了一场面试。
- 在面试过程中,面试官会向你提出一些问题,这些问题中既有外部的内容(例如,“讲讲闭包”),也有在面试过程中产生的内容(例如,“你可以讲个例子吗?”)。
- 面试结束后,面试官将对你的评价交给人事部门。
对于没有参与面试的其他人事部门成员来说,他们不知道你和面试官在面试中讨论了什么内容,但他们可以根据面试官的评价来决定你的后续安排。
面试(高阶函数):面试就像一个高阶函数,它有外部的背景(问题和环境),同时会产生一些内部的信息(你在面试中给出的回答和例子)。这些信息最终会成为面试后的评价(闭包)。
面试官的评价(闭包函数):面试结束后,面试官的评价就像一个闭包函数,它能够记住面试中讨论过的问题和回答(即使面试已经结束),并将这些评价交给人事部门。
面试的内容(外部作用域):在面试中,面试官的问题和你给出的答案相当于外部作用域中的变量。即使面试结束,这些内容依然会通过闭包被保存。
function interview() {
let question = '讲讲闭包'; // 外部的内容:问题
let example = '以我们当前进行的这场面试为例子'; // 在面试中产生的内容:例子
return function evaluation() {
console.log(`问题: ${question}`); // 闭包函数:能够访问外部内容
console.log(`答案: ${example}`);
};
}
const interviewFeedback = interview(); // 参加面试并得到反馈
interviewFeedback(); // 输出面试中的内容: 问题和答案
异步编程
JavaScript 是单线程语言,这意味着它就像一条单行道一样,一次只能通过一辆车。也就是说,它一次只能执行一个任务,其他任务只能排队等候。
但可以通过异步编程让单行道可以错峰出行,使得可以处理耗时的操作(如网络请求、文件读取等),从而避免阻塞主线程。
可以这样理解:
你希望下载一份大文件,但是这份文件非常的大,需要下载一天的同时,对于网络的占用也很高。如果你开始了下载,那么在此期间只要你在家你就无法通过网络进行任何事。
而采用异步的方式,你就可以在早上出门上班的时候开始下载。当你下班回来后就刚好下载完毕。期间家里无人,网络占用高没事,你也可以在公司正常进行工作。
JavaScript 提供了以下几种方法来处理异步操作:
- 回调函数(Callback):通过传递函数作为参数来实现异步操作完成后的回调。比如外卖到了后,会给你打个电话告诉你。
- Promises:Promise 是一个代表异步操作最终完成或失败的对象,提供了更简洁的语法来处理异步操作。比如一个项目需求被提出,正在处于待完成状态,最后会变成已完成或已废弃状态。
- async/await:async/await 是基于 Promise 的语法糖,使异步代码更像同步代码,易于理解和维护。
回调函数
通过传递函数作为参数来实现异步操作完成后的回调。但是容易产生回调地狱。
// 点外卖,需要留一个手机号联系你
function buyFoodDelivery(phoneNumber) {
// 需要联系店家有外卖订单
callShop(
// 制作食物
makeFood(
// 制作食物完成需要呼叫外卖员
callDeliveryMan(
// 外卖员拿到外卖需要到达目的地
arriveDestination(
// 外卖员到达目的地后需要联系你
callYou(phoneNumber)
)
)
)
)
}
Promise
是一个代表异步操作最终完成或失败的对象,提供了更简洁的语法来处理异步操作。
function addProjectRequirement(description) {
return new Promise((requirementCompleted, requirementAbandoned) => {
if (canDoing()) {
requirementCompleted('需求完成了')
} else {
requirementAbandoned('需求做不了')
}
})
}
addProjectRequirement('一个新的需求')
.then(message => {
console.log(message) // 需求完成了
})
.catch(reason => {
console.log(reason) // 需求做不了
})
.finally(() => {
console.log('这个需求结束了')
})
async/await
async/await 是基于 Promise 的语法糖,使异步代码更像同步代码,易于理解和维护。
async function addProjectRequirement (description) {
// 等待做需求
const status = await doing()
if (status) {
return '需求完成了'
}
else {
throw new Error('需求做不了')
}
}
addProjectRequirement('一个新的需求')
.then(message => {
console.log(message) // 需求完成了
})
.catch(reason => {
console.log(reason) // 需求做不了
})
.finally(() => {
console.log('这个需求结束了')
})