Visual LabINTERACTIVE LEARNING
JavaScript 内核精校教程

JavaScript 核心机制

系统整理 this、原型、类型化数组、迭代器与异步机制,作为深入运行时之前的知识底座。

this原型异步
进阶预计 65 分钟查看源文 ↗

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 绑定

this - JavaScript | MDN

super调用会比较特殊

当一个函数以 super.method() 的形式被调用时,method 函数内的 thissuper.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 }))

规则:

  1. 箭头函数里没有绑定自己的this,如果在箭头函数使用了this,调用的时候会到上层作用域中查找this,就像普通变量查找一样的规则
  2. 没有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 进行预填充。
注意

提示

让我们先澄清一个可能的误区。ArrayBufferArray 没有任何共同之处:

  • 它的长度是固定的,我们无法增加或减少它的长度。
  • 它正好占用了内存中的那么多空间。
  • 要访问单个字节,需要另一个“视图”对象,而不是 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),mapslicefindreduce 等。

但有几件事我们做不了:

  • 没有 splice —— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、连续的内存区域。我们所能做的就是分配一个零值。
  • concat 方法。

还有两种其他方法:

  • arr.set(fromArr, [offset])offset(默认为 0)开始,将 fromArr 中的所有元素复制到 arr
  • arr.subarray([begin, end]) 创建一个从 beginend(不包括)相同类型的新视图。这类似于 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,包含 ArrayBufferTypedArrayDataViewBlob、字符串或者任意这些元素的混合,这些元素将会被放入 Blob 中。

  • options 可选一个可以指定以下任意属性的对象:

    • type 可选将会被存储到 blob 中的数据的 MIME 类型。默认值是空字符("")。
    • endings 可选 非标准如果数据是文本,那么如何解释其中的换行符(\n)。默认值 transparent 会将换行符复制到 blob 中而不会改变它们。要将换行符转换为主机系统的本地约定,请指定值 native
  • 返回值

    • 一个包含指定数据的新的 Blob 对象。
实例属性、方法
实例属性
  • Blob.size 只读Blob 对象中所包含数据的大小(字节)。
  • Blob.type 只读一个字符串,表明该 Blob 对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。
实例方法
  • 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()FileReaderBlob.text()Response(blob).text() 方法的区别:

  1. Blob.arrayBuffer():

    • 这个方法返回一个 Promise,它解析为一个 ArrayBuffer 对象。
    • 它适用于任何类型的 Blob 数据,不仅限于文本。
    • 它不关心 Blob 的类型,只将数据作为原始二进制数据返回。
  2. FileReader:

    • FileReader 是一个更通用的接口,可以用来读取文件或 Blob 数据。
    • 它支持多种读取模式,例如 readAsText(), readAsDataURL(), 和 readAsArrayBuffer()
    • 使用 FileReader 时,你可以指定编码(仅当读取文本时)。
    • FileReader 通常用于读取文件输入字段中的文件。
  3. Blob.text():

    • 这个方法是 Blob 对象的一个便利方法,返回一个 Promise,解析为一个字符串。
    • 它专门用于读取文本数据,自动将 Blob 中的数据解码为 UTF-8 字符串。
    • 它适用于文本类型的 Blob,例如 JSON、XML 或纯文本。
  4. Response(blob).text():

    • 这个方法是 Response 对象的一个方法,用于从 Response 对象中提取文本数据。
    • 当你从 fetch API 接收到一个包含 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'
 }

作用域

  1. ES6之前,js只有全局作用域和函数作用域,也就是if/for等代码块里没有作用域,也就是说外层if/for里面使用var定义的变量会被挂在window上
if(true){
  var test = 'test'
  function foo(){
    console.log('foo')
  }
}
// 这里的test会被挂在到window上去
// foo在if代码执行后才能访问,在之前不行,不是作用域提升
  1. 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中,迭代器是一个具体的对象,这个对象需要符合迭代器协议:

  1. 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式
  2. js中,这个标准就是实现了一个特定的next方法 next方法的要求
  3. 有一个参数或无参数
  4. 必须返回一个包含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())
可迭代对象

❤️ 提示

可迭代对象

  1. 必须实现了[Symbol.iterator]函数
  2. [Symbol.iterator]函数必须返回一个迭代当前对象的迭代器 可迭代对象的特性:
  3. js语法: for...of操作、展开运算符操作、yield、解构赋值
  4. 创建对象时:new Set(iterable)、new Map()、new weakMap() 、new weakSet()
  5. 常见的方法如: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

📍 提示

  1. 执行生成器函数返回一个生成器,但不会执行函数内部任何代码
  2. 第一次调用生成器的next方法,会执行函数体第一行到第一个yield之间的代码,并可以通过yield返回值,后面每次调用next方法,都会执行上一个yield后面都下一个yield或者return之间的语句
  3. next可以传递参数,在函数中通过yield前面定义的变量接收
  4. 函数中return或者生成器调用return方法都可以终止生成器函数的继续执行
  5. 生成器的return方法也可以传递参数,也可以使用throw方法传递一个错误来提前终结
使用生成器

🎨 提示

场景:

  1. 自定义的迭代器太麻烦了,需要自己实现,生成器函数可以简化迭代器的生成 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 {
//最终都会执行
}

进程/线程

  1. 进程 计算机已运行的程序,是操作系统管理程序的一种方式
  2. 线程 操作系统能够运行运算调度的最小单位,通常包含在进程中

浏览器事件循环

js是单线程,浏览器是多进程管理的,每个tab都是一个单独的进程,每个进程里有多个线程,其中一个线程就是j专门执行avascript的线程,叫javascript线程

js是基于事件驱动的单线程-异步-非阻塞

基于宏微任务和事件循环模式实现的

一些特定的操作并非由javascript线程执行的,而是由浏览器执行的,如setTimeOut是由javascript执行,但是具体的计时是在其他线程来实现的

飞书画板

执行一个宏任务后,会先去清空微任务队列,才会切换到去执行下一个宏任务

模块化

🏢 提示

模块化

  • 模块化开发目的是把程序划分成一个小小的结构
  • 这个结构由自己的逻辑代码、有自己的作用域,不会相互影响
  • 这个结构可以按需暴露自己的变量、函数、对象给其他的结构使用
  • 这个结构可以通过某种方式,导入其他结构的变量、函数、对象等 总结 模块就是,有独立作用域,可以导出和导入变量的单独结构 注意: 模块外并未禁止直接修改模块变量

Commonjs

commonjs是一个规范,在node.js和浏览器里均有实现,简称cjs。node中默认使用,在浏览器里需要由Browserify实现,webpack具备对commonjs的实现

🏢 提示

规范内容

  1. 使用exports关键词实现导出,module.exports
  2. 使用require函数实现导入
  1. 模块中的exports是一个空对象
  2. module是一个对象,它里面有一个属性exports内存指向了模块中的exports对象
  3. require函数导入的就是上面的module.exports对象,也就是说它们是同一个内存引用
  4. 所以如果修改了module.exports的指向,直接添加到模块中exports对象上的属性不会被导出
  5. 如果在导入的模块中,也可以直接修改模块中的属性,虽然可以,但是不要这么做

飞书画板

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