网上商城建站工作室,合肥网站建设维护,中建八局第一建设有限公司电话,门户网站系统设计【简答题】一、谈谈你是如何理解JS异步编程的#xff0c;EventLoop、消息队列都是做什么的#xff0c;什么是宏任务、什么是微任务#xff1f;如何理解JS异步编程众所周知JavaScript语言执行环境是“单线程”(单线程#xff0c;就是指一次只能完成一件任务#xff0c;如果…【简答题】一、谈谈你是如何理解JS异步编程的EventLoop、消息队列都是做什么的什么是宏任务、什么是微任务如何理解JS异步编程众所周知JavaScript语言执行环境是“单线程”(单线程就是指一次只能完成一件任务如果有多个任务就必须排队等候前面一个任务完成再执行后面一个任务)。这种“单线程”模式执行效率较低任务耗时长。 为了解决这个问题提出了“异步模式”(异步模式是指后一个任务不等前一个任务执行完就执行每个任务有一个或多个回调函数)。 异步模式使得JavaScript在处理事务时非常高效但也带来很多问题如异常处理困难、嵌套过深。EventLoop是做什么的event loop是一个执行模型在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。浏览器的Event Loop是在html5的规范中明确定义。NodeJS的Event Loop是基于libuv实现的。libuv已经对Event Loop做出了实现而HTML5规范中只是定义了浏览器中Event Loop的模型具体的实现留给了浏览器厂商。浏览器的Event Loop这张图将浏览器的Event Loop完整的描述了出来我来讲执行一个JavaScript代码的具体流程 1. 执行全局Script同步代码这些同步代码有一些是同步语句有一些是异步语句(比如setTimeout等) 2. 全局Script代码执行完毕后调用栈Stack会清空 3. 从微队列microtask queue中取出位于队首的回调任务放入调用栈Stack中执行执行完后microtask queue长度减1 4. 继续取出位于队首的任务放入调用栈Stack中执行以此类推直到直到把microtask queue中的所有任务都执行完毕。注意如果在执行microtask的过程中又产生了microtask那么会加入到队列的末尾也会在这个周期被调用执行 5. microtask queue中的所有任务都执行完毕此时microtask queue为空队列调用栈Stack也为空 6. 取出宏队列macrotask queue中位于队首的任务放入Stack中执行 7. 执行完毕后调用栈Stack为空 8. 重复第3-7个步骤 9. ......可以看到这就是浏览器的事件循环Event Loop这里归纳3个重点 1. 宏队列macrotask一次只从队列中取一个任务执行执行完后就去执行微任务队列中的任务 2. 微任务队列中所有的任务都会被依次取出来执行直到microtask queue为空 3. 图中没有画UI rendering的节点因为这个是由浏览器自行判断决定的但是只要执行UI rendering它的节点是在执行完所有的microtask之后下一个macrotask之前紧跟着执行UI render。在执行微队列microtask queue中任务的时候如果又产生了microtask那么会继续添加到队列的末尾也会在这个周期执行直到microtask queue为空停止。注当然如果你在microtask中不断的产生microtask那么其他宏任务macrotask就无法执行了但是这个操作也不是无限的拿NodeJS中的微任务process.nextTick()来说它的上限是1000个这里不再详细讲。总结浏览器的Event Loop和NodeJS的Event Loop是不同的实现机制也不一样不要混为一谈今天我们只介绍浏览器里面的Event Loop。浏览器可以理解成只有1个宏任务队列和1个微任务队列先执行全局Script代码执行完同步代码调用栈清空后从微任务队列中依次取出所有的任务放入调用栈执行微任务队列清空后从宏任务队列中只取位于队首的任务放入调用栈执行注意这里和Node的区别只取一个然后继续执行微队列中的所有任务再去宏队列取一个以此构成事件循环。消息队列是做什么的消息队列也称为任务队列是一个先进先出的队列它里面存放着各种消息即异步操作的回调函数异步操作会将相关回调添加到任务队列中而不同的异步操作添加到任务队列的时机也不同如onclicksetTimeoutajax处理的方式都不同这些异步操作都是由浏览器内核的不同模块来执行的onclick由浏览器内核的DOM Binding模块来处理当事件触发的时候回调函数会立即添加到任务队列中setTimeout会由浏览器内核的timer模块来进行延时处理当时间到达的时候才会将回调函数添加到任务队列中ajax会由浏览器内核的network模块来处理在网络请求完成返回之后才将回调添加到任务队列中什么是宏任务宏任务/宏队列macrotask也叫tasks。 一些异步任务的回调会依次进入macro task queue等待后续被调用这些异步任务包括setTimeoutsetIntervalsetImmediate (Node独有)requestAnimationFrame (浏览器独有)I/OUI rendering (浏览器独有)什么是微任务微任务/微队列microtask也叫jobs。 另一些异步任务的回调会依次进入micro task queue等待后续被调用这些异步任务包括process.nextTick (Node独有)PromiseObject.observeMutationObserverqueueMicroTask宏任务与微任务的区别这个就像去银行办业务一样先要取号进行排号。 一般上边都会印着类似“您的号码为XX前边还有XX人。”之类的字样。因为柜员同时只能处理一个来办理业务的客户这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的当柜员处理完当前客户的问题以后选择接待下一位广播报号也就是下一个宏任务的开始。 所以多个宏任务合在一起就可以认为说有一个任务队列在这里边是当前银行中所有排号的客户。 任务队列中的都是已经完成的异步操作而不是说注册一个异步任务就会被放在这个任务队列中就像在银行中排号如果叫到你的时候你不在那么你当前的号牌就作废了柜员会选择直接跳过进行下一个客户的业务处理等你回来以后还需要重新取号。而且一个宏任务在执行的过程中是可以添加一些微任务的就像在柜台办理业务你前边的一位老大爷可能在存款在存款这个业务办理完以后柜员会问老大爷还有没有其他需要办理的业务这时老大爷想了一下“最近P2P爆雷有点儿多是不是要选择稳一些的理财呢”然后告诉柜员说要办一些理财的业务这时候柜员肯定不能告诉老大爷说“您再上后边取个号去重新排队”。 所以本来快轮到你来办理业务会因为老大爷临时添加的“理财业务”而往后推。 也许老大爷在办完理财以后还想 再办一个信用卡或者 再买点儿纪念币 无论是什么需求只要是柜员能够帮她办理的都会在处理你的业务之前来做这些事情这些都可以认为是微任务。在当前的微任务没有执行完成时是不会执行下一个宏任务的。【代码题】一、将下面异步代码使用Promise的方式改进setTimeout(function(){var a  hellosetTimeout(function(){var b  lagousetTimeout(function(){var c  I ❤ Uconsole.log(a  b  c)},10)},10)},10)解答function fn(parp) {const promise  new Promise((resolved, rejected)  {setTimeout(()resolved(parp), 10)});return promise;}fn().then(()  fn(hello)).then(value  fn(valuelagou)).then(value  fn(valueI ❤ U)).then(value  console.log(value))【代码题】二、基于以下代码完成下面的四个练习const fp  require(lodash/fp)// 数据// horsepower 马力, dollar_value 价格, in_stock 库存const cars  [{ name: Ferrari FF, horsepower: 660,dollar_value: 700000, in_stock: true },{ name: Spyker C12 Zagato, horsepower: 650,dollar_value: 648000, in_stock: false },{ name: Jaguar XKR-S, horsepower: 550,dollar_value: 132000, in_stock: false },{ name: Audi R8, horsepower: 525,dollar_value: 114200, in_stock: false },{ name: Aston Martin One-77, horsepower: 750,dollar_value: 1850000, in_stock: true },{ name: Pagani Huayra, horsepower: 700,dollar_value: 1300000, in_stock: false },]练习1使用函数组合fp.flowRight()重新实现下面这个函数let isLastInStock  function (cars) {// 获取最后一条数据 let last_car  fp.last(cars)// 获取最后一条数据的 in_stock 属性值 return fp.prop(in_stock, last_car)}解答let isLastInStock  fp.flowRight(fp.prop(in_stock), fp.last)//fp.prop可以替换为fp.getconsole.log(isLastInStock(cars))练习2使用fp.flowRight()、fp.prop()和fp.first()获取第一个car的name解答let isFirstCarName  fp.flowRight(fp.prop(name), fp.first)//fp.first可替换为fp.headconsole.log(isFirstCarName(cars))练习3使用帮助函数_average重构averageDollarValue使用组合函数的方式实现let _average  function (xs) {return fp.reduce(fp.add, 0, xs)/xs.length}// let dollar_values  fp.map(function(car){return car.dollar_value}, cars)return _average(dollar_values)}解答//第一种let averageDollarValue  fp.flowRight(_average, fp.map(carcar.dollar_value))//第二种let averageDollorValue  fp.flowRight(_average, fp.map(fp.curry(fp.prop)(dollar_value)))练习4使用flowRight写一个sanitizeNames()函数返回一个下划线连接的小写字符串把数组中的name转换为这种形式例如sanitizeNames([Hello World])[hello_world]let _underscore  fp.replace(/\w/g, _) // 解答//let _underscore  fp.replace(/\w/g, _) // 不符合题意 应该为\slet _underscore  fp.replace(/\s/g, _)//答案let sanitizeNames  fp.flowRight(fp.map(_underscore), fp.map(fp.toLower), fp.map(car  car.name))【代码题】三、基于下面提供的代码完成后续的四个练习// support.jsclass Container {static of(value) {return new Container(value)}constructor(value) {this.value  value}map(fn) {return Container.of(fn(this.value))}}class Maybe {static of(x) {return new Maybe(x)}isNothing() {return this._value  null || this._value  undefined}constructor(x) {this._value  x}map(fn) {return this.isNothing() ? this : Maybe.of(fn(this._value))}}module.exports  { Maybe, Container }练习1使用fp.add(x,y)和fp.map(f,x)创建一个能让functor里的值增加的函数ex1// app.jsconst fp  require(lodash/fp)const { Maybe, Container }  require(./support)let maybe  Maybe.of([5, 6, 1])let ex1  ()  {// 你需要实现的函数。。。}解答return maybe.map(function(arr){return fp.map(function(v){return fp.add(v,1) // 数组每一项加1 },arr)})//ES6return maybe.map(arr  fp.map(v  fp.add(v, 1), arr))练习2实现一个函数ex2能够使用fp.first获取列表的第一个元素// app.jsconst fp  require(lodash/fp)const { Maybe, Container }  require(./support)let xs  Container.of([do, ray,me, fa, so, la, ti, do])let ex2  ()  {// 你需要实现的函数。。。}解答return xs.map(fp.first).value练习3实现一个函数ex3使用safeProp和fp.first找到user的名字的首字母// app.jsconst fp  require(lodash/fp)const { Maybe, Container }  require(./support)let safeProp  fp.curry(function (x, o){return Maybe.of(o[x])})let user  { id:2, name: Albert }let ex3  ()  {// 你需要实现的函数。。。}解答return safeProp(name, user).map(fp.first)._value练习4使用Maybe重写ex4不要有if语句// app.jsconst fp  require(lodash/fp)const { Maybe, Container }  require(./support)let ex4  function (n) {if(n) {return parseInt(n)}}解答let ex4  n  Maybe.of(n).map(parseInt)._value;【代码题】四、手写实现MyPromise源码要求尽可能还原Promise中的每一个API并通过注释的方式描述思路和原理解答/*1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pendingpending - fulfilledpending - rejected一旦状态确定就不可更改3. resolve和reject函数是用来更改状态的resolve: fulfilledreject: rejected4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因6. 同一个promise对象下面的then方法是可以被调用多次的7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值*///我们将状态定义为常量因为我们需要频繁使用它//当我们去使用这个常量的时候编辑器是有代码提示的const PENDING  pending; // 等待const FULFILLED  fulfilled; // 成功const REJECTED  rejected; // 失败class MyPromise {constructor (executor) { // 代表执行器 try { // 捕获执行器的错误 executor error // 这个执行器executor是立即执行的 //当前我们在一个类的里面我们需要通过this访问 executor(this.resolve, this.reject)} catch (e) {this.reject(e);}}// promsie 状态 默认为 等待 status  PENDING;// 成功之后的值 value  undefined;// 失败后的原因 reason  undefined;// 成功回调 数组的原因是同一个promise对象下面的then方法是可以被多次调用的 为了同时存储多个回调函数 successCallback  [];// 失败回调 数组的原因是同一个promise对象下面的then方法是可以被多次调用的 为了同时存储多个回调函数 failCallback  [];// 这里的resolve和reject之所以使用箭头函数是因为this指向的问题 // 我们希望this指向promise对象而不是window resolve  value  {// 如果状态不是等待 阻止程序向下执行 if (this.status ! PENDING) return;// 将状态更改为成功 this.status  FULFILLED;// 保存成功之后的值 this.value  value;// 判断成功回调是否存在 如果存在 调用 // this.successCallback  this.successCallback(this.value); //处理异步情况 由于回调为数组 故失效 while(this.successCallback.length) this.successCallback.shift()() //从前往后执行 弹出回调函数 }reject  reason  {// 如果状态不是等待 阻止程序向下执行 if (this.status ! PENDING) return;// 将状态更改为失败 this.status  REJECTED;// 保存失败后的原因 this.reason  reason;// 判断失败回调是否存在 如果存在 调用 // this.failCallback  this.failCallback(this.reason); //处理异步情况 while(this.failCallback.length) this.failCallback.shift()() //从前往后执行 弹出回调函数 }then (successCallback, failCallback) {//then()方法可以不传递参数 // 参数可选 successCallback  successCallback ? successCallback : value  value;// 参数可选 failCallback  failCallback ? failCallback: reason  { throw reason };// 为了能够被链式调用 then方法必须返回一个promise对象 let promsie2  new MyPromise((resolve, reject)  { //立即执行 resolve和reject是为了传递给下一个then方法的回调函数 // 判断状态 if (this.status  FULFILLED) { // 成功 setTimeout(()  { // 正常来讲内部无法直接获取到 promise2 因为其本身还未执行 所以将其变为异步代码 try { // then回调函数错误 并在下一次promise的下一次错误处理函数中输出 // 方便then的链式调用 我们需要获取到then的返回值 以便传递给下一个then的成功回调函数 let x  successCallback(this.value);// 判断 x 的值是普通值还是promise对象 // 如果是普通值 直接调用resolve // 如果是promise对象 查看promsie对象返回的结果 // 再根据promise对象返回的结果 决定调用resolve 还是调用reject resolvePromise(promsie2, x, resolve, reject) // 调用 解析promise }catch (e) {reject(e);}}, 0)}else if (this.status  REJECTED) { // 失败 setTimeout(()  { // 正常来讲内部无法直接获取到 promise2 因为其本身还未执行 所以将其变为异步代码 try { // then回调函数错误 并在下一个promise的错误处理函数中输出 // 方便then的链式调用 我们需要获取到then的返回值 以便传递给下一个then的失败回调函数 let x  failCallback(this.reason);// 判断 x 的值是普通值还是promise对象 // 如果是普通值 直接调用resolve // 如果是promise对象 查看promsie对象返回的结果 // 再根据promise对象返回的结果 决定调用resolve 还是调用reject resolvePromise(promsie2, x, resolve, reject) // 调用 解析promise }catch (e) {reject(e);}}, 0)} else { // 等待 // 将成功回调和失败回调存储起来 this.successCallback.push(()  { // 存储成功回调到数组 setTimeout(()  {try {let x  successCallback(this.value);// 判断 x 的值是普通值还是promise对象 // 如果是普通值 直接调用resolve // 如果是promise对象 查看promsie对象返回的结果 // 再根据promise对象返回的结果 决定调用resolve 还是调用reject resolvePromise(promsie2, x, resolve, reject)}catch (e) {reject(e);}}, 0)});this.failCallback.push(()  { // 存储失败回调到数组 setTimeout(()  {try {let x  failCallback(this.reason);// 判断 x 的值是普通值还是promise对象 // 如果是普通值 直接调用resolve // 如果是promise对象 查看promsie对象返回的结果 // 再根据promise对象返回的结果 决定调用resolve 还是调用reject resolvePromise(promsie2, x, resolve, reject)}catch (e) {reject(e);}}, 0)});}});return promsie2;}finally (callback) { // 接收一个回调函数 可以返回一个函数 return this.then(value  { //通过then得到当前promise的状态 并返回以便再次调用 return MyPromise.resolve(callback()).then(()  value);}, reason  {return MyPromise.resolve(callback()).then(()  { throw reason })})}catch (failCallback) {return this.then(undefined, failCallback)}static all (array) { // 接收数组 参数顺序一定为结果顺序 也是一个promise对象 Promise.all 故为静态方法 let result  []; // 结果数组 let index  0; // 解决for循环的异步操作问题 return new MyPromise((resolve, reject)  {function addData (key, value) {result[key]  value;index;if (index  array.length) {resolve(result);}}for (let i  0; i  array.length; i) { // 执行for循环的过程中可能有异步操作 let current  array[i]; // 当前值 if (current instanceof MyPromise) {// promise 对象 current.then(value  addData(i, value), reason  reject(reason))}else {// 普通值 addData(i, array[i]);}}})}static resolve (value) { // 判断给定的是不是promise 是 直接返回 不是 创建promise 并返回 if (value instanceof MyPromise) return value;return new MyPromise(resolve  resolve(value));}}function resolvePromise (promsie2, x, resolve, reject) {// 解析promise 判断then回调的返回值的类型 为promise对象还是普通值 if (promsie2  x) { // 判断 then是否被循环调用 并阻止其运行 return reject(new TypeError(Chaining cycle detected for promise #))}if (x instanceof MyPromise) {// promise 对象 // x.then(value  resolve(value), reason  reject(reason)); // 简化为 ↓ x.then(resolve, reject);} else {// 普通值 resolve(x);}}module.exports  MyPromise;