闭包和promise day20
闭包
概述:闭包就是在函数内部返回一个函数,内部函数有外部函数的引用。称为闭包。
函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制所收回.
函数的生命周期
function fn(){
var i = 0
i++
return i
}
函数在预编译阶段
-
开辟一个内存空间
-
将对应的代码块放到这个内存空间
函数的执行阶段
-
将对应的函数开辟的这个空间放在执行栈上
-
执行栈就开始执行对应的函数对应的空间的代码块
-
这个代码块里面如果需要开辟空间,它就在这个函数的内存空间上开辟
-
当你不需要使用这个函数了,对应的函数的内存空间就会被回收,那么里面的代码开辟的空间也就被回收了
我们需要保持 i 的状态,那么我们可以将这个 i 放到这个引用数据类型里面,然后保证这个引用数据类型对象的引用,这个时候gc就不会回收对应的这个 i 。
function fn(){
var i = 0
i++
retrun {
i
}
}
var obj = fn()
console.log(obj.i) //1
-
通过上述代码,我们可以保持对 i 的引用,保持 i 不会被回收,以返回一个引用数据类型来保持对应 i 的引用。那么对应的函数也是一个引用数据类型,那么我们是可以通过返回函数的形式来做到保证 i 的唯一性。
function fn(){
var i = 0
return function(){
i++
console.log(i)
}
}
var f = fn()
f() //1
f() //2
f() //3
而上面这种保证i不被回收的机制就叫做闭包。(返回一个引用数据类型 这个里面保证对应的对应i的引用 从而不被回收)
闭包的优缺点
优点
-
内部函数拥有外部函数参数和变量的引用,使用我们的参数和变量的作用范围被扩大。
-
对应的参数不会被回收,在使用的时候就不需要重新开辟空间,速度更快。
-
作为缓存
-
可以让一个变量长期驻扎在内存当中不被释放
-
避免全局变量的污染, 和全局变量不同, 闭包中的变量无法被外部使用
缺点
-
内部函数要一直保持对外部函数里面参数和变量的引用。
-
因为不会回收那么对应的内存空间就会一直占用。
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。 (2)闭包会在父函数外部,改变父函数内部变量的值。
闭包的应用
防抖(在规定时间内置执行一次 执行最后一次)
//第一个参数是做的操作 第二个参数是等待时间
function debounce(fn,delay){
var timer = null
return function(){
clearTimeout(timer) //清除上一次的等待
//开始新的等待
timer = setTimeout(fn,delay)
}
}
节流 (在规定时间内执行第一次 减少执行次数)
//操作 执行一次的时长
function throttle(fn,delay){
var timer = null
return function(){
//判断上一次是否走完
if(timer) return
//上一次走完了开始下一次
timer = setTimeout(()=>{
fn()
//走完了要将节流阀设置为false
timer = null
},delay)
}
}
防抖和节流的区别
-
防抖执行最后一次 节流执行第一次
-
防抖在规定时间内只执行一次 节流是在规定时间内减少对应的执行次数
-
防抖对应的开始下一次先要清除上一次 节流开始下一次先要判断上一次是否执行完毕
函数柯里化(将多个参数的函数 分析为 多个单参数的函数 可以自由组合)
例:
function sum(a,b){
return a+b
}
sum(1,2)
简易的柯里化
function sum(a){ return function(b){ return function(c){ return a+b+c } } } console.log(`sum(1)(2)(3)`, sum(1)(2)(3));//6 console.log(`sum(1)(2)`, sum(1)(2));//fn
核心就是参数没有够 就返回对应的函数,参数够了返回结果
高阶函数柯里化
//传递一个函数 (参数没到返回函数 参数到了返回结果) function currying(fn) { //获取currying传递的参数 let args = Array.prototype.slice.call(arguments,1) return function () { //将对应的函数的参数和curry传递参数做连接 let arg = Array.from(arguments).concat(args) //判断参数个数是否一样 if(arg.length < fn.length){ //参数没到返回函数 return currying.call(this,fn,...arg) }else{ //参数到了 调用方法返回结果 return fn.apply(this,arg) } } }
调用
functin sum(a,b,c){ return a+b+c } console.log(`sum(1,2,3)`,sum(1,2,3)); let fn =currying(sum) console.log(`fn(2)`, fn(2));//函数 console.log(`fn(2)(3)`, fn(2)(3));//函数 console.log(`fn(2)(3)(1)`, fn(2)(3)(1));//6 console.log(` fn()()()(2)()()(3)()()()(1)`, fn()()()(2)()()(3)()()()(10));//15
Promise 的三种状态
概述:promise 是es6新增的一个类,这个类翻译为承诺,它有三种状态 等待状态,成功状态,拒绝状态。它被设计为异步的,它里面的内容是异步的(方法为异步的)
promise 的三种状态
-
等待状态(没有处理) pending
-
成功状态(有对应的处理) fulfilled (里面resolve 方法调用)
-
失败状态(有对应的处理) rejected (里面代码报错 或者 调用 reject)
构建promise 对象
new Promise((成功的函数,失败的函数)=>{
代码块
})
//里面传递的参数是一个函数
//这个传递的函数里面有俩个参数 这个俩个参数也是一个函数
//这个函数里面的第一个参数为成功的函数 resolve 第二个参数为失败的函数 reject (这个俩个函数都是异步的)
var promise = new Promise((resolve,reject)=>{
//包含异步的代码
console.log('hello promise')
})
不同场景的示例
此示例展示了使用 promise 的多种方法,以及其可能发生的多种情况。要理解这一点,首先滚动到代码块的底部,然后查看 promise 调用链。在创建初始的 primise 后,可以接上一条 promise 调用链。
该调用链由 组成,通常(但不一定)在末尾会有一个 ,并可能会接上一个 。在本示例中,promise 调用链是由一个自定义的 构造并发起的;但在实践中,promise 调用链通常由一个 API 函数(由其他人编写的)返回的 promise 开始。.then()``.catch()``.finnaly()``new Promise()
示例函数 会在设置同步调用或者函数内部抛出异常时调用 。函数 展示了如何在 API 函数内部创建并返回一个 promise。tetheredGetNumber()``reject()``promiseGetWord()
请注意,函数 以 结束。这是强制的做法,因为 ES6 的 promsie 会遍历所有的 promise,在遇到错误时,如果不使用 ,这个错误会被当作“已修复”。这很麻烦,因此,通常会在 promise 调用链中忽略 ,而仅在最后的 中保留一个 。另一种方法是抛出一个特殊值(本例使用了 ,但使用自定义错误类型更合适)。troubleWithGetNumber()``throw()``.then``throw()``.then()``rejectionFunc``.catch()``rejectionFunc``-999
示例代码可以在 NodeJS 下运行。请通过查看实际发生的错误来理解代码。若要提高错误发生的概率,请该改变 的值。threshold
"use strict"; // To experiment with error handling, "threshold" values cause errors randomly const THRESHOLD_A = 8; // can use zero 0 to guarantee error function tetheredGetNumber(resolve, reject) { try { setTimeout( function() { const randomInt = Date.now(); const value = randomInt % 10; try { if(value >= THRESHOLD_A) { throw new Error(`Too large: ${value}`); } } catch(msg) { reject(`Error in callback ${msg}`); } resolve(value); return; }, 500); // To experiment with error at set-up, uncomment the following 'throw'. // throw new Error("Bad setup"); } catch(err) { reject(`Error during setup: ${err}`); } return; } function determineParity(value) { const isOdd = value % 2 ? true : false ; const parityInfo = { theNumber: value, isOdd: isOdd }; return parityInfo; } function troubleWithGetNumber(reason) { console.error(`Trouble getting number: ${reason}`); throw -999; // must "throw" something, to maintain error state down the chain } function promiseGetWord(parityInfo) { // The "tetheredGetWord()" function gets "parityInfo" as closure variable. const tetheredGetWord = function(resolve,reject) { const theNumber = parityInfo.theNumber; const threshold_B = THRESHOLD_A - 1; if(theNumber >= threshold_B) { reject(`Still too large: ${theNumber}`); } else { parityInfo.wordEvenOdd = parityInfo.isOdd ? 'odd' : 'even'; resolve(parityInfo); } return; } return new Promise(tetheredGetWord); } (new Promise(tetheredGetNumber)) .then(determineParity,troubleWithGetNumber) .then(promiseGetWord) .then((info) => { console.log("Got: ",info.theNumber," , ", info.wordEvenOdd); return info; }) .catch((reason) => { if(reason === -999) { console.error("Had previously handled error"); } else { console.error(`Trouble with promiseGetWord(): ${reason}`); } }) .finally((info) => console.log("All done"));
promise的方法
原型的方法
-
then 执行成功的回调
var promise = new Promise((resolve,reject)=>{ //成功的函数调用 传递对应的参数 resolve('成功') }) promise.then((res)=>{ console.log(`第一次then`, res); return 'hello' }).then((res)=>{ console.log(`第二次then`, res); return 'world' }).then((res)=>{ console.log(`第三次then`, res); return 'abc' }).then() //值穿透 当你的then没有处理它会给到下一个处理 .then((res)=>{ console.log(`第n次then`, res); })
-
catch 执行失败
var promise = new Promise((resolve,reject)=>{ reject('失败') // throw new Error('失败了') }) //.catch默认请求下只执行第一个 如果需要走下面的 那么需要你报错 promise.catch((error)=>{ console.log(`第一次`, error); throw new Error('失败了') }).catch((error)=>{ console.log(`第二次`, error); throw new Error('失败了') }).catch((error)=>{ console.log(`第三次`, error); throw new Error('失败了') }).catch() .catch((error)=>{ console.log(`第n次`, error); })
-
finally 执行完成调用的
示例
//promise promise只能满足于一种状态 进入到成功它就成功了 进入失败就失败了 var success = new Promise((resolve,reject)=>{ //成功的函数调用 传递对应的参数 resolve('成功') reject('失败') // throw new Error('失败了') }) //成功的回调 俩个参数 成功的函数 失败的函数 success.then((res)=>{//res会接收resolve传递的参数 console.log(`res`, res); },(error)=>{//error 接收reject传递的参数 console.log(`error`, error); }) //失败的回调 参数1个 传递为一个函数 这个函数可以接收rejected传递的参数 success.catch((error)=>{ console.log(`rejected`, error); }) //完成就能调用的函数 (成功 失败) success.finally(()=>{ console.log(`完成了`); })
静态方法
-
resolve (返回成功状态的promise)
-
reject (返回失败状态的promise)
-
all (并行执行所有的promise 如果遇到rejected就返回reject的promise 如果全部成功就返回所有的结果(promiseresult))
-
allSettled (互不影响执行对应的promise 返回所有的结果(状态一定是成功))
-
race (返回最快执行完成的promise)
//静态方法 Promise.方法名 //resolve方法 //返回一个成功的promise对象 var promise = Promise.resolve('hello') console.log(`promise`, promise); promise.then(res=>{ console.log(res); }) // reject返回一个失败的promise对象 var promise1 = Promise.reject('错误') console.log(`promise1`, promise1); promise1.catch(error=>{ console.log(error); }) //all 传入一个promise数组 并行执行promise数组里面的promise (如果有一个是rejected 那么整体都是rejected) var promise2 = Promise.resolve('hello') var promise3 = Promise.reject('错误') // var promise3 = Promise.resolve('成功') var promise4 = Promise.resolve('world') var promise5 = Promise.resolve('你好') //all方法返回的是一个promise对象 如果全部成功对应的promiseResult里面的结果就是所有成功 否则就是错误的结果 var promise6 = Promise.all([promise2,promise3,promise4,promise5]) console.log(promise6); //传入一个promise数组 返回一个promise对象 不会互相影响 返回所有结果(状态为成功) var promise7 = Promise.allSettled([promise2,promise3,promise4,promise5]) console.log(promise7); //竞速 race 传入promise数组 返回最快走完的promise var promise8 = Promise.race([promise2,promise3,promise4,promise5]) console.log(promise8);
promise的三种状态图
一个 对象代表一个在这个 promise 被创建出来时不一定已知值的代理。它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。Promise
一个 必然处于以下几种状态之一:Promise
-
待定(pending):初始状态,既没有被兑现,也没有被拒绝。
-
已兑现(Fulfilled):意味着操作成功完成。
-
已拒绝(rejected):意味着操作失败。
待定状态的 Promise 对象要么会通过一个值被兑现,要么会通过一个原因(错误)被拒绝。当这些情况之一发生时,我们用 promise 的 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序也同样会被调用,因此在完成异步操作和绑定处理方法之间不存在竞态条件。then
因为 和 方法返回的是 promise,所以它们可以被链式调用。Promise.prototype.then``Promise.prototype.catch
注:
-
有一些语言中有惰性求值和延迟计算的特性,它们也被称为“promise”,例如 Scheme。JavaScript 中的 promise 代表的是已经在发生的进程,而且可以通过回调函数实现链式调用。如果你想对一个表达式进行惰性求值,就考虑一下使用无参数的箭头函数,如 来创建惰性求值的表达式,然后使用 进行求值。
`f =()=>expression f()
-
如果一个 promise 已经被兑现或被拒绝,那么我们也可以说它处于 已敲定(settled) 状态。你还会听到一个经常跟 promise 一起使用的术语:已决议(resolved),它表示 promise 已经处于已敲定状态,或者为了匹配另一个 promise 的状态被“锁定”了。
回调地狱
概述:回调函数的无限嵌套导致当前代码失去了对应的维护价值及对应的可读性。
示例
//传入一个函数作为回调函数 在对应代码执行完成调用 function fn(fn) { setTimeout(function () { console.log('10'); //走完了以后回调函数执行 fn() }, 1000) } fn(() => { console.log(1); }) //多个回调函数嵌套 回调地狱 (回调函数的无限嵌套 代码的可读性 可维护性 已经失去了) fn(() => { console.log(1); fn(() => { console.log(2); fn(() => { console.log(3); fn(() => { console.log(4); fn(() => { console.log(5); fn(() => { console.log(6); .... }) }) }) }) }) })
promise来解决回调地狱(链式调用)
在.then里面返回一个新的promise对象 在对应的异步代码执行完后调用resolve
//利用promise来解决回调地狱的问题 new Promise((resolve, reject) => { setTimeout(() => { console.log(1); resolve() }); }).then(() => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(2); resolve() }); }) }).then(()=>{ return new Promise((resolve, reject) => { setTimeout(() => { console.log(3); resolve() }); }) }).then(()=>{ return new Promise((resolve, reject) => { setTimeout(() => { console.log(4); resolve() }); }) }).then(()=>{ console.log(5); })
Promise 的链式调用
我们可以用 、 和 这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。Promise.prototype.then()``Promise.prototype.catch()``Promise.prototype.finally()
例如 :方法需要两个参数,第一个参数作为处理已兑现状态的回调函数,而第二个参数则作为处理已拒绝状态的回调函数。每一个 方法还会返回一个新生成的 promise 对象,这个对象可被用作链式调用,就像这样:.then()``.then()
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
myPromise
.then(handleResolvedA, handleRejectedA)
.then(handleResolvedB, handleRejectedB)
.then(handleResolvedC, handleRejectedC);
当 中缺少能够返回 promise 对象的函数时,链式调用就直接继续进行下一环操作。因此,链式调用可以在最后一个 之前把所有的处理已拒绝状态的回调函数都省略掉。.then()``.catch()
过早地处理变为已拒绝状态的 promise 会对之后 promise 的链式调用造成影响。不过有时候我们因为需要马上处理一个错误也只能这样做。例如,外面必须抛出某种类型的错误以在链式调用中传递错误状态。另一方面,在没有迫切需要的情况下,可以在最后一个 语句时再进行错误处理,这种做法更加简单。其实只是没有给处理已兑现状态的回调函数预留参数位置的 而已。.catch()``.catch()``.then()
myPromise
.then(handleResolvedA)
.then(handleResolvedB)
.then(handleResolvedC)
.catch(handleRejectedAny);
使用箭头函数表达式作为 promise 回调函数
例:
myPromise
.then(value => { return value + ' and bar'; })
.then(value => { return value + ' and bar again'; })
.then(value => { return value + ' and again'; })
.then(value => { return value + ' and again'; })
.then(value => { console.log(value) })
.catch(err => { console.log(err) });
这些函数的终止状态决定着链式调用中下一个 promise 的“已敲定”状态是什么。“已决议”状态意味着 promise 已经成功完成,而“已拒绝”则表示 promise 未成功完成。"已决议"状态的返回值会逐级传递到下一个 中,而"已拒绝"的理由则会被传递到链中的下一个已拒绝状态的处理函数。.then()
链式调用中的 promise 们就像俄罗斯套娃一样,是嵌套起来的,但又像是一个栈,每个都必须从顶端被弹出。链式调用中的第一个 promise 是嵌套最深的一个,也将是第一个被弹出的。
(promise D, (promise C, (promise B, (promise A) ) ) )
一个 promise 可能会参与不止一次的嵌套。对于下面的代码, 向"已敲定"状态的过渡会导致两个实例的 都被调用。promiseA``.then
const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2);
一个已经处于“已敲定”状态的 promise 也可以接收操作。在那种情况下,(如果没有问题的话)这个操作会被作为第一个异步操作被执行。注意,所有的 promise 都一定是异步的。因此,一个已经处于“已敲定”状态的 promise 中的操作只有 promise 链式调用的栈被清空且一个时间片段过去之后才会被执行。这种效果跟 特别相似。setTimeout(action, 10)
const promiseA = new Promise( (resolutionFunc,rejectionFunc) => { resolutionFunc(777); }); // At this point, "promiseA" is already settled. promiseA.then( (val) => console.log("asynchronous logging has val:",val) ); console.log("immediate logging"); // produces output in this order: // immediate logging // asynchronous logging has val: 777
追踪现有设置对象
设置对象(settings object)是 JavaScript 代码运行时用于提供附加信息的环境。它包含了领域(realm)和模块映射(module map),以及 HTML 的特定信息,如来源(origin)等。对现有设置对象的追踪保证了浏览器知道用户给定的哪些代码片段需要使用。
为了更好地说明这一点,我们在这里进一步探讨领域是如何引发问题的。我们可以粗略地认为领域是一个全局对象。其独特之处在于,它拥有运行 JavaScript 代码所需的所有信息。这包括像 Array
和 Error
这样的对象。每一个设置对象都有自己的"副本",而且它们与副本之间是不共享的。这可能会导致一些与 promise 相关的意外行为。为了解决这个问题,我们需要追踪现有设置对象(incumbent settings object)。它表示负责用户某个函数调用工作的特定信息。
我们可以尝试在文档中嵌入 <iframe>
,并让其与父级上下文通信。由于所有的 web API 都有现有设置对象,下面的代码能够在所有的浏览器中运行:
<!DOCTYPE html> <iframe></iframe> <!-- we have a realm here --> <script> // we have a realm here as well const bound = frames[0].postMessage.bind( frames[0], "some data", "*"); // bound is a built-in function -- there is no user // code on the stack, so which realm do we use? window.setTimeout(bound); // this still works, because we use the youngest // realm (the incumbent) on the stack </script>
同样的概念也适用与 promise。稍加修改上面的示例,就能得到如下:
<!DOCTYPE html> <iframe></iframe> <!-- we have a realm here --> <script> // we have a realm here as well const bound = frames[0].postMessage.bind( frames[0], "some data", "*"); // bound is a built in function -- there is no user // code on the stack -- which realm do we use? Promise.resolve(undefined).then(bound); // this still works, because we use the youngest // realm (the incumbent) on the stack </script>
修改代码,使用文档中的 <iframe>
来监听发送的消息,可以观察到现有设置对象的影响:
<!-- y.html --> <!DOCTYPE html> <iframe src="x.html"></iframe> <script> const bound = frames[0].postMessage.bind(frames[0], "some data", "*"); Promise.resolve(undefined).then(bound); </script> <!-- x.html --> <!DOCTYPE html> <script> window.addEventListener("message", (event) => { document.querySelector("#text").textContent = "hello"; // 这一部分代码仅在追踪现有设置对象的浏览器中会被运行 console.log(event); }, false); </script>
上诉可得,<iframe>
仅在现有设置对象被追踪时才会被更新。这是因为在不追踪的情况下,我们可能会使用错误的环境发送消息。
注: 目前,Firefox 完全实现了现有领域追踪,Chrome 和 Safari 仅部分实现。
构造函数
-
创建一个新的 对象。该构造函数主要用于包装还没有添加 promise 支持的函数。
Promise
静态方法
-
这个方法返回一个新的 promise 对象,等到所有的 promise 对象都成功或有任意一个 promise 失败。如果所有的 promise 都成功了,它会把一个包含 iterable 里所有 promise 返回值的数组作为成功回调的返回值。顺序跟 iterable 的顺序保持一致。一旦有任意一个 iterable 里面的 promise 对象失败则立即以该 promise 对象失败的理由来拒绝这个新的 promise。
-
等到所有 promise 都已敲定(每个 promise 都已兑现或已拒绝)。返回一个 promise,该 promise 在所有 promise 都敲定后完成,并兑现一个对象数组,其中的对象对应每个 promise 的结果。
-
接收一个 promise 对象的集合,当其中的任意一个 promise 成功,就返回那个成功的 promise 的值。
-
等到任意一个 promise 的状态变为已敲定。当 iterable 参数里的任意一个子 promise 成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应处理函数,并返回该 promise 对象。
-
返回一个状态为已拒绝的 对象,并将给定的失败信息传递给对应的处理函数。
Promise
-
返回一个状态由给定值 决定的 对象。如果该值是 thenable(即,带有 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行结果决定;否则,返回的 Promise 对象状态为已兑现,并且将该 value 传递给对应的 then 方法。
Promise``then
通常而言,如果你不知道一个值是否是 promise 对象,使用Promise.resolve(value)
来返回一个 Promise 对象,这样就能将该 value 以 promise 对象形式使用。
async await (es7新增的)
概述:async和await是对应的俩个连用的关键词,async是修饰函数的,await是修饰promise的。await只能在async内使用。async修饰的函数返回一个promise对象,await修饰的promise对象会占用当前的线程 直到对应的promise执行完成才会释放。
async function fn() { await new Promise((resolve,reject)=>{ //如果没有放行后面的不会执行 setTimeout(() => { console.log('hello'); resolve() }) }) console.log('world'); } //async修饰完函数执行会返回一个promise对象 console.log(fn()); //async修饰的函数返回的promise对象 //里面的报错 会使当前的promise对象的状态为rejected //如果里面return内容那么内容将会传递给对应的then async function fn1(){ throw new Error('错误') // return '我是fn1' } fn1().then(res=>{ console.log(res); },error=>{ console.log(error); }) //await会使用当前的函数的线程占用 直到对应的修饰的promise执行完成 // await Promise.reject() 报错
利用async和await来解决回调地狱
function fn(v,delay) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(v); resolve() },delay); }) } async function fn1(){ await fn(1,1000) await fn(2,2000) await fn(3,100) await fn(4,0) console.log(5); } fn1()
总结
-
async修饰函数的
-
await修饰promise对象
-
async里面使用await 那么如果这个await修饰的promise没有执行完,那么对应的async修饰的函数返回promise状态时pending。
-
如果async修饰的函数内什么都没有那么对应返回的promise状态是成功(默认函数返回undefined)
-
async修饰的函数 返回值就是成功 返回的值传递给then方法
-
async修饰的函数如果里面报错 那么返回的是失败 传递的值为报的错
-
await只能在async里面使用 await会使当前的函数陷入等待
代码执行机制
同步代码执行比异步代码快
同步代码的执行是利用对应的js引擎解析的
异步代码执行是利用事件轮询机制执行的
事件轮询机制
-
先找script标签里面的微任务
-
按照微任务队列执行完对应的微任务
-
进入下一个宏任务 执行对应的宏任务代码
-
进行宏任务对应微任务队列 执行对应微任务
-
再进行到下一个宏任务 执行对应的微任务
-
直到对应的宏任务队列和微任务队列被清空
-----------------------
仅供参考
标签:闭包,console,log,promise,Promise,fn,函数 From: https://www.cnblogs.com/hofenglang/p/16830256.html