前端高频面试题---js篇(五)

73 阅读7分钟

1. 简述一下Promise以及其底层实现原理

Promise 是 JavaScript 中的一种异步编程的方式,它表示一个最终可能完成(成功解决)或失败(被拒绝)的异步操作,以及其结果值。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦 Promise 进入解决或拒绝的状态,就不会再改变状态。

JavaScript 的 Promise 底层实现涉及到了很多复杂的概念和机制,包括事件循环(Event Loop)、微任务队列(Microtask Queue)和异步队列(Async Queue)。

  1. 事件循环(Event Loop):JavaScript 的执行环境是单线程的,为了能处理多个任务,它采用了事件循环模型。事件循环中的每一个循环都由一个事件队列构成,JavaScript 先处理同步任务,然后检查异步任务。当一个异步任务完成时,它会被添加到微任务队列中。
  2. 微任务队列(Microtask Queue):微任务包括 Promise 的回调、process.nextTick、MutationObserver 等。微任务的优先级高于宏任务(即事件循环中的下一个任务)。每次宏任务执行完后,会立刻执行微任务队列里的所有任务。
  3. 异步队列(Async Queue):当一个异步任务完成时,它会被添加到异步队列中。在事件循环的下一个循环中,JavaScript 会检查异步队列,并执行相应的回调。

Promise 的实现原理就是基于上述的底层机制。当你在 Promise 上调用 then 或 catch 方法时,返回的其实是一个新的 Promise 对象,它的解决值就是原始 Promise 的解决值,而它的回调函数会被添加到微任务队列中。因此,当原始 Promise 状态改变时(无论是解决还是拒绝),所有的 then 和 catch 回调都会立即执行。

2. javascript中的原型链是什么

在JavaScript中,原型链是一种实现对象继承和共享属性的机制。每个对象都有一个内部属性[[Prototype]],它指向该对象的原型。而这个原型对象本身也可能有它自己的原型,这样就形成了一条原型链。

  1. 原型链的顶端:JavaScript中的原型链没有明确的顶端,或者说它可以是任何对象。一个对象的原型可以是另一个对象,而那个对象的原型也可以是另一个对象,如此类推,直到你选择一个对象作为终点。这个终点对象通常是一个空对象(如 {}),因为在大多数情况下,我们并不需要知道这个终点对象的原型。
  2. Object 的原型:在JavaScript中,Object的原型是Object.prototype
  3. Object的原型的原型Object.prototype的原型是null。在JavaScript中,null是一个表示没有对象或没有值的特殊值。因此,Object.prototype的原型是null

以下是一个简单的示例来解释这个问题:

console.log(Object.prototype);  // 输出: Object {}
console.log(Object.prototype.prototype);  // 输出: null

这个例子中,我们首先打印出Object.prototype,它是一个对象。然后我们打印出Object.prototype的原型,结果是null

3. js中Localstorage、sessionStorage、 cookie的区别

LocalStorage、sessionStorage和Cookie在web开发中有一些关键的区别,包括它们的存储大小、数据有效期、作用域、事件通知机制以及API接口的易用性。

  1. 存储大小:LocalStorage和sessionStorage虽然也有存储大小的限制,但比Cookie大得多,可以达到5M或更大。
  2. 数据有效期:LocalStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;Cookie只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭;而sessionStorage仅在当前浏览器窗口关闭之前有效。
  3. 作用域:sessionStorage在不同的浏览器窗口中不共享,即使是同一个页面;Localstorage在所有同源窗口中都是共享的;Cookie也是在所有同源窗口中都是共享的。
  4. 事件通知机制:Web Storage支持事件通知机制,可以将数据更新的通知发送给监听者。
  5. API接口使用:Web Storage的API接口使用更方便。

总的来说,虽然LocalStorage、sessionStorage和Cookie都是web存储数据的方式,但它们在存储大小、数据有效期、作用域、事件通知机制以及API接口的易用性方面有所不同。根据实际应用的需求,可以选择最适合的存储方式。

4. JavaScript中的事件循环

JavaScript的事件循环是一种处理异步编程的方法,它允许程序在执行过程中暂停和恢复,以处理异步事件。

在JavaScript中,事件循环由以下四个主要部分组成:

  1. 调用栈(Call Stack):当JavaScript代码开始执行时,首先会创建一个调用栈。调用栈中的每一项都表示一个正在执行的函数。如果一个函数调用了另一个函数,那么后一个函数就会被推入调用栈。
  2. 宏任务队列(Macro-task Queue):JavaScript的执行环境会在每个事件循环开始时,将宏任务(如setTimeout、setInterval、I/O、UI渲染、script等)放入宏任务队列中。这些宏任务会按照它们被添加到队列中的顺序执行。
  3. 微任务队列(Micro-task Queue):当JavaScript代码执行时,可能会产生微任务(如Promise.then/catch/finally、MutationObserver等)。这些微任务会被添加到微任务队列中。一旦调用栈为空,微任务队列中的所有微任务都会被立即执行。
  4. 事件循环:当宏任务队列不为空时,事件循环会从宏任务队列中取出一个宏任务放到调用栈中执行。当宏任务执行完毕后,如果微任务队列不为空,那么所有的微任务都会被立即执行。然后,事件循环会再次回到宏任务队列,取出下一个宏任务放到调用栈中执行。这个过程会不断循环。

总的来说,JavaScript通过这种方式实现了异步操作的处理。它允许一些操作(如异步回调)在未来的某个时间点执行,而不是立即执行。这种机制使得JavaScript能够高效地处理异步操作,并且能够处理大量的并发操作。

5. 异步加载JS的方法

异步加载JS的方法主要有以下几种:

  1. 使用async属性:在HTML中,你可以通过在<script>标签中添加async属性来异步加载JS。被async属性修饰的脚本会独立于其他脚本执行。当使用async属性时,脚本何时开始和结束执行是不能预知的,但是可以确定的是它会在整个HTML文档解析的过程中执行。
<script async src="script.js"></script>
  1. 使用defer属性:与async属性不同,defer属性表示脚本会在整个HTML文档解析完成之后执行。也就是说,使用defer属性的脚本会等待所有使用async属性的脚本执行完毕后才开始执行。
<script defer src="script.js"></script>
  1. 使用动态创建<script>元素:你也可以通过JavaScript动态创建一个<script>元素并设置其src属性来异步加载JS。这种方法可以让你更灵活地控制脚本加载和执行的时间。
var script = document.createElement('script');
script.src = 'script.js';
document.head.appendChild(script);
  1. 使用模块化加载器如Webpack、Rollup等:如果你需要加载的JS文件很多,或者需要更高级的代码拆分和按需加载功能,可以使用模块化加载器。这些加载器可以将你的JS代码拆分成多个小文件,并按需异步加载它们。这样可以提高页面加载速度,改善用户体验。
  2. 使用动态导入(dynamic imports):这是一种新的JavaScript语法,允许你在运行时动态导入一个模块。这对于实现代码拆分和按需加载非常有用。使用这种语法,你可以将一个模块的加载和执行推迟到需要它的代码执行时才进行。