js_model_tutorial
!!待更新
现代 JavaScript 教程的学习笔记,它是一份不错的学习资源,感谢开源。
中文链接
函数
代码示例
函数的声明方式
function name(parameters, delimited, by, comma) {
/* code */
}
作为参数传递给函数的值,会被复制到函数的局部变量。
函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
函数可以返回值。如果没有返回值,则其返回的结果是 undefined。
建议在函数中主要使用局部变量和参数,而不是外部变量。
函数命名
- 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
- 一个函数是一个行为,所以函数名通常是动词。
- 目前有许多优秀的函数名前缀,如 create…、show…、get…、check… 等等。使用它们来提示函数的作用。
函数表达式
代码示例
- 函数是值。它们可以在代码的任何地方被分配,复制或声明。
- 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。
- 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。
- 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。
- 函数表达式在执行流程到达时创建。
箭头函数
代码示例
- 不带花括号:
(...args) => expression
— 右侧是一个表达式:函数计算表达式并返回其结果。
- 带花括号:
(...args) => { body }
— 花括号允许我们在函数中编写多个语句,但是我们需要显式地 return 来返回一些内容。
对象
代码示例
- 对象是具有一些特殊特性的关联数组
- 存储属性(键值对)
- 属性的键必须是字符串或者symbol
- 值可以是任何类型
- 访问属性
- 点符号:obj.property
- 方括号:obj["property"]
- 其他操作
- 删除属性:delete obj.property
- 检查是否存在给定键的属性:"key" in obj
- 遍历对象:for(let key in obj)循环
- 对象是通过引用被赋值或复制的。即变量存储的不是”对象的值“,而是值的”引用“(内存地址)。
- JS中其他类型的对象
- Array用于存储有序数据集合
- Date用于存储时间日期
- Error用于存储错误信息
垃圾回收
代码示例
- 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
- 当对象是可达状态时,它一定是存在与内存中的。
- 被引用与可访问(从一个根)不同:一直相互连接的对象可能整体都不可达。
Symbol 类型
代码示例
Symbol是唯一标识符的基本类型
Symbol是使用带有可选描述(name)的Symbol()调用创建的
Symbol总有不同的值,即使它们有相同的名字。如果希望同名的Symbol相等,那么应该使用全局注册表:Symbol.for(key)返回一个以key作为名字的全局Symbol。
使用Symbol.for多次调用key相同的Symbol时,返回的就是同一个Symbol
Symbol的两个主要的使用场景:
“隐藏”对象属性。如果想要向“属于”另一个脚本或者库的对象添加一个属性,可以创建一个Symbol并
使用它作为属性的键。Symbol属性不会出现在for .. in中,因此它不会意外地被与其他属性一起处理。
并且,它不会被直接访问,因为另一个脚本没有我们地symbol。因此,该属性将受到保护,防止被意外
或重写
JS使用了许多系统地Symbol,这些Symbol可以作为Symbol.*访问
从技术上来讲,Symbol不是100%隐藏的
对象方法,"this"
代码示例
- 存储在对象属性中的函数被称为"方法"
- 方法允许对象进行像object.doSomething()这样的操作
- 方法可以讲对象引用为this
- this的值是在程序运行时得到的
- 一个函数在声明时,可能就使用了this,但是这个this只有在函数被调用时才会有值
- 可以在对象之间复制函数
- 以"方法"的语法调用函数时:object.method(),调用过程中的this值是object
对象-原始值转换
代码示例
对象到原始值的转换,是由许多期望以原始值作为值得内建函数和运算符自动调用的,包括三种类型:
- "string" 对于alert和其他需要字符串的操作
- "number" 对于数学运算
- "default" 少数运算符
规范明确描述了哪些运算符使用哪个hint。很少有运算符"不知道期望什么"并使用"default"hint。
通常对于内建对象,"default"hint的处理方式与"number"相同,因此实践中,最后两个hint常常结合在一起
转换算法是:
- 调用
obj[Symbol.toPrimitive](hint)
如果这个方法存在
- 否则,如果hint是"string"
- 尝试obj.toString()和obj.valueOf(),无论那个存在
- 否则,如果 hint 是 "number" 或者 "default"
- 尝试 obj.valueOf() 和 obj.toString(),无论哪个存在。
构造器和操作符"new"
代码示例
- 构造函数,或简称构造器,就是常规函数,但有个共同约定,就是其命名首字母要大写
- 构造函数只能使用new来调用。这样的调用意味着在开始时创建了空的this,并在最后返回填充了值的this
- 当一个函数被使用new操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并被分配给this
- 函数体执行。通常它会修改this,为其添加新的属性。
- 返回this的值
数字类型
代码示例
要写很多零的数字
- 将"e"和0的数量附加到数字后。123e6 <=> 123000000
- "e"后面的负数将使数字除以1后面接着给定数量的零的数字。123e-6 <=> 0.000123
对于不同的数字系统
- 可以直接在十六进制(0x),八进制(0o)和二进制(0b)系统中写入数字
- parseInt(str, base)将字符串str解析为给定的base数字系统中的整数,2 <= base <= 36
- num.toString(base)将数字转换为在给定的base数字系统中的字符串
要将12pt和100px之类的值转换为数字
- 使用parseInt/parseFloat进行"软"转换,它从字符串中读取数字,然后返回在发生error前可以读取到的值
- 使用Math.floor、Math.ceil、Math.trunc、Math.round或者num.toFixed(precision)进行舍入
- 请确保记住使用小数时会损失精度
字符串
代码示例
- 有3种类型的引号,反引号允许字符串跨越多行并可以使用${…}在字符串中嵌入表达式
- JavaScript 中的字符串使用的是 UTF-16 编码
- 可以使用像 \n 这样的特殊字符或通过使用 \u… 来操作它们的 unicode 进行字符插入
- 获取字符时,使用 []
- 获取子字符串,使用 slice 或 substring 或 substr
- 字符串的大/小写转换,使用:toLowerCase/toUpperCase
- 查找子字符串时,使用 indexOf 或 includes/startsWith/endsWith 进行简单检查
- 根据语言比较字符串时使用 localeCompare,否则将按字符代码进行比较
- 其他几种有用的字符串方法
- str.trim() — 删除字符串前后的空格 (“trims”)
- str.repeat(n) — 重复字符串 n 次
数组
代码示例
- 数组是一种特殊的对象,适用于存储和管理有序的数据项
- 声明:let arr = [item1, item2, item3]; let arr = new Array(item1, item2, item3);
- 调用 new Array(number) 会创建一个给定长度的数组,但不含有任何项
- length属性是数组的长度,准确地说,它是数组最后一个数字索引值加一,它由数组方法自动调整
- 手动缩短length,那么数组就会被截断,且不可逆
- 用操作双端队列的方式使用数组
- push(items)在末端添加items项
- pop()从末端移除并返回该元素
- unshift(items)从首端添加items项
- shift()从首端移除并返回该元素
- 遍历数组的方法
- for(let i = 0; i < arr.length; i++) 运行最快,可兼容旧版本浏览器
- for(let item of arr) 现代语法,只能访问items
- for(let i in arr) 永远不要使用这个
数组方法
代码示例
添加/删除元素
- push(items) 向尾端添加元素
- pop() 从尾端提取一个元素
- shift() 从首端提取一个元素
- unshift(items) 向首端添加元素
- splice(pos, deleteCount, items) 从index开始删除deleteCount个元素,并在当前位置插入items
- slice(start, end) 创建一个新数组,将从位置start到位置end(但不包括end)的元素复制进行
- concat(items) 返回一个新数组:复制当前数组的新元素,并向其中添加items。如果items中的任意一项是一个数组,那么就取其元素
搜索元素
- indexOf/lastIndexOf(item, pos) 从位置pos开始搜索item,搜索到则返回该项的索引,否则返回-1
- includes(value) 如果数组有value,则返回true,否则返回false
- find/filter(func) 通过func过滤元素,返回使func返回true的第一个值/所有值
- findIndex() 和find()类似,但是返回索引而不是值
遍历元素
- forEach(func) 对每个元素都调用func,不返回任何内容
转换数组
- map(func) 根据对每个元素调用func的结果创建一个新数组
- sort(func) 对数组进行原位排序,然后返回它
- reverse() 对数组进行原位反转,然后返回它
- split/join 将字符串转换为数组并返回它
- reduce(func, initial) 通过对每个元素调用func计算数组的单个值,并在调用之间传递中间结果
其他
- Array.isArray(arr) 检查arr是否是一个数组
可迭代对象
代码示例
可以应用 for..of 的对象被称为可迭代的
技术上来说,可迭代对象必须实现 Symbol.iterator 方法
obj[Symbol.iterator] 的结果被称为迭代器(iterator),由它处理进一步的迭代过程
一个迭代器必须由next()方法,它返回一个{done: Boolean, value: any}对象,
这里的done: true表明迭代结束,否则value就是下一个值
Symbol.iterator方法会被 for..of 自动调用,但是也可以直接调用
内置的可迭代对象例如字符串和数组,都实现了Symbol.iterator
字符串迭代器能够识别代理对,即UTF-16拓展字符
有索引属性和length属性的对象被称为类数组对象,这种对象可能还具有其他属性和方法,但是没有数组的内建方法
Array.from(obj[, mapFn, thisArg])将可迭代对象或者类数组对象obj转化为真正的数组Array,然后就可以对它应用数组的方法,可选参数mapFn和thisArg允许将函数应用到每个元素
映射和集合
代码示例
弱映射和弱集合
代码示例
WeakMap是类似于Map的集合,它仅允许对象作为键,并且一旦通过其他方式无法访问它们,
便会将它们与其关联值一同删除
WeakSet是类似于Set的集合,它仅存储对象,并且一旦通过其他方式无法访问他们们,便会将其删除
均不支持引用所有键或其计数的方法和属性,仅允许单个操作
WeakMap和WeakSet被用作“主要”对象存储之外的“辅助”数据结构,一旦将对象从主存储器中删除,
如果该对象仅被用作WeakMap或WeakSet的键,那么它将被自动清除
解构赋值
代码示例
时间和日期
代码示例
- 在JS中,日期和时间使用Date对象来表示,但是不能只创建日期,或者只创建时间,Date对象总是同时创建两者
- 月份从0开始计数,即一月是0
- 一周的某一天 getDay() 同样是从0开始计算,0代表星期日
- 当设置了超出范围的组件时,Date会自我进行校准,这一点对于日/月/小时的加减很有用
- 日期可以相减,得到的是以毫秒表示的两者的差值。因为当Date被转换为数字时,Date对象会被转换为时间戳
- 使用 Date.now() 可以更快地获取当前时间的时间戳
- 和其他系统不同,JS中的时间戳以毫秒为单位,而不是秒
JSON方法,toJSON
代码示例
- JSON是一种数据格式,具有自己的独立标准和大多数编程语言的库
- JSON支持object、array、string、number、boolean、null
- JS提供序列化成JSON的方法 JSON.stringify 和解析JSON的方法 JSON.parse
- 这两种方法都支持用于只能读/写的转换函数
- 如果一个对象具有toJSON,那么它会被JSON.stringify调用
递归和堆栈
代码示例
- 递归式编程的一个术语,表示从自身调用函数。递归函数可用于以更优雅的函数解决问题
- 递归定义的数据结构是指可以使用自身来定义的数据结构
- 任何递归函数都可以被重写为迭代形式,有时这是在优化代码时需要做的,但对于大多数任务来说,递归方法足够快,并且容易编写和维护
Rest参数与Spread语法
代码示例
- 当在代码中看到"…"时,它要么是rest参数,要么就是spread语法
- 简单的区分:
- 若 … 出现在函数参数列表的最后,那么它就是rest参数,它会把参数列表中剩余的参数收集到一个数组中
- 若 … 出现在函数调用或类似的表达式中,那它就是spread语法,它会把一个数组展开为列表
- 使用场景:
- Rest参数用于创建可接受任意数量参数的函数
- Spread语法用于将数组传递给通常需要含有许多参数的列表的函数
- "旧式的" arguments(类数组对象) 依然能获取函数调用中的所有参数,但不推荐,推荐使用Rest参数
闭包
代码示例
旧时的"var"
代码示例
var与let/const 有两个主要的区别
- var 声明的变量没有块级作用域,它们的最小作用域就是函数级作用域
- var 变量声明在函数开头就会被处理(脚本启动对应全局变量)
全局对象
代码示例
全局对象包含应该在任何位置都可见的变量
全局对象有一个通用名称globalThis
但是更常见的是使用“老式”的环境特定(environment-specific)的名字,
例如 window(浏览器)和 global(Node.js)。
由于 globalThis 是最近的提议,因此在 non-Chromium Edge 中不受支持(但可以进行 polyfills)
仅当值对于项目而言确实是全局时,才应将其存储在全局对象中,并保持其数量最少
在浏览器中,除非使用modules。否则使用var声明的全局函数和变量会变成全局对象的属性
为了使代码面向未来并更易于理解,应该使用直接的方式访问全局对象的属性,如window.x
函数对象,NFE
代码示例
- 函数就是对象
- 一些属性
- name -- 函数的名字。通常取自函数定义,但如果函数定义时没设定函数名,JS会尝试通过函数的上下文猜一个函数名
- length -- 函数定义时入参的个数,Rest参数不参与计数
- 如果函数是通过函数表达式的形式被声明的,并且附带了名字,那么它就被称为命名函数表达式,这个名字可以用于在该函数内部进行指调用,例如递归调用等
"new Function" 语法
代码示例
- 使用 new Function 创建的函数,它的 [[Environment]] 指向全局词法环境,而不是函数所在的外部词法环境
- 不能在 new Function 中直接使用外部变量
- 这有助于降低我们代码出错的可能
- 从代码架构上讲,显式地使用参数传值是一种更好的方法,并且避免了与使用压缩程序而产生冲突的问题
调度:setTimeout 和 setInterval
代码示例
- setTimeout(func, delay, …args)和setInterval(func, delay, …args)方法允许在delay毫秒之后允许func一次或者以delay毫秒为时间间隔周期性运行func
- 要取消函数的执行,应该调用clearInterval/clearTimeout,并将setInterval/setTimeout返回的值作为入参传入
- 嵌套的setTimeout比setInterval用起来更加灵活,允许更精确地设置两次执行之间的时间
- 零延时调度setTimeout(func, 0)(或者setTimeout(func))用来调度需要尽快执行的调用,但是会在当前脚本执行完成后进行调度
- 浏览器会将setTimeout或setInterval的五层或者更多层嵌套调用(调用五次之后)的最小延时限制在4ms,这是一个历史遗留问题
- 注意,所有的调度方法都不能保证确切的延时,例如,浏览器内的计时器可能由于许多原因而变慢
- CPU过载
- 浏览器页签处于后台模式
- 笔记本电脑用的电池供电,使用电池供电会以降低性能为代价提升续航
装饰者模式和转发,call/apply
代码示例
装饰器是一个围绕改变函数行为的包装器,其主要工作任由该函数来完成
装饰器可以被看作是可以添加到函数的"features"或"aspects",可以添加一个或添加多个,而这一切都无需更改其代码
为了实现cachingDecorator,可以运用以下方法
- func.call(context, arg1, arg2) -- 给定的上下文和参数调用func
- func.apply(context, args) -- 调用func将context作为this和类数组的args传递给参数列表
通用的呼叫转移(call forwarding)通常是使用apply完成的
一个方法借用(method borrowing),就是从一个对象中获取一个方法,并在另一个对象的上下文中"调用"它。
采用数组方法并将它们应用于参数arguments是很常见的。另一种方法是使用Rest参数对象,该对象是一个真正的数组。
函数绑定
代码示例
- 方法func.bind(context, …args)返回函数func的"绑定的变体",它绑定了上下文this和第一个参数
- 通常应用bind来绑定对象方法的this,这样就可以把它们传递到其他地方使用
- 当绑定一个现有的函数的某些参数时,绑定后的函数被称为partial
- 当不想一遍又一遍重复相同的参数时,partial非常有用
深入理解箭头函数
代码示例
- 箭头函数没有this、arguments,这对装饰器来说非常有用
- 箭头函数也不能使用new进行调用
- 箭头函数也没有super
- 箭头函数时针对那些没有自己的"上下文",但在当前上下文中起作用的短代码的
属性标志和属性描述符
代码示例
属性的getter和setter
代码示例
原型继承
代码示例
- 在JS中,所有的对象都有一个隐藏的[[Prototype]]属性,它要么是另一个对象,要么就是null
- 可以使用obj.__proto__访问它
- 通过[[Prototype]]引用的对象被称为"原型"
- 想要读取obj的一个属性或者调用一个方法,并且它不存在,那么JS就会尝试在原型中查找它
- 写/删除操作直接在对象上进行,它们不使用原型(假设它是数据类型,不是setter)
- 如果想调用obj.method(),而且method是从原型中获取的,this仍然会引用obj,因此,方法始终是与当前对象一起使用,即使方法是继承的
- for .. in 循环在其自身和继承的属性上进行迭代,所有其他的键/值获取方法仅对对象本身起作用
F.prototype
代码示例
- F.prototype属性(不要把它与[[Prototype]]弄混了)在new F被调用时为新对象的[[Prototype]]赋值
- F.prototype的值要么是一个对象,要么就是null,其他值都不起作用
- "prototype"属性仅在设置了一个构造函数,并通过new调用时,才具有这种特殊的影响
原生的类型
代码示例
原生方法,没有__proto__的对象
代码示例
Class基本语法
代码示例
基本的类语法
class MyClass {
prop = value; // 属性
constructor(…) { // 构造器
// …
}
method(…) {} // method
get something(…) {} // getter 方法
set something(…) {} // setter 方法
[Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
// …
}
技术上来说,MyClass 是一个函数(提供作为 constructor 的那个),而 methods、getters 和 settors 都被写入了 MyClass.prototype。
类继承
代码示例
静态属性和静态方法
代码示例
- 静态方法被用于实现整个类的功能,与具体的类示例无关
- 在类生命中,它们都被用关键字static进行了标记
- 静态属性被用于但想要存储类级别的数据时,而不是绑定到实例
- 从技术上讲,静态声明与直接给类本身赋值相同
- 静态属性和方法都是可以被继承的
私有的和受保护的属性和方法
代码示例
- OOP而言,内部接口和外部接口的划分被称为封装
- 封装的优点
- 保护用户,使他们不会误伤自己
- 可支持性
- 严格界定内部接口,class的开发人员可以自由地更改其内部属性和方法,甚至无需通知用户
- 隐藏复杂性
- 当实施细节被隐藏,并提供了简单且有据可查地外部接口时,总是方便的
- 为了隐藏内部接口,使用了受保护或私有的属性
- 受保护的字段以_开头,这是一个约定
- 私有字段以#开头,确保只能从类的内部访问它们
- 目前,在各浏览器中不支持私有字段,但可以用 polyfill 解决,即自行解决
扩展内建类
代码示例
- 内建的类,例如Array、Map等也都是可以扩展的
- 如果想要map或filter这样的内建方法返回常规数组,可以在Symbol.species中返回Array
- 内建对象有它们自己的静态方法,例如Object.keys、Array.isArray等,原生的类互相扩展,通常,当一个类扩展另一个类时,静态方法和非静态方法都会被继承,但内建类是一个例外,它们相互间不继承静态方法
类检查:"instanceof"
代码示例
- typeof用于原始数据类型,返回值为string
- {}.toString用于原始数据类型和内建对象,包括Symbol.toStringTag属性的对象,返回string
- instanceof用于对象,返回true/false
- 从技术上讲,{}.toString 是一种"更高级的"typeof
- 当使用类的层次结构,并想要对该类进行检查,同时考虑继承时,这种场景下instanceof操作确实很出色
Mixin模式
代码示例
- Mixin是一个通用的面向对象编程术语:一个包含其他类的方法的类
- JS不支持多重继承,但是可以通过将方法拷贝到原型来实现mixin
- 可以使用mixin作为一种通过添加多种行为来扩充类的方法
- 如果Mixins意外覆盖了现有类的方法,那么它们可能会成为一个冲突点。因此,通常应该仔细考虑mixin的命名方法,以最大程度地降低发生这种冲突的可能性
错误处理,"try…catch"
代码示例
try…catch结构允许处理执行过程中出现的error,从字面上看,它允许"尝试"允许代码并"捕获"其中可能发生的错误
try {
// 执行此处代码
} catch(err) {
// 如果发生错误,跳转至此处
// err 是一个 error 对象
} finally {
// 无论怎样都会在 try/catch 之后执行
}
可能没有catch部分或者没有finally,所以try…catch或者try…finally都是可用的
Error对象包括下列属性:
- message -- 人类可读的error信息
- name -- 具有error名称的字符串(Error构造器的名称)
- stack(没有标准化,但是得到了很好的支持),即Error发生时的调用栈
可以使用throw操作符来生成自定义的error,从技术上讲,throw的参数可以是任何东西,但通常是继承自内建的Error类的error对象
再次抛出是一中错误处理的重要模式:catch块通常期望并知道如何处理特定的error类型,因此它应该再次抛出它不知道的error
自定义Error,扩展Error
代码示例
- 可以正常地从Error和其他内建的error类进行继承,但需要注意部分属性以及不忘了调用super
- 可以用instanceof来检查特定的error,但是对于一些第三方库的error,由于没有简单的方法获取它的类,可以将name属性用于这一类的检查
- 包装异常是一项广泛应用的技术:用于出入低级别异常并创建高级别error而不是各种低级别error的函数
回调介绍
代码示例
- 一些函数支持异步行为,即现在开始执行的行为,但是它们在稍后才会完成
- 由于脚本是"异步"调用,所以不会等到脚本加载完成才执行
- 异步执行某项功能的函数应该提供一个callback参数用于在相应事件完成时调用
- 避免"回调地狱"式的编码方式
Promise
代码示例
- "生产者代码":会做一些事儿,并且会需要一些时间,例如,通过网络加载数据的代码
- "消费者代码":想要在"生产者代码"完成哦你工作的第一时间就能获得其工作成果,许多函数可能都需要这个结果
- Promise是将"生产者代码"和"消费者代码"连接在一起的特殊的JS的对象
- 由new Promise构造器返回的promise对象具有以下内部属性:
- state -- 最初是"pending",然后再resolve被调用时变为"fulfilled",或者在reject被调用时变成"rejected"
- result -- 最初是undefined,然后在resolve(value)被调用时变为value,或者在reject(error)被调用时变成error
- Promises和Callbacks对比:
- Promises允许按照自然顺序进行编码,允许loadScript和.then来处理结果;
- 而Callbacks在调用loadScript(script, callback)时,在处理的地方必须有一个callback函数,即在调用loadScript之前,必须知道如何处理结果
- Promises可以根据需要在promise上多次调用.then,每次调用,都会在"订阅列表"中添加一个新的"分析",一个新的订阅函数,即Promise链
- 而Callbacks只能有一个回调
Promise链
代码示例
- 如果.then(或catch/finally都可以)处理程序(handler)返回一个promise,那么链的其余部分将会等待,直到它状态变为settled。当它被settled后,其result(或error)将被进一步传递下去
使用promise进行错误处理
代码示例
- .catch处理promise中的各种error:在reject()调用中的,或者在处理程序(handler)中抛出的(throw)error
- 应该将.catch准确地放在想要处理的error上,并知道如何处理这些error的地方。处理程序应该分析error(也可以自定义error类来帮助分析)并再次抛出未知的error
- 如果没有办法从error中恢复的话,不使用.catch也可以
- 在任何情况下都应该有unhandled rejection事件处理程序(用于浏览器,以及其他环境的模拟),以跟踪未处理的error并告知用户/服务器有关信息,以使应用程序永远不会死掉
Promise API
代码示例
Promise类有5种静态方法
- Promise.all(promises) —— 等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成 Promise.all 的 error,所有其他 promise 的结果都会被忽略
- Promise.allSettled(promises)(ES2020 新增方法)—— 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果:
- state: "fulfilled" 或 "rejected"
- value(如果 fulfilled)或 reason(如果 rejected)
- Promise.race(promises) —— 等待第一个 settle 的 promise,并将其 result/error 作为结果
- Promise.resolve(value) —— 使用给定 value 创建一个 resolved 的 promise
- Promise.reject(error) —— 使用给定 error 创建一个 rejected 的 promise
Promisify
代码示例
- "Promisification"是用于一个简单转换的一个长单词,它指将一个接受回调的函数转换为一个返回promise的函数
- 由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要这种转换,因为使用promise更加方便
- 在实际开发中,可能需要promisify很多函数,使用一个helper很有意义,将其称为promisify(f): 它接受一个需要被promisify的函数f,并返回一个包装(wrapper)
- 注意:Promisification是一种很好的方法,特别是在使用async/await的时候,但是不是回调的完全替代,一个promise可能只有一个结果,但从技术上将,一个回调将函数可能被调用多次,因此Promisification仅适用于调用一次回调的函数,进一步调用将被忽略
微任务(Microtask)
代码示例
Promise处理始终时异步的,因为所有promise行为都会通过内部的"promise jobs"队列,即微任务队列
- 队列(queue)是先进先出的:首先进入队列的任务会首先运行
- 只有在JS引擎中没有其他任务在运行时,才开始执行任务队列中的任务
.then/catch/finally 处理程序(handler)总是在当前代码完成后才会被调用
如果需要确保一段代码在 .then/catch/finally 之后被执行,可以将它添加到链式调用的 .then 中
在大多数 JavaScript 引擎中(包括浏览器和 Node.js),微任务(microtask)的概念与“事件循环(event loop)”和“宏任务(macrotasks)”紧密相关
Async/await
代码示例
函数前面的关键字async有两个作用
- 让这个函数总是返回一个promise
- 允许在该函数内使用await
Promise前的关键字await使JS引擎等待该promise settle,然后:
- 如果有error,就会抛出异常 -- 就像那里调用了throw error 一样
- 否则,就返回结果
关键字async和await在一起能很好的编写异步代码的框架,且代码易于阅读和编写
有了async/await之后,几乎就不需要使用promise.then/catch,但是不要忘了它们本质上是promise的,有时候(例如在函数最外层作用域)不得不使用它们
当需要同时等待多个任务时,别忘了Promise.all
Generator
代码示例
- Generator是通过generator函数function* f(…) {…}创建的
- 在generator(仅在)内部,存在yield操作
- 外部代码和generator可能会通过next/yield调用交换结果
- 在现代JS中,generator很少被使用,但有时却非常有用,因为函数在执行过程中与调用代码交换数据的能力是非常独特的,而且它们非常适合创建可迭代对象
Async iterator 和 generator
代码示例
Iterator 和 Async Iterator 的 区别
提供iterator的对象方法
- Iterator -- Symbol.iterator
- Async Iterator -- Symbol.asyncIterator
next() 返回的值
- Iterator -- 任意值
- Async Iterator -- Promise
进行循环使用时
- Iterator -- for .. of
- Async Iterator -- for await .. of
Iterable 和 Async Iterable 的 区别
提供iterator的对象方法
- Iterable -- Symbol.iterator
- Async Iterable -- Symbol.asyncIterator
next() 返回的值
- Iterable -- {value: …, done: true/false}
- Async Iterable -- resolve成{value: …, done: true/false}的Promise
Generator 和 Async Generator 的 区别
声明方式
- Generator -- function*
- Async Generator -- async function*
next() 返回的值
Generator -- {value: …, done: true/false}
Async Generator -- resolve成{value: …, done: true/false}的Promise
在现代JS中,generator很少被使用,但有时却非常有用,因为函数在执行过程中与调用代码交换数据的能力是非常独特的,而且它们非常适合创建可迭代对象
模板简介
代码示例