面试题第八期

137 阅读10分钟

10月14日

1.异步编程的实现方式?

(1)回调函数

回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

(2)事件监听

实现原理也是利用定时器的原理去把f1放入事件队列里,等全部执行完毕之后,才会执行事件队列里的方法

  这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

(3)Promises对象

使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。

(4) Generator

Generator 也是一种异步编程解决方案,它最大的特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用 yield 语法来标注。Generator 函数一般配合 yield 使用,Generator 函数最后返回的是迭代器。

(5) async 函数

async 函数就是 Generator 函数的语法糖。

2 并发与并行的区别?

  • 并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
  • 并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。

3 什么是回调地狱?回调地狱会带来什么问题?

回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。

4.setTimeout时间为0_,程序发生了什么?

  settimeout(fun,0)

当setTimeout时间为0时 会立即进入任务队列 插入到任务队列最前面 不是立即执行 要等同步代码执行完 才会执行

5 对Promise的理解

Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。

状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

注意:在构造Promise的时候,构造函数内部的代码是立即执行的

6. promise的三个状态

pending,表示进行中

fulfilled也是resolved,表示成功

rejected,表示失败

7.promise的状态变化

只能从pending->resolved或者pending->rejected promise的状态改变后就不会再次改变会一直保持这个状态,而且promise不会受到外界的影响,只会受到异步操作的结果的影响

8.promise的基本用法

new Promise((resolve,reject)=>{
    if(true){
        resolve(value);
    }else{
        reject(err);
    }
}).then(
    value=>{};
    reason=>{};
).catch(

)

新建一个promise构造函数,函数接受一个函数作为参数,同时该函数有两个参数,reject和resolve 执行异步操作

成功执行resolve(),状态变成resolved状态

失败执行reject(),状态变成rejected状态

无论成功还是失败都可以通过.then()指定成功和失败的回调函数,catch只能调用失败的回调函数

promise是同步执行,then是异步执行

9.promise的API

Promise构造函数:Promise(excutor) {}
(1)executor 函数:执行器 (resolve, reject) => {}
(2)resolve 函数:内部定义成功时调用的函数 value => {}
(3)reject 函数:内部定义失败时调用的函数 reason => {}
说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行
Promise.prototype.then方法:(onResolved, onRejected) => {}
(1)onResolved 函数:成功的回调函数 (value) => {}
(2)onRejected 函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功value的成功回调和用于失败reason的失败回调,返回的是一个新的promise对象
Promise.prototype.catch方法:(onRejected) => {}
(1)onRejected 函数:失败的回调函数 (reason) => {}
Promise resolve方法:(value) => {}
(1)value: 成功的数据或Promise对像
说明:返回一个成功/失败的promise对象,取决于传入参数,参数为promise,就由内部promise的结果决定;参数为非promise,则返回状态为成功,结果为参数
Promise.reject方法:(reason) => {}
(1)reason: 失败的原因
说明:返回一个失败的promise对象
Promise.all方法:(promise) => {]
(1)promise:包含n个promise的数组
说明:返回一个新的promise,只有所有的promise都成功才成功,,结果是所有promise成功的值组成的数组;只要有一个失败就直接失败,返回Error
Promise.race方法:(promise) => {}
(1)promise包含n个promise的数组
说明:返回一个新的promise,第一个决定的promise的结果状态就是最终的结果状态

10. await 到底在等啥?

await 表达式的运算结果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

11.async/await的优势

  1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
  2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
  3. 调试时的阅读性, 也相对更友好

12. async/await 如何捕获异常

async function fn(){
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

10月16日

1.移动端如何实现上拉加载,下拉刷新?

 1.上拉加载的本质是页面触底,或者快要触底时的动作:
判断页面触底我们需要先了解一下下面几个属性
scrollTop:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值
clientHeight:它是一个定值,表示屏幕可视区域的高度;
scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)
综上我们得出一个触底公式:scrollTop + clientHeight >= scrollHeight
2.下拉刷新的本质是页面本身置于顶部时,用户下拉时需要触发的动作:
关于下拉刷新的原生实现,主要分成三步:
(1)监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
(2)监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
(3)监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置

2.浏览器怎么进行垃圾回收?

浏览器垃圾回收机制根据数据的存储方式分为栈垃圾回收和堆垃圾回收。

栈垃圾回收的方式非常简便,当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文,遵循先进后出的原则。

堆垃圾回收,当函数直接结束,栈空间处理完成了,但是堆空间的数据虽然没有被引用,但是还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。

为了使垃圾回收达到更好的效果,根据对象的生命周期不一样,使用不同的垃圾回收的算法。在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。新生区中使用Scavenge算法,老生区中使用标记-清除算法和标记-整理算法。

3.哪些情况会导致内存泄漏

以下四种情况会造成内存的泄漏:

  • 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
  • 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
  • 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
  • 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。

4.浏览器中不同类型变量的内存都是何时释放?

Javascritp 中类型:值类型,引用类型。

  • 引用类型

    • 在没有引用之后,通过 V8 自动回收。
  • 值类型

    • 如果处于闭包的情况下,要等闭包没有引用才会被 V8 回收。
    • 非闭包的情况下,等待 V8 的新生代切换的时候回收。

5.哪些情况会导致内存泄露?如何避免?

内存泄露是指你「用不到」(访问不到)的变量,依然占据着内存空间,不能被再次利用起来。 以 Vue 为例,通常有这些情况:

  1. 监听在 window/body 等事件没有解绑
  2. 绑在 EventBus 的事件没有解绑
  3. Vuex 的 $store,watch 了之后没有 unwatch
  4. 使用第三方库创建,没有调用正确的销毁函数

解决办法:beforeDestroy 中及时销毁

  1. 绑定了 DOM/BOM 对象中的事件 addEventListener ,removeEventListener。
  2. 观察者模式 $on$off处理。
  3. 如果组件中使用了定时器,应销毁处理。
  4. 如果在 mounted/created 钩子中使用了第三方库初始化,对应的销毁。
  5. 使用弱引用 weakMap、weakSet。

6.闭包会导致内存泄露吗?

不会。内存泄露是指你「用不到」(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。 闭包里面的变量就是我们需要的变量,不能说是内存泄露。

7:weakMap weakSet 和 Map Set 有什么区别?

在 ES6 中为我们新增了两个数据结构 WeakMap、WeakSet,就是为了解决内存泄漏的问题。

它的键名所引用的对象都是弱引用,就是垃圾回收机制遍历的时候不考虑该引用。

只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。