前端的几道面试题

187 阅读5分钟

第一题

setTimeout(() => console.log(0))
new Promise((resolve, reject) => {
    console.log(1);
    // resolve(999);
    // reject('what')
})
    // .then(d => {
    //     console.log(d);
    // })
    .then((d) => {
        setTimeout((_) => console.log(2));
    })
    // .then()
    .then((d) => {
        console.log(3);
    })
    .catch((err) => console.log(err))
    .finally((_) => console.log(4));

//  1 -> 3 -> 4 -> 0 -> 2

EventLoop事件循环

JS是单线程执行的,当遇到一个异步事件后,会先将这个事件挂起,继续执行栈中的其他任务。 当一个异步事件返回结果后,JS会将这个事件加入到另一个事件队列。被放入事件队列后不会立即执行它的回调,而是等待当前执行栈中的所有任务都执行完毕,主线程会去查找事件队列是否有任务。如果有,主线程会从里面取出排在第一位的事件,并将这个事件对应的回调放入执行栈中,然后执行其中的同步代码。 这种反复循环的过程就叫做事件循环。

异步任务队列里面又分为微任务和宏任务,并且微任务的优先级更高,微任务队列清空后,再去执行宏任务。

(一个宏任务要想结束,必然要去检查且清空微任务队列,才能开启下一轮事件循环(执行下一个宏任务),换句话说,一个宏任务即代表一个事件循环)

微任务包括 promise的回调、对DOM变化监听的 MutationObserver、process.nextTick(nodejs的)

宏任务包括 script脚本的执行 、 setTimeout 、setInterval 、setImmediate一类的定时事件、I/O操作、UI渲染、postMessage、MessageChannel。

题解:
  1. setTimeout(() => console.log(0)) 宏任务,被挂起在宏任务队列
  2. promise构造函数里面是同步代码,console.log(1);会先执行;
  3. 如果没有 resolve 或者 reject,promise后面的回调都不会执行,结果就是 1 -> 0;
  4. 反之,执行then回调,这个是微任务,被挂起在微任务队列,console.log(3);console.log(4);顺序执行;
  5. then回调里遇到宏任务setTimeout((_) => console.log(2));,被挂起在宏任务队列里,需要执行完所有微任务,才会执行。
  6. 最终结果:打印 1 -> 3 -> 4 -> 0 -> 2

promise

promise是异步编程的一种解决方案,它通常用来保存异步操作的结果。

promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

构建promise对象时,需要传入一个执行函数,resolve和reject两个函数作为参数,resolve被调用时,promise的状态会变成fulfilled(完成的),会触发then回调函数;reject被调用时,promise的状态会变成rejected(失败的),会触发catch回调函数。而且一旦状态改变,就不会再变,任何时候都可以得到这个结果。

promise的错误具有冒泡特性,会一直向后传递,可以通过最后一个 catch 来捕获异常,通过这种方式可以将所有 Promise 对象的错误合并到一个函数来处理,这样就解决了每个任务都需要单独处理异常的问题。

如何中断promise的链式调用:

  • 抛出异常 throw xxx
  • 通过reject中断 return Promise.reject('break with exception')

async/await的实现是基于 Promise的,async 函数就是返回Promise对象。

  • 语法简洁,更像是同步代码,也更符合普通的阅读习惯;
  • 改进JS中异步操作串行执行的代码组织方式,减少callback的嵌套;
  • Promise中不能自定义使用try/catch进行错误捕获,但是在Async/await中可以像处理同步代码处理错误
  • 缺点是如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。

Promise.all() 当参数数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回。如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。

返回的结果是按照参数数组中的顺序来。(效果是“谁跑的慢,以谁为准执行回调”)

Promise.all([false, undefined, 0]);会返回[false, undefined, 0],原理是在promise内部使用了 Promise.resolve() 将这些参数值包裹了一下。

Promise.allSettled()和all类似,区别在于它可以拿到每个promise的状态,不管是否处理成功。

Promise.race()只要有一个promise对象进入FulFilled或者Rejected状态的话,就会继续进行后面的处理。(“谁跑的快,以谁为准执行回调”)

finally()在promise结束时,无论结果是fulfilled还是rejected,都会执行finally的回调函数,没有参数,表明与状态无关,通常可以用来关闭请求的loading。

第二题

var myObject = {
    foo: 'bar',
    func: function () {
        var self = this;
        console.log(this.foo);  // bar
        console.log(self.foo);  // bar
        (function () {
            (
                (self) => console.log(self.foo) // undefined
            )(this);
            console.log(self.foo);  // bar
        })();
    }
}
myObject.func();

this

this是函数运行时自动生成的一个内部对象,指向调用它的对象。

  • 作为普通函数执行时,this指向window,严格模式下this指向undefined。(匿名函数的this指向window
  • 函数作为对象里的方法被调用时,this指向该对象。
  • 当用new的时候,this指向返回的这个对象。
  • 箭头函数没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined(箭头函数是定义时绑定,其他是允许时绑定)。
  • 可以通过bind,apply,call方法显示改变this的指向。call和apply的区别,传参的格式不一样,call是字段分开,apply是数组。
  • this的优先级:new > 显式 > 隐式 > 默认