Skip to content

一、前端基础面试题

1. Ajax、Fetch、Axios 的区别

  • 三者都用于网络请求,但是不同维度

  • Ajax (Asynchronous Javascript and XML),一种技术统称

    • 延伸:用 XMLHttpRequest 实现 Ajax
      js
      function ajax (url, successFn) {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url, false)
        xhr.onreadystatechange = function () {
          // 这里的函数异步执行
          if (xhr.readyState === 4) {
            if (xhr.status === 200) {
              successFn(xhr.responseText)
            }
          }
        }
        xhr.send(null)
      }
  • Fetch 就是一个具体的 API

    • 浏览器原生 API,用于网络请求
    • 和 XMLHttpRequest 一个级别
    • Fetch 语法更加简洁、易用、支持 Promise
  • Axios, 第三方库 https://axios-http.com/

    • 最常用的网络请求 lib
    • 内部可用 XMLHttpRequest 和 Fetch 来实现
  • 重点:lib 和 API 区别

    • 第三库可以用API来实现

2. 节流 和 防抖

  • 问题
    • 两者有什么区别
    • 分别用于什么场景

节流:

  • 节流,顾名思义节省交互沟通。流,不一定指流量。"别急,一个一个来,按时间节奏来,插队者无效"
  • 场景:drag 或 scroll 期间触发某个回调,要设置一个时间间隔。
  • 代码演示:
    js
    /**
     * 节流
     */
    function throttle(fn, delay = 100) {
      let timer = 0;
       return function () {
         if (timer) return
    
         timer = setTimeout(() => {
           fn.apply(this, arguments);
           timer = 0
         }, delay)
       }
    }

防抖:

  • 防抖,顾名思义防止抖动,"你先抖动着,啥时候停了,再执行下一步"
  • 场景:一个搜索输入框,等输入停止之后,再触发搜索。
  • 代码演示:
    js
    /**
     * 防抖
     */
    function debounce(fn, delay = 200) {
      let timer = 0;
      return function () {
        if (timer) clearTimeout(timer)
    
        timer = setTimeout(() => {
          fn.apply(this,  arguments);
          timer = 0;
        }, delay)
      }
    }

总结:

  • 节流:限制执行频率,有节奏的执行。
  • 防抖:限制执行次数,多次密集的触发只执行一次。
  • 节流关注"过程",防抖关注"结果"
  • 实际工作可使用https://loadash.com

3. px % em rem vw/vh 有什么区别

  • px 和 %

    • px 基本单位。绝对单位(其他的都是相对单位)
    • % 相对于父元素的宽度比例
  • em 和 rem

    • em 相对于当前元素的 font-size
    • rem 相对于根节点的 font-size
  • vw 和 vh

    • vw 屏幕宽度的 1%
    • vh 屏幕高度的 1%
    • vmin 两者的最小值, vmax 两者的最大值
  • 关注下:各自的使用场景

4. 箭头函数的缺点,哪里不能用箭头函数?

  • 箭头函数的缺点:

    • 没有 arguments
    • 无法通过 apply call bind 改变 this, 箭头函数 this 指向的父作用域
  • 箭头函数不适用场景:

    • 对象方法

      js
      const obj = {
        name: '张三',
        getName: () => {
          return this.name
        }
      }
      console.log(obj.getName())
    • 原型方法

      js
      const obj = {
        name: '张三',
      }
      obj.__proto__.getName = () => {
        return this.name;
      }
      console.log(obj.getName())
    • 构造函数

      js
      const Foo = (name, age) => {
        this.name = name;
        this.age = age;
      }
      const f = new Foo('张三', 20) // 报错 Foo is not a constructor
    • 动态上下文中的回调函数

      js
      const btn = document.getElementById('btn');
      btn.addEventListener('click', () => {
        this.innerHTML = 'clicked'
      })
    • vue 生命周期和 method

      vue
      
      <template>
      </template>
      
      <script>
      export default {
        data() { return { name: "张三" } },
        methods: {
          getName: () => {
            // 报错 Cannot read properties of undefined (reading 'name')
            return this.name
          }
          // getName() {
          //   return this.name // 正常
          // }
        },
        mounted: () => {
          // 报错 Cannot read properties of undefined (reading 'name')
          console.log('msg', this.name)
        },
         // mounted() {
         //  console.log('msg', this.name) // 正常
         // }
      }
      }
      </script>
      
      <style scoped lang='less'>
      </style>
      • 注意:Vue 组件本质上是一个 JS 对象, React 组件(非 Hooks) 它本质上是一个 ES6 class
        js
        class Foo {
          constructor(name, city) {
            this.name = name;
            this.city = city;
          }
        
          getName = () => {
            return this.name;
          }
        }
        
        const f = new Foo('张三', '上海');
        console.log(f.getName())

5. 请描述 TCP 三次握手 和 四次挥手

TCP三次握手和四次挥手.png

  • 建立 TCP 连接

    • 先建立连接(确保双方都有收发消息的能力)
    • 再传输内容 (如发送给一个 get 请求)
    • 网络连接是 TCP 协议,传输内容是 HTTP 协议
  • 建立连接【三次握手】

    • Client 发包,Server 接受。Server:有 Client 要找我。
    • Server 发包,Client 接受。Client:Server 已经收到信息了。
    • Client 发包,Server 接受。Server:Client 要准备发送了。
  • 关闭连接【四次挥手】

    • Client 发包,Server 接受。Server:Client 已请求结束。
    • Server 发包,Client 接受。Client:Server 已收到,我等待它关闭。
    • Server 发包,Client 接受。Client:Server 此时可以关闭连接了。
    • Client 发包,Server 接受。Server:可以关闭了(然后关闭连接)。
  • 重点

    • 握手是建立连接,挥手是告别。

6. for in 和 for of 有什么区别

  • key 和 value
    • for in 遍历得到 key
    • for of 遍历得到 value
    • 适用于不同的数据类型
      • 遍历对象:for in 可以,for of 不可以
      • 遍历 Map Set:for of 可以, for in 不可以
      • 遍历 generator: for of 可以, for in 不可以
    • 可枚举 和 可迭代
      • for in 用于可枚举数据,如对象、数组、字符串
        • Object.getOwnPropertyDescriptors(obj)
          • enumerable: true
      • for of 用于可迭代数据,如数组、字符串、Map、Set
        • arr[Symbol.iterator]

7. for await of 有什么作用

  • for await of 用于遍历多个 Promise 也就是 Promise.all() 的代替品

8. offsetHeight scrollHeight clientHeight 区别

  • 盒子模型

    • width
    • height
    • padding
    • border
    • margin
    • box-sizing
  • offsetHeight offsetWidth: border + padding + content

  • clientHeight clientWidth: padding + content

  • scrollHeight scrollWidth: padding + 实际内容尺寸(不一定是content,以实际为主)

  • scrollTop scrollLeft 超出部分的距离

9. HTMLCollection 和 NodeList 区别

node.png

  • Node 和 Element

    • DOM 是一棵树,所有节点都是 Node
    • Node 是 Element 的基类
    • Element 是其他 HTML 元素的基类,如 HTMLDivElement
  • HTMLCollection 是 Element 的集合

  • NodeList 是 Node 集合

  • 代码演示

    js
    class Node {}
    
    // document
    class Document extends Node {}
    class DocumentFragment extends Node {}
    
    // 文本和注释
    class CharacterData extends Node {}
    class Comment extends CharacterData {}
    class Text extends CharacterData {}
    
    // elem
    class Element extends Node {}
    class HTMLElement extends Element {}
    class HTMLDivElement extends HTMLElement {}
    class HTMLInputElement extends HTMLElement {}
  • 重点

    • 获取 Node 和 Element 的返回结果可能不一样
    • 如 elem.childNodes 和 elem.children 不一样
    • 前者会包含 Text 和 Comment 节点,后者不会
  • 扩展

    • HTMLCollection 和 NodeList 都不是数组,而是 "类数组"
    • 将类数组转化成数组
      js
      const arr1 = Array.from(list);
      const arr2 = Array.prototype.slice.call(list);
      const  arr3 = [...list]

10. Vue 中 computed 和 watch 区别

  • 两者用途不同
    • computed 用于计算产生新的数据
    • watch 用于监听现有数据
  • 缓存
    • computed 有缓存
    • method 没有缓存

11. Vue 组件通讯方式有几种? 尽量说全面

  • 通信方式

    • props 和 $emit
    • 自定义事件-事件总线
      • 自定义事件 在mounted中 on 之后,一定需要在 beforeUnmount中 off 这个事件
    • $attrs
    • $parent
    • $refs
    • provide/inject
    • Vuex
  • 不同场景

    • 父子组件
    • 上下级组件(跨多级)通讯
    • 全局组件

12. Vuex mutation action 区别

  • mutation:原子操作,必须同步代码
  • action: 可包含多个 mutation; 可包含异步代码;

13. JS 严格模式有什么特点

  • JS 严格模式细节要求很多,只掌握重点即可
    • 开启严格模式
    • 特点
      • 全局变量必须先声明
        js
        'use strict'
        n = 100  // ReferenceError: n is not defined
      • 禁止使用 with
        text
        'use strict'
        var obj = { x: 100 }
        with (obj) {
          // Uncaught SyntaxError: Strict mode code may not include a with statement
          console.log(x)
        }
      • 创建 eval 作用域
        js
        'use strict'
        var x = 10;
        eval('var x = 20; console.log(x)') 
        console.log(x)
      • 禁止 this 指向 window
        js
        'use strict'
        function fn () {
          console.log('this', this) //undefined
        }
        fn()
      • 函数参数不能重名
        text
        'use strict'
        // Uncaught SyntaxError: Duplicate parameter name not allowed in this context
        function fn (x, x, y) {
          return
        }

14. HTTP 跨域请求时为何发送 options 请求

  • 浏览器同源策略

  • 同源策略一般限制Ajax 网络请求,不能跨域请求 server

  • 不会限制 <link> <img> <script> <iframe> 加载第三方资源

  • 处理方式

    • JSONP jsonp

    • CORS cors.png

  • 多余的 options 请求 options.png

    • options 请求,是跨域请求之前的预检查
    • 浏览器自行发起的,无需我们干预
    • 不会影响实际的功能

15. JS 内存泄漏如何检测?场景有哪些?

  • 什么是垃圾回收

    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>gc</title>
    </head>
    <body>
        <p> Garbage collection </p>
        <script>
            function fn1() {
              const a = 'aa'
              console.log(a)
    
              const obj = { x: 100}
              console.log(obj)
            }
            // 执行完该函数,函数中的变量会被回收
            fn1()
    
            function fn2() {
              const obj = { x: 100 }
              // 回收不了,这是用户期望的
              window.obj = obj;
            }
            fn2()
    
            function getDataFns() {
              const data = {}; // 闭包的数据永远都是常驻内存的
              return {
                get(key) {
                  return data[key]
                },
                set(key, value) {
                  data[key] = value
                }
              }
            }
            const { get, set } = getDataFns();
            set('x', 100);
            get('x');
    
            // 对象被 a 引用
            let a = { x: 100 };
            let a1 = a;
            a = 10;
            a1 = null;
    
            // 引用计数缺陷:循环引用
            function fn3() {
              const obj1 = {};
              const obj2 = {};
              obj1.a = obj2;
              obj2.a = obj1;
            }
            fn3()
    
            // JS 根 window 遍历属性
        </script>
    </body>
    </html>
  • JS 垃圾回收的算法

    • 引用计数(之前)
      js
       // 对象被 a 引用
      let a = { x: 100 };
      let a1 = a;
      a = 10;
      a1 = null;
    • 标记清除(现代)
      js
      // JS 根 window 遍历属性
  • 闭包是内存泄漏吗?

    • 闭包严格意义上不算内存泄漏
  • 如何检测泄漏

    • 使用 Chrome devTools 的 Performance 和 Memory 工具检测 js 内存 memory.png
    • 扩展:wangEditor 检测内存泄漏
    • 内存泄漏的场景(Vue 为例)
      • 被全局变量、函数引用,组件销毁时未清除

        vue
        <template>
        </template>
        
        <script>
        export default {
          name: 'MemoryLeak',
          data() {
            return {
              arr: [10, 20, 30]
            }
          },
          mounted() {
            window.arr = this.arr;
            window.printArr = () => {
              console.log(this.arr)
            }
        
          },
          beforeUnmount() {
            window.arr = null;
            window.printArr = null
          },
          methods: {
        
          }
        }
        </script>
        
        <style scoped lang='less'>
        </style>
      • 被全局事件、定时器引用,组件销毁时未清除

        vue
        <template>
        </template>
        
        <script>
        export default {
          name: 'MemoryLeak',
          data() {
            return {
              arr: [10, 20, 30], // 数组、对象
              intervalId: 0
            }
          },
          mounted() {
            this.intervalId = setInterval(() => {
              console.log(this.arr)
            }, 100)
          },
          beforeUnmount() {
            if (this.intervalId) {
              clearInterval(this.intervalId)
            }
          }
        }
        </script>
        
        <style scoped lang='less'>
        </style>
        vue
        <template>
        </template>
        
        <script>
        export default {
          name: 'MemoryLeak',
          data() {
            return {
              arr: [10, 20, 30], // 数组、对象
            }
          },
          mounted() {
            window.addEventListener('resize',  this.printArr)
          },
          beforeUnmount() {
            window.removeEventListener('resize', this.printArr)
          },
          methods: {
            printArr() {
              console.log(this.arr)
            }
          }
        }
        </script>
        
        <style scoped lang='less'>
        </style>
      • 被自定义事件引用,组件销毁时未清除

        • 使用 eventBus 的时候,on 绑定事件,需要在组件销毁的生命周期函数中使用 off 进行解绑。
    • 扩展 WeakMap WeakSet
      • WeakMap 的 key 只能是引用类型

16. 浏览器和 node.js 的事件循环有什么区别?

  • 单线程和异步
    • JS 是单线程的(无论在浏览器还是nodejs)。
    • 浏览器中 JS 执行和 DOM 渲染公用一个线程。
    • 异步
      • 宏任务和微任务
        • 宏任务:如 setTimeout setInterval 网络请求。
        • 微任务:如 promise async/await。
        • 微任务在下一轮 DOM 渲染之前执行,宏任务在之后执行。

17. 伪类和伪元素的区别

  • 伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时, 我们可以通过:hover来描述这个元素的状态。虽然它和普通的css类相似,可以为已有的元素添加样式,但是它只有处于dom树无法描 述的状态下才能为元素添加样式,所以将其称为伪类。

  • 伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before来在一个元素前增加一些文本,并为这些文本 添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。

18. typeof能判断哪些类型

答案: 所有值类型、函数、引用类型
解析:

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)
    js
      // 判断所有值类型
      let a;                  typeof a    // 'undefined'
      const str = 'abc';      typeof str  // 'string'
      const n = 100;          typeof n    // 'number'
      const b = true;         typeof b    // 'boolean'
      const s = Symbol('s');  typeof s    // 'symbol'
      
      // 能判断函数
      typeof console.log    // 'function'
      typeof function () {} // 'function'
    
      // 能识别引用类型(不能再继续识别)
      typeof null;       // 'object'
      typeof ['a', 'b']  // 'object'
      typeof { x: 100 }  // 'object'

19. 变量计算-类型转换

  • 字符串拼接
    js
      const a = 100 + 10;    // 110
      const b = 100 + '10';  // '10010'
      const c = true + '10'; // 'true10'
  • ==
    js
      100 == '100'       // true
      0 == ''            // true
      0 == false         // true
      false == ''        // true
      null == undefined  // true
    
      // 除了 == null 之外,其他都一律用 === 例如:
      const obj = { x: 100 };
      if (obj.a == null) {}
      // 相当于
      // if (obj.a === null || obj.a === undefined) {}
  • if语句和逻辑运算
    • truly变量:!!a === true 的变量
    • falsely变量:!!a === false 的变量
    js
        // 以下是 `falsely`变量。除此之外都是`truly`变量
        !!0 === false
        !!NaN === false
        !!'' === false
        !!null === false
        !!undefined === false
        !!false === false
    js
       // if 语句
       // `truly`变量
       const a = true;
       if (a) {}
    
       const b = 100;
       if (b) {}
    
       // falsely变量
       const c = ''
       if (c){}
    
       const d = null
       if (d) {}
    
       let e
       if (e){}
    js
       // 逻辑判断
       console.log(10 && 0);
       console.log('' || 'acb')
       console.log(!window.abc)

20. instanceof

  • 类型判断

    js
        xiaoming instanceof Student   // true
        xiaoming instanceof People    // true
        xiaoming instanceof Object    // true
                
        console.log([] instanceof Array)   // true
        console.log([] instanceof Object)  // true
        
        console.log({} instanceof Object)  // true
  • 原型

    js
       // class 实际上是函数,可见是语法糖
       typeof People      // 'function'
       typeof Student     // 'function'
    
       // 隐式原型和显示原型
       console.log( xiaoming.__proto__)
       console.log( Student.prototype)
       console.log( xiaoming.__proto__ === Student.prototype)

    proto

  • 原型关系

    • 每个class都有显示原型prototype
    • 每个实例都有隐式原型__proto__
    • 实例的__proto__指向对应classprototype
  • 基于原型的执行规则

    • 获取属性 xiaoming.name 或执行方法xiaoming.sayHi()
    • 先在自身属性和方法寻找
    • 如果找不到则自动去__proto__中查找
  • 原型链

    js
       console.log( Student.prototype.__proto__ )
       console.log( People.prototype )
       console.log( People.prototype === Student.prototype.__proto__)

    protoproto

  • instanceof 就是顺着原型链往上找,直到找到判别的真身,返回true 如果找不到需要判别的真身,则返回false

  • 重要提示

    • classES6语法规范,由ECMA委员发布
    • ECMA只规定语法规则,即代码的书写规范,不规定如何实现
    • 以上实现方式都是V8引擎的实现方式,也是主流的
  • 如何准确判断一个变量是数组

    js
       a instanceof Array

21. class的原型本质,怎么理解

  • 原型和原型链的图示
  • 属性和方法的执行规则

22. 手写一个简易的jQuery,考虑插件和拓展性

js
  class jQuery {
      constructor(selector) {
          const result = document.querySelectorAll(selector)
          const length = result.length
          for (let i = 0; i < length; i++) {
              this[i] = result[i]
          }
          this.length = length
          this.selector = selector
      }
      get(index) {
          return this[index]
      }
      each(fn) {
          for (let i = 0; i < this.length; i++) {
              const elem = this[i]
              fn(elem)
          }
      }
      on(type, fn) {
          return this.each(elem => {
              elem.addEventListener(type, fn, false)
          })
      }
      // 扩展很多 DOM API
  }
  
  // 插件
  jQuery.prototype.dialog = function (info) {
      alert(info)
  }
  
  // “造轮子”
  class myJQuery extends jQuery {
      constructor(selector) {
          super(selector)
      }
      // 扩展自己的方法
      addClass(className) {
  
      }
      style(data) {
          
      }
  }
  
  // const $p = new jQuery('p')
  // $p.get(1)
  // $p.each((elem) => console.log(elem.nodeName))
  // $p.on('click', () => alert('clicked'))

Released under the MIT License.