JavaScript 核心机制
系统整理 this、原型、类型化数组、迭代器与异步机制,作为深入运行时之前的知识底座。
this绑定规则(四种方式)
默认绑定,直接调用
❗ 提示
- 严格模式下,this默认绑定undefined
- 非严格模式下,this默认绑定globalThis 浏览器是 window
- 类中 默认开启严格模式 默认绑定和上层作用域的this不是一回事,直接调用函数时,没有显式绑定this的情况下,使用的this
隐式绑定
❗ 提示
被一个对象执行,调用,注意是被当作对象的一个方法来调用
const obj = {
name:'obj',
foo(){
console.log(this.name)
}
}
obj.foo() // this指向obj
function foo(){
console.log(this.length)
}
const arr = [foo,1,2]
arr[0]() // 这也会把this绑定到arr 即结果是 3
显示绑定
❗ 提示
bind/apply/call方式调用
- bind 函数只绑定,不执行,参数在绑定的时候可以传入,在调用的时候不能覆盖,会在后面添加新的参数
- call调用即执行,参数是单独传入
- apply调用即执行,参数通过数组传入
//三者调用
const obj ={ name:'this'}
var func = function(arg1, arg2) {};
// 直接调用了
func.call(obj, arg1, arg2);
func.apply(obj, [arg1, arg2]);
//返回一个函数
nfn = func.bind(obj,arg1)
nfn(arg2)
New 绑定
super调用会比较特殊
当一个函数以 super.method() 的形式被调用时,method 函数内的 this 与 super.method() 调用周围的 this 值相同,通常不等于 super 所指向的对象。这是因为 super.method 不是像上面的对象成员访问——它是一种特殊的语法,有不同的绑定规则。有关示例,请参见 super 参考。
🐵 提示
通过new 内部创建一个新的对象,绑定到this
绑定优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
bind优先级高于apply/call
function Foo(){
console.log('this', this)
}
const objA ={
name:'objA',
}
const objB ={
name:'objB',
}
const fn = Foo.bind(objA)
fn.call(objB) // this { name: 'objA' }
new fn() // this Foo {}
const objC ={
name:'objC',
foo:fn
}
new objC.foo() // this Foo {}
特殊情况
非严格模式下,apply/call/bind绑定null/undefied时,这个绑定会被忽略,使用默认绑定(并不是降级,不会使用隐式绑定)
非严格模式下,基本类型会被转换成包装类型(对象),null和undefined会使用默认绑定
严格模式下,会直接使用绑定的类型,默认绑定是undefined
箭头函数
// 函数表达式
const fn1 = function(){}
// 用来改写 简化函数表达式
const fn2 = ()=> {}
const newArr = [1,2,3]
//1. 只有一个参数,可以把小括号省略
newArr.forEach(item=>{
console.log('item', item)
})
//2. 函数体只有一行代码,大括号可以省略(非return 语句)
newArr.forEach(item=>console.log('item', item))
//3. 如果函数体只有一行代码且是返回值,可以把return 也省略,默认返回
newArr.filter(item=>item>3)
//4. 如果默认返回是对象,那么需要给对象加上小括号
newArr.map(item=>({ num:item }))
规则:
- 箭头函数里没有绑定自己的this,如果在箭头函数使用了this,调用的时候会到上层作用域中查找this,就像普通变量查找一样的规则
- 没有arguments,可使用剩余参数来实现不定参数传递
const obj = {
name:'obj',
foo: function() {
const bar = ()=>{
console.log('this', this)
}
return bar
}
}
// 这里bar的this是上层作用域foo的this,如果改变foo的this指向,bar中的this也会跟着改变
const obj = {
name:'obj',
foo: ()=> {
const bar = ()=>{
console.log('this', this)
}
return bar
}
}
//如果把foo也改成了箭头函数,那么foo和bar的this指向都要继续往外查找,是全局作用域的this
// 注意!! 对象不是代码块,没有产生作用域!!
函数的调用方式:这里也是直接调用!!! 因为obj.foo()函数执行后,返回值是一个新的函数的地址!!!
obj.foo()()
也是通过小括号直接调用了
对象不是代码块,没有产生作用域!!
类型数组详解
参考资料 ArrayBuffer,二进制数组
ArrayBuffer
❗ 提示
是基本的二进制对象, 对固定长度的连续内存空间的引用
创建
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
alert(buffer.byteLength); // 16
// 它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。
注意
❗ 提示
让我们先澄清一个可能的误区。
ArrayBuffer与Array没有任何共同之处:
- 它的长度是固定的,我们无法增加或减少它的长度。
- 它正好占用了内存中的那么多空间。
- 要访问单个字节,需要另一个“视图”对象,而不是
buffer[index]。
ArrayBuffer 是一个内存区域,它里面存储了一个原始的字节序列。
视图对象
视图对象的概念
❗ 提示
**“视图”对象 是操作
ArrayBuffer的工具,**本身并不存储任何东西,而是通过它的规则解释存储在ArrayBuffer中的字节,通用术语是 TypedArray
Uint8Array
将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。
Uint16Array
将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
Uint32Array
将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
Float64Array
将每 8 个字节视为一个 5.0x10(-324) 到 1.8x10(308) 之间的浮点数。

因此,一个 16 字节 ArrayBuffer 中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)
TypedArray属性和方法
创建方法
// 以上的几种类型数组,都有以下5中函数重载的实现,Uint32Array
new TypedArray(buffer, [byteOffset], [length]); // 除了提供buffer外,其他情况都会创建新的buffer
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
属性
arr.buffer—— 引用ArrayBuffer。arr.byteLength——ArrayBuffer的长度
越界行为
对于越界值,会采用仅保留最右位,多余的位被切除的操作

Uint8``Clampe``dArray 在这方面比较特殊,它的表现不太一样。对于大于 255 的任何数字,它将保存为 255,对于任何负数,它将保存为 0。此行为对于图像处理很有用。
方法
TypedArray 具有常规的 Array 方法,但有个明显的例外。
我们可以遍历(iterate),map,slice,find 和 reduce 等。
但有几件事我们做不了:
- 没有
splice—— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、连续的内存区域。我们所能做的就是分配一个零值。 - 无
concat方法。
还有两种其他方法:
arr.set(fromArr, [offset])从offset(默认为 0)开始,将fromArr中的所有元素复制到arr。arr.subarray([begin, end])创建一个从begin到end(不包括)相同类型的新视图。这类似于slice方法(同样也支持),但不复制任何内容 —— 只是创建一个新视图,以对给定片段的数据进行操作。
DataView`` 更灵活的TypedArray
DataView 是在 ArrayBuffer 上的一种特殊的超灵活“未类型化”视图。它允许以任何格式访问任何偏移量(offset)的数据。
- 对于类型化的数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是
arr[i]。 - 通过
DataView,我们可以使用.getUint8(i)或.getUint16(i)之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。
语法:
new DataView(buffer, [byteOffset], [byteLength])
buffer—— 底层的ArrayBuffer。与类型化数组不同,DataView不会自行创建缓冲区(buffer)。我们需要事先准备好。byteOffset—— 视图的起始字节位置(默认为 0)。byteLength—— 视图的字节长度(默认至buffer的末尾)。
/ 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量为 0 处获取 8 位数字
alert( dataView.getUint8(0) ); // 255
// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
alert( dataView.getUint16(0) ); // 65535(最大的 16 位无符号整数)
// 在偏移量为 0 处获取 32 位数字
alert( dataView.getUint32(0) ); // 4294967295(最大的 32 位无符号整数)
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
可以自由操作二进制数字了
Blob 和File
Blob
💡 提示
Blob对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成ReadableStream来用于数据操作
构造函数
blobParts可选一个可迭代对象,比如Array,包含ArrayBuffer、TypedArray、DataView、Blob、字符串或者任意这些元素的混合,这些元素将会被放入Blob中。options可选一个可以指定以下任意属性的对象:-
- 一个包含指定数据的新的
Blob对象。
- 一个包含指定数据的新的
实例属性、方法
实例属性
实例方法
Blob.arrayBuffer()返回一个 promise,其会兑现一个包含Blob所有内容的二进制格式的ArrayBuffer。Blob.slice()返回一个新的Blob对象,其中包含调用它的 blob 的指定字节范围内的数据。Blob.stream()返回一个能读取Blob内容的ReadableStream。Blob.text()返回一个 promise,其会兑现一个包含Blob所有内容的 UTF-8 格式的字符串。
const obj = { hello: "world" };
const blob = new Blob([JSON.stringify(obj, null, 2)], {
type: "application/json",
});
读取方式
从 Blob 对象中提取数据有几种不同的方法,每种方法都适用于不同的场景和数据类型。下面是 arrayBuffer()、FileReader、Blob.text() 和 Response(blob).text() 方法的区别:
Blob.arrayBuffer():- 这个方法返回一个
Promise,它解析为一个ArrayBuffer对象。 - 它适用于任何类型的 Blob 数据,不仅限于文本。
- 它不关心 Blob 的类型,只将数据作为原始二进制数据返回。
- 这个方法返回一个
FileReader:FileReader是一个更通用的接口,可以用来读取文件或 Blob 数据。- 它支持多种读取模式,例如
readAsText(),readAsDataURL(), 和readAsArrayBuffer()。 - 使用
FileReader时,你可以指定编码(仅当读取文本时)。 FileReader通常用于读取文件输入字段中的文件。
Blob.text():- 这个方法是
Blob对象的一个便利方法,返回一个Promise,解析为一个字符串。 - 它专门用于读取文本数据,自动将 Blob 中的数据解码为 UTF-8 字符串。
- 它适用于文本类型的 Blob,例如 JSON、XML 或纯文本。
- 这个方法是
Response(blob).text():- 这个方法是
Response对象的一个方法,用于从Response对象中提取文本数据。 - 当你从
fetchAPI 接收到一个包含 Blob 类型的响应时,可以使用Response.text()来获取响应体的文本表示。 - 它与
Blob.text()类似,但Response.text()可以处理来自网络请求的响应体。
- 这个方法是
总结来说,选择哪种方法取决于你需要处理的数据类型和上下文:
- 如果你需要处理二进制数据,使用
Blob.arrayBuffer()。 - 如果你需要读取文本数据,并且 Blob 类型已知,使用
Blob.text()或Response.text()。 - 如果你需要更多的灵活性,例如不同的编码或不同的读取模式,使用
FileReader。
File
继承自Blob,是它的一个扩展
作用域相关
作用域提升
🌅 提示
变量声明前能被访问,叫作用域提升
- Var 有
- Let/const 没有,且变量不能重复被声明,即前面已被var/const/let/函数参数 声明过的变量都不能被再次声明
var message = "message";
function foo(m) {
console.log('m', m)
const m = 'm' //报错
// let m = 'm' //报错
console.log('m', m)
}
foo(message)
暂时性死区(TDZ)
💡 提示
const / let 没有作用域提升的实现原理,就是暂时性死区
一个作用域内,在声明message之前的区域,成为暂时性死区,这时访问message会报错,这个例子虽然上层作用域有message,但是当前的作用域内已经声明了message,所以不会访问外部的message
const message = '123'
function foo(){
console.log(message) //error
const message='456'
}
作用域
- ES6之前,js只有全局作用域和函数作用域,也就是if/for等代码块里没有作用域,也就是说外层if/for里面使用var定义的变量会被挂在window上
if(true){
var test = 'test'
function foo(){
console.log('foo')
}
}
// 这里的test会被挂在到window上去
// foo在if代码执行后才能访问,在之前不行,不是作用域提升
- ES6之后,新增了块级作用域,if/for 存在作用域
🏕️ 提示
注意: 顶层作用域下的 单纯的代码块 ,内部var定义的变量/函数 仍然能被外层访问,这是对低版本js的兼容 但是也只能在 代码块之后被访问到,而不是作用域提升,var定义的变量仍会挂到window上 const/let 定义的变量仍然不能被外层访问到
异步编程
Promise
Promise的使用
构造函数的参数 excutor
⚽ 提示
本身也是一个函数,会自动传入2个参数,函数体会在new promise时被立即执行
const excutor = (resolve, reject) => {}
const promise = new Promise(excutor)
resolve的值的类型
- 普通值,直接返回
- 新的Promise,以新的promise的值为值
- thenable的对象,即实现了then方法的对象,then方法会被自动调用,且传入resolve,reject参数,resolve的值就是then的结果,reject就是catch结果
const value1 = 'aaa'
const valueObj = {name:'aaa'}
const promiseRes = (resolve,reject)=>{
setTimeout(() => {
resolve('promiseRes')
}, 2);
}
const promiseErr = (resolve,reject)=>{
setTimeout(() => {
reject('promiseErr')
}, 4);
}
const thenable = {
name: 'thenable',
then: (resolve,reject) =>{
resolve('thenable')
}
}
function foo(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value1) // res :>> aaa
// resolve(valueObj) // res :>> { name: 'aaa' }
// resolve(new Promise(promiseRes)) // res :>> promiseRes
// resolve(new Promise(promiseErr)) // err :>> promiseErr
// resolve(thenable) //res :>> thenable
}, 4);
})
}
foo().then(res=>{
console.log('res :>> ', res);
}).catch(err=>{
console.log('err :>> ', err);
})
then方法的细节
- 可以传入两个参数(回调函数),分别是成果和失败的回调
- 一个promise可以使用多个then,每个then都会被回调
- then后面可以链式
- then/catch里面再返回一个值,会产生一个新的promise,并把返回值作为resolve的值,链式操作时会作为下一个then的结果,throw new Error() 会被下一个最近的catch捕获
function foo(m){
return new Promise((resolve, reject) => {
setTimeout(() => {
if(m>3){
resolve('succ')
}else {
reject('err')
}
}, 4);
})
}
const promise = foo(4)
// 1. then 可以传入2个参数
promise.then(res=>{
console.log('res1 :>> ', res);
},err=>{
console.log('err :>> ', err);
})
//2. 同一个promise,then可以监听多次,都会被回调执行
promise.then(res=>{
console.log('res1 :>> ', res);
})
promise.then(res=>{
console.log('res2 :>> ', res);
})
promise.then(res=>{
console.log('res3 :>> ', res);
})
// 3. then后面可以链式操作
promise.then(res=>{
console.log('res1 :>> ', res);
}).catch(err=>{
console.log('err :>> ', err);
})
// 4. then/catch里面再返回一个值,会产生一个新的promise,并把返回值作为resolve的值,
// 链式操作时会作为下一个then的结果,
// throw new Error() 会被下一个最近的catch捕获
promise.then(res=>{
console.log('res1 :>> ', res);
throw new Error('bad')
}).catch(err=>{
console.log('error1 :>> ', String(err));
throw new Error('bad2')
}).catch(err=>{
console.log('error2 :>> ', String(err));
return 'from error'
}).then(res=>{
console.log('res2 :>> ', res);
})
catch方法的细节
- 一个promise可以单独多次使用catch,都会被独立回调
- then后面没有捕获错误,而是如果出现错误则会报错,即使已经单独catch了
function foo(m){
return new Promise((resolve, reject) => {
setTimeout(() => {
if(m>3){
resolve('succ')
}else {
reject('err')
}
}, 4);
})
}
const promise = foo(3)
//2. 同一个promise,catch可以监听多次,都会被回调执行
promise.catch(err=>{
console.log('err1 :>> ', err);
})
promise.catch(err=>{
console.log('err2 :>> ', err);
})
promise.catch(err=>{
console.log('err3 :>> ', err);
})
// 2. then后面如果没有catch,即使promise已经在上面直接catch了,这里的then仍然会报错
promise.then(res=>{
console.log('res1 :>> ', res);
})
finally回调
ES9(ES2018)新增的特性
无用论fullfilled还是rejected,都会回调这个函数
promise.then(res=>{}).catch(()=>{}).finally(()=>{
//最终一定会执行的方法
})
Promise执行时机
function foo (m){
return new Promise((resolve, reject) => {
console.log('immedi',1)
setTimeout(() => {
console.log('setTimeout0',2)
}, 0);
setTimeout(() => {
if(m>1){
console.log('resolve', 3)
resolve(1)
console.log('after resolve', 4)
}else {
console.log('reject', 5)
reject('错误')
console.log('after reject', 6)
}
console.log('setTimeout3',7)
}, 3);
})
}
foo(4).then(res=>{
console.log('then', 8)
}).catch(err=>{
console.log('catch', 9)
})
console.log('outside',10)
// 执行结果
// immedi 1
// outside 10
// setTimeout0 2
// resolve 3
// after resolve 4
// setTimeout3 7
// then 8
Promise类方法
Promise.resolve /reject
允许直接使用,不需要new一个实例,返回一个promise
Promise.all
全部的promise执行完成
- 返回一个新的promise,状态由所有的promise共同决定,当所有promisefullfilled时,最终状态为fullfilled,在then中回调所有的执行结果的数组
- 有一个promise为reject,则最终状态为reject,并返回第一个reject结果,可以catch到第一个error
Promise.allSettled
ES11 (ES2012) 新增
全部promise都执行,这个promise的状态一定是fullfilled,结果在then中返回一个数组,包含了state
const p1 = new Promise((resolve,reject)=>{
reject('p1')
})
const p2 = new Promise((resolve,reject)=>{
reject('p2')
})
const p3 = new Promise((resolve,reject)=>{
resolve('p3')
})
Promise.allSettled([p1,p2,p3]).then(res=>{
console.log('res', res)
// res [
//{ status: 'rejected', reason: 'p1' },
//{ status: 'rejected', reason: 'p2' },
//{ status: 'fulfilled', value: 'p3' }
//]
})
Promise.race
返回的promise状态由最快的一个promise决定
const p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('p1')
}, 6);
})
const p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('p2')
}, 1);
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('p3')
}, 2);
})
Promise.race([p1,p2,p3]).then(res=>{
console.log('res', res)
}).catch(err=>{
console.log('err', err) // err p2
})
Promise.any
返回最快的一个fullfilled,如果没有fullfilled,则最终会是reject状态
const p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('p1')
}, 6);
})
const p2 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('p2')
}, 1);
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('p3')
}, 2);
})
Promise.any([p1,p2,p3]).then(res=>{
console.log('res', res) // res p3
}).catch(err=>{
console.log('err', err)
})
async/await
🏕️ 提示
实际上是生成器函数的语法糖,并且做了一些优化 生成器函数返回一个迭代器,调用next方法返回每个yield后面的promise,再promise的then中再次调用next并把结果当成参数传递进去
async用于定义一个异步函数
await只能在异步函数中使用 (ES8),以及在模块顶层使用(ES13)
async function foo(){
await promise
}
- 会等await后面的promise有fullfilled状态,才会继续往下执行
生成器迭代器
迭代器
迭代器
🎁 提示
使用户可以在容器对象上遍访的对象,使用该接口无需关心对象内部的实现细节 在js中,迭代器是一个具体的对象,这个对象需要符合迭代器协议:
- 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式
- js中,这个标准就是实现了一个特定的next方法 next方法的要求:
- 有一个参数或无参数
- 必须返回一个包含value和done的对象。done是一个boolean,表示是否迭代完成,value表示当前具体值
const arr = [1,2,3,4]
let index = 0
const arrIterator = {
next:function(){
if(index<arr.length){
return {
done:false,
value: arr[index++]
}
}else {
return {
done:true,
}
}
}
}
console.log('arrIterator,next()', arrIterator.next())
console.log('arrIterator,next()', arrIterator.next())
console.log('arrIterator,next()', arrIterator.next())
console.log('arrIterator,next()', arrIterator.next())
console.log('arrIterator,next()', arrIterator.next())
console.log('arrIterator,next()', arrIterator.next())
可迭代对象
❤️ 提示
可迭代对象
- 必须实现了[Symbol.iterator]函数
- [Symbol.iterator]函数必须返回一个迭代当前对象的迭代器 可迭代对象的特性:
- js语法:
for...of操作、展开运算符操作、yield、解构赋值- 创建对象时:new Set(iterable)、new Map()、new weakMap() 、new weakSet()
- 常见的方法如:Promise.all(iterable)、Promise.race、promise.any,Array.from(iterable)
在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性
所以不是在构建字面量对象的时候,对一个对象使用展开运算符,会报错
const info = { name:'info',age:18}
function foo(...arg){
console.log('arg', arg)
}
foo(...info) //报错
迭代器中断
在迭代过程中被break、return、throw中断,在迭代器中监听中断事件,在迭代器中实现return方法
const arr = [1,2,3,4]
let index = 0
const arrIterator = {
next:function(){
if(index<arr.length){
return {
done:false,
value: arr[index++]
}
}else {
return {
done:true,
}
}
}
return : ()=>{
console.log('被中断了')
return {done:true}
}
}
原生可迭代对象
Array、Set、Map、String、arguments对象、NodeList等
生成器
🏝️ 提示
生成器是一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等,ES6新增 在js中,生成器是指由生成器函数返回的对象,它同时也一个特殊的迭代器,实现了迭代器协议 (即next方法),也是一个可迭代对象,实现可迭代协议
生成器函数
生成器函数也是一个函数,但与普通函数有一些区别:
生成器函数需要在function后面加符号: *
生成器函数可以通过yield关键词来控制函数的执行流程(普通函数不可以)
生成器函数的返回值是一个Generator(生成器):
- 生成器事实上是一种特殊的迭代器
function* foo(init){
console.log('内部代码执行: 1')
const res = yield init
console.log('内部代码执行: 2')
const res2 = yield init+ res
console.log('内部代码执行: 3')
const res3 = yield init + res2
console.log('内部代码执行: 4')
const res4 = yield init + res3
console.log('内部代码执行: 5')
yield init + res4
console.log('内部代码执行: 6')
}
const generator = foo(1)
console.log('generator.next(1).value', generator.next().value)
console.log('generator.next(2).value', generator.next(2).value)
console.log('generator.next(3).value', generator.next(3).value)
console.log('generator.next(4).value', generator.next(4).value)
console.log('generator.next(5).value', generator.next(5).value)
console.log('generator.next(6).value', generator.next(6).value)
//内部代码执行: 1
//generator.next(1).value 1
//内部代码执行: 2
//generator.next(2).value 3
//内部代码执行: 3
//generator.next(3).value 4
//内部代码执行: 4
//generator.next(4).value 5
//内部代码执行: 5
//generator.next(5).value 6
//内部代码执行: 6
//generator.next(6).value undefined
📍 提示
- 执行生成器函数返回一个生成器,但不会执行函数内部任何代码
- 第一次调用生成器的next方法,会执行函数体第一行到第一个yield之间的代码,并可以通过yield返回值,后面每次调用next方法,都会执行上一个yield后面都下一个yield或者return之间的语句
- next可以传递参数,在函数中通过yield前面定义的变量接收
- 函数中return或者生成器调用return方法都可以终止生成器函数的继续执行
- 生成器的return方法也可以传递参数,也可以使用throw方法传递一个错误来提前终结
使用生成器
🎨 提示
场景:
- 自定义的迭代器太麻烦了,需要自己实现,生成器函数可以简化迭代器的生成 yield* 语法糖 yield* arr arr需要是一个可迭代对象,那么会自动迭代arr返回
const arr = [1,2,34,4]
function* foo(){
yield* arr
}
const generator = foo()
console.log('generator.next()', generator.next()) // {value: 1}
console.log('generator.next()', generator.next()) // {value: 2}
console.log('generator.next()', generator.next()) // {value: 34}
console.log('generator.next()', generator.next()) // {value: 4}
console.log('generator.next()', generator.next()) // {done: true}
总结:
🥛 提示
生成器函数相当于可以替代原来[Symbol.iterator]函数,快速实现可迭代协议
const b = {
friends:[1,2,3,4],
[Symbol.iterator](){
let index = 0
return {
next:()=>{
if(index<this.friends.length){
return {
value:this.friends[index++],
done:false
}
}
else {
return {
done:true
}
}
}
}
}
}
for (const iterator of b) {
console.log('iterator', iterator)
}
//改造后
const b = {
friends:[1,2,3,4],
*[Symbol.iterator](){
yield* this.friends
}
}
for (const iterator of b) {
console.log('iterator', iterator)
}
内置对象
Array
构造函数
调用 Array() 时可以使用或不使用 new。两者都会创建一个新的 Array 实例
稀疏数组
某些方法对稀疏数组的处理是不一样的!
- 不访问空槽的特殊处理
concat()、copyWithin()、every()、filter()、flat()、flatMap()、forEach()、indexOf()、lastIndexOf()、map()、reduce()、reduceRight()、reverse()、slice()、some()、sort() 和 splice()。等较旧的方法,根本不会访问空槽
// [ 2, 3, <2 empty items>, 6 ]
const keys = Object.keys(arr); // [ '0', '1', '4' ] 跳过了空槽
for (const key in arr) {
console.log(key);
}
// 输出:'0' '1' '4'
// 在对象中使用展开,使用属性枚举,而不是数组的迭代器
const objectSpread = { ...arr }; // { '0': 1, '1': 2, '4': 5 }
- 将空槽合并为
undefined元素,
方法有:entries()、fill()、find()、findIndex()、findLast()、findLastIndex()、includes()、join()、keys()、toLocaleString()、values() 和 with()。 以及for ... of、展开运算符
使用数组默认迭代方法的都会合并成undiefined,如for...of , 展开运算符,解构赋值,promise.all
const a = [];
a.length = 3;
// 获取数组的迭代器
const iterator = a[Symbol.iterator]();
// 手动调用迭代器
console.log(iterator.next()); // { value: undefined, done: false }
console.log(iterator.next()); // { value: undefined, done: false }
console.log(iterator.next()); // { value: undefined, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
异常处理
错误抛出
- 通过throw 抛出错误,throw下面的代码会中断执行,可以抛出多种数据类型
- Error类是一个封装好的错误类,会包含message、stack、name
function bar(){
// 基本类型 string/number/boolean/对象
//throw '123'
// throw 123
// throw true
// throw {message:'这是一个错误'}
throw new Error('这是一个默认的错误')
}
try {
bar()
} catch (error) {
// error是一个对象
console.log('error.message', error.message)
}
错误捕获
- 错误如果没有捕获,会沿着调用栈一层一层往上传递,最后到达浏览器层,每一层都会阻断后面的代码执行
function bar(){
throw new Error('这是一个默认的错误')
console.log('after bar') //不会执行
}
function foo() {
bar()
console.log('after foo')//不会执行
}
function outfoo() {
foo()
console.log('after outfoo')//不会执行
}
outfoo()
console.log('after outside')//不会执行
- 捕获方式
try {
//执行的代码
} catch (error) {
//如有错误
}finally {
//最终都会执行
}
进程/线程
- 进程 计算机已运行的程序,是操作系统管理程序的一种方式
- 线程 操作系统能够运行运算调度的最小单位,通常包含在进程中
浏览器事件循环
js是单线程,浏览器是多进程管理的,每个tab都是一个单独的进程,每个进程里有多个线程,其中一个线程就是j专门执行avascript的线程,叫javascript线程
js是基于事件驱动的单线程-异步-非阻塞
基于宏微任务和事件循环模式实现的
一些特定的操作并非由javascript线程执行的,而是由浏览器执行的,如setTimeOut是由javascript执行,但是具体的计时是在其他线程来实现的

执行一个宏任务后,会先去清空微任务队列,才会切换到去执行下一个宏任务
模块化
🏢 提示
模块化
- 模块化开发目的是把程序划分成一个小小的结构
- 这个结构由自己的逻辑代码、有自己的作用域,不会相互影响
- 这个结构可以按需暴露自己的变量、函数、对象给其他的结构使用
- 这个结构可以通过某种方式,导入其他结构的变量、函数、对象等 总结 模块就是,有独立作用域,可以导出和导入变量的单独结构 注意: 模块外并未禁止直接修改模块变量
Commonjs
commonjs是一个规范,在node.js和浏览器里均有实现,简称cjs。node中默认使用,在浏览器里需要由Browserify实现,webpack具备对commonjs的实现
🏢 提示
规范内容
- 使用exports关键词实现导出,module.exports
- 使用require函数实现导入
- 模块中的exports是一个空对象
- module是一个对象,它里面有一个属性exports内存指向了模块中的exports对象
- require函数导入的就是上面的module.exports对象,也就是说它们是同一个内存引用
- 所以如果修改了module.exports的指向,直接添加到模块中exports对象上的属性不会被导出
- 如果在导入的模块中,也可以直接修改模块中的属性,虽然可以,但是不要这么做

ESModule
📚 提示
js原生的模块化方案,ES6后引入,使用import 和export 关键词,实现模块隔离 注意: 模块外不能直接修改模块内变量,否则会抛出错误 它可以实现静态分析 这是与commonjs最大的区别
Export
❤️ 提示
导出声明不受暂时性死区规则的限制。你可以在声明名称
X之前声明当前模块导出X。
// export default value/func/name 值/函数名/变量名
// 1. export default 默认导出
export default 1
export default function(){}
function foo(){}
export default foo
// 2. 分别导出
// export const/let/const // 单独去在定义时导出
const abc = 0
export { abc } // 最后统一导出
export { abc as default } // 分别导出重命名
/**
注意
1. defualt是一个关键词,上面这样写与 export default 相同
2. export {} 是一个语句,后面的花括号并不是一个对象,不能使用对象增强语法 ,只能使用as重命名,
是用花括号括起来的用逗号分割的列表
*/
// 导出声明
export let name1, name2/*, … */; // also var
export const name1 = 1, name2 = 2/*, … */; // also var, let
export function functionName() { /* … */ }
export class ClassName { /* … */ }
export function* generatorFunctionName() { /* … */ }
export const { name1, name2: bar } = o;
export const [ name1, name2 ] = array;
// 导出列表
export { name1, /* …, */ nameN };
export { variable1 as name1, variable2 as name2, /* …, */ nameN };
export { variable1 as "string name" };
export { name1 as default /*, … */ };
// 默认导出
export default expression;
export default function functionName() { /* … */ }
export default class ClassName { /* … */ }
export default function* generatorFunctionName() { /* … */ }
export default function () { /* … */ }
export default class { /* … */ }
export default function* () { /* … */ }
Import
//1. 导入默认模块
import xxx from './xxx'
//2. 导入分别导出
import { abc,cbd } from './xxx'
//3. 一起结合
import * as aModule from './xxx' //方式一
import { default as aModule,abc ,cbd } from './xxx' // 方式二
import aModule,{ abc ,cbd } from './xxx' //方式三
// 官方例子
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name"; //副作用导入,只是为了执行一个模块内的代码
结合简化
export * from "x.js";
export { name } from "x.js";
export { defualt as name, nameb } from "x.js";
// 导出模块合集
export * from "module-name";
export * as name1 from "module-name";
export { name1, /* …, */ nameN } from "module-name";
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";
export { default, /* …, */ } from "module-name";
export { default as name1 } from "module-name";
动态导入
import("/modules/mymodule.js").then((module) => {// Do something with the module.});
🍞 提示
Import 按MDN的说法并不是一个函数,而是类函数的方式调用,它是一个关键词 MDN- import
