深圳某云产品公司

1. 描述一下Promise

这道题我会先大概介绍一下Promise:

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then方法和catch、finally方法会返回一个新的Promise所以可以允许我们链式调用,解决了传统的回调地狱问题

再说一下then以及catch方法:

  • Promise的状态一经改变就不能再改变
  • .then和.catch都会返回一个新的Promise
  • catch不管被连接到哪里,都能捕获上层未捕捉过的错误
  • 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)。
  • Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值
  • .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获
  • .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
  • .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
  • .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法
  • .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数

另外也可以说一下finally方法:

  • .finally()方法不管Promise对象最后的状态如何都会执行
  • .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
  • 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。

最后可以说一下all以及race方法:

  • Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
  • Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
  • allrace传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。

2. Promise.all中如果有一个抛出异常了会如何处理

all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行

3. Promise为什么能链式调用

由于它的then方法和catchfinally方法会返回一个新的Promise所以可以允许我们链式调用

4. 描述一下EventLoop的执行过程

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

5. docoment,window,html,body的层级关系

    window > document > html > body
  • window是BOM的核心对象,它一方面用来获取或设置浏览器的属性和行为,另一方面作为一个全局对象
  • document对象是一个跟文档相关的对象,拥有一些操作文档内容的功能。但是地位没有window高
  • html元素对象和document元素对象是属于html文档的DOM对象,可以认为就是html源代码中那些标签所化成的对象。他们跟div、select什么对象没有根本区别。

(我是这样记的,整个浏览器中最大的肯定就是窗口window了,那么进来的我不管你是啥,就算你是document也得给我盘着)

6. addEventListener函数的第三个参数

  • 第三个参数涉及到冒泡和捕获,是true时为捕获,是false则为冒泡。
  • 或者是一个对象{passive: true},针对的是Safari浏览器,禁止/开启使用滚动的时候要用到。

7. 有写过原生的自定义事件吗

  • 使用Event
  • 使用customEvent (可以传参数)
  • 使用document.createEvent('CustomEvent')initEvent()

创建自定义事件

原生自定义事件有三种写法:

  1. 使用Event
    let myEvent = new Event('event_name');
  1. 使用customEvent (可以传参数)
    let myEvent = new CustomEvent('event_name', {
    	detail: {
    		// 将需要传递的参数放到这里
    		// 可以在监听的回调函数中获取到:event.detail
    	}
    })
  1. 使用document.createEvent('CustomEvent')initEvent()
    let myEvent = document.createEvent('CustomEvent');// 注意这里是为'CustomEvent'
    myEvent.initEvent(
    	// 1. event_name: 事件名称
    	// 2. canBubble: 是否冒泡
    	// 3. cancelable: 是否可以取消默认行为
    )
  • createEvent:创建一个事件
  • initEvent:初始化一个事件

可以看到,initEvent可以指定3个参数。

事件的监听

自定义事件的监听其实和普通事件的一样,使用addEventListener来监听:

    button.addEventListener('event_name', function (e) {})

事件的触发

触发自定义事件使用dispatchEvent(myEvent)

注意⚠️,这里的参数是要自定义事件的对象(也就是myEvent),而不是自定义事件的名称('myEvent')

案例

来看个案例吧:

    // 1.
    // let myEvent = new Event('myEvent');
    // 2.
    // let myEvent = new CustomEvent('myEvent', {
    //   detail: {
    //     name: 'lindaidai'
    //   }
    // })
    // 3.
    let myEvent = document.createEvent('CustomEvent');
    myEvent.initEvent('myEvent', true, true)
    
    let btn = document.getElementsByTagName('button')[0]
    btn.addEventListener('myEvent', function (e) {
      console.log(e)
      console.log(e.detail)
    })
    setTimeout(() => {
      btn.dispatchEvent(myEvent)
    }, 2000)

8. 冒泡和捕获的具体过程

冒泡指的是:当给某个目标元素绑定了事件之后,这个事件会依次在它的父级元素中被触发(当然前提是这个父级元素也有这个同名称的事件,比如子元素和父元素都绑定了click事件就触发父元素的click)。

捕获则是从上层向下层传递,与冒泡相反。(非常好记,你就想想水底有一个泡泡从下面往上传的,所以是冒泡)

来看看这个例子:

    <!-- 会依次执行 button li ul -->
    <ul onclick="alert('ul')">
      <li onclick="alert('li')">
        <button onclick="alert('button')">点击</button>
      </li>
    </ul>
    <script>
      window.addEventListener('click', function (e) {
        alert('window')
      })
      document.addEventListener('click', function (e) {
        alert('document')
      })
    </script>
  • 冒泡结果:button > li > ul > document > window
  • 捕获结果:window > document > ul > li > button

9. 所有的事件都有冒泡吗?

并不是所有的事件都有冒泡的,例如以下事件就没有:

  • onblur
  • onfocus
  • onmouseenter
  • onmouseleave

11. 手写new

    function myNew (fn, ...args) {
      let instance = Object.create(fn.prototype);
      let result = fn.call(instance, ...args)
      return typeof result === 'object' ? result : instance;
    }

12. typeof和instanceof的区别

  • typeof表示是对某个变量类型的检测,基本数据类型除了null都能正常的显示为对应的类型,引用类型除了函数会显示为'function',其它都显示为object。
  • 而instanceof它主要是用于检测某个构造函数的原型对象在不在某个对象的原型链上。

13. typeof为什么对null错误的显示

这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object 。

14. 详细说下instanceof

instanceof它主要是用于检测某个构造函数的原型对象在不在某个对象的原型链上。

    function myInstanceof (left, right) {
      let proto = Object.getPrototypeOf(left);
      while (true) {
        if (proto === null) return false;
        if (proto === right.prototype) return true;
        proto = Object.getPrototypeOf(proto)
      }
    }

15. 一句话描述一下this

对于函数而言指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用;对于全局来说,this指向window。

16. 函数内的this是在什么时候确定的?

函数调用时,指向最后调用的那个对象

17. webpack中的loader和plugin有什么区别

  • loader它是一个转换器,只专注于转换文件这一个领域,完成压缩、打包、语言编译,它仅仅是为了打包。并且运行在打包之前。
  • 而plugin是一个扩展器,它丰富了webpack本身,为其进行一些其它功能的扩展。它不局限于打包,资源的加载,还有其它的功能。所以它是在整个编译周期都起作用。

18. HTTP和TCP的不同

  • HTTP的责任是去定义数据,在两台计算机相互传递信息时,HTTP规定了每段数据以什么形式表达才是能够被另外一台计算机理解。
  • 而TCP所要规定的是数据应该怎么传输才能稳定且高效的传递与计算机之间。

(还可以再扩展)

19. TCP和UDP的区别

  • TCP是一个面向连接的、可靠的、基于字节流的传输层协议。
  • UDP是一个面向无连接的传输层协议。
  • TCP为什么可靠,是因为它有三次握手来保证双方都有接受和发送数据的能力。

字节流服务:将大块数据分割为以报文段为单位的数据包进行管理

20. 介绍一下虚拟DOM

  • 虚拟DOM本质就是用一个原生的JavaScript对象去描述一个DOM节点。是对真实DOM的一层抽象。
  • 由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题,因此我们需要这一层抽象,在patch过程中尽可能地一次性将差异更新到DOM中,这样保证了DOM不会出现性能很差的情况。
  • 另外还有很重要的一点,也是它的设计初衷,为了更好的跨平tai,比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM,因为Virtual DOM本身是JavaScript对象。
  • Vue2.x中的虚拟DOM主要是借鉴了snabbdom.js,Vue3中借鉴inferno.js算法进行优化
Last Updated:
Contributors: leeguooooo