2020前端面试题汇总(2020.05-2020.10)

hewq
2020 年 5 月份选择了裸辞,这是一个不理智的决定,面试之后才发现自己有太多的不足,再加上疫情之后的经济环境,历经 5 个月终于才找到了一份工作。现在把这 5 月所有的面试题记录下来做一次总结,在此感谢在这 5 个月里关心我和帮我推荐工作的人们。

github 地址:https://github.com/hewq/blog/...

web 性能优化

  • 资源优化

    • CDN
      减少网络延迟,加快加载速度。
    • 预加载
      预先加载资源,提高用户体验,一般在 loading 页上处理;
      preloadjs 可做预加载,phaserjs 的 preload 方法可做预加载。
    • 懒加载
      按需加载,节省资源,提高用户体验;
      vue 按需加载路由 /webpackChunkName: "xxx"/
    • 资源压缩

    • 缓存
      服务器端做相应的缓存技术,强缓存和协商缓存。
    • 资源分割
      图片或文件过大时,应该做分割处理;
      webpack optimization.SplitChunks 分割代码。
    • 减少请求
      使用雪碧图减少 http 请求;
      合并代码减少请求。
  • 代码优化

    • 减少 dom 层级,减少 css 选择器层级;层级越深,渲染性能越低。
    • css 有多个层级时,尽量不要直接使用标签名,因为 css 的遍历是从内到外(从右到左)的。例如:

      .nav a {
          color: red;
      }
      /*
          css 会先遍历所有的 a 标签,再找到 .nav 下的 a 标签。
      */
    • 减少 dom 操作的次数。
    • 注意内存回收,尤其是再 dom 节点或 dom 节点绑定的方法赋值个某个变量时。
    • 减少绑定方法的次数。
    • 尽量使用 transform 或者 opacity 来实现动画效果。相关阅读:https://fed.taobao.org/blog/t...
    • for 循环体先将循环次数放到变量中,避免多次取值影响性能

        // 错误
        for (let i = 0; i < arr.length; i++) {}
        // 正确
        for (let i = 0, len = arr.length; i < len; i++) {}
    • 避免使用全局变量
      因为原型链的存在,使用全局变量要比局部变量开销更大。
    • if 语句分支过多时,应当使用 switch;switch 可以直接定位到指定条件。

CommonJS 和 ES6 Module 的区别

  • CommonJS 模块依赖关系的建立发生在代码运行阶段;ES6 Module 模块依赖关系的建立发生在代码编译阶段。
  • CommonJS require 的模块路径可以动态指定,支持传入一个表达式,也可以使用 if 语句判断是否加载某个模块;ES6 Module 的导入、导出语句都是声明式的,不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域(比如不能放在 if 语句中)。
  • 在导入一个模块时,CommonJS 获取的是一份导出值的拷贝;ES6 Module 获取的是值的动态映射,并且这个映射是只读的。

手写 call、apply、bind 方法

// call
Function.prototype.myCall = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments].slice(1);
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// apply
Function.prototype.myApply = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments][1] || [];
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// bind
Function.prototype.myBind = function (context) {
  context = context || window;
  let fn = this;
  let args = [...arguments].slice(1);
  return function () {
    let exeArgs = [...arguments]
    fn.apply(context, [...args, ...exeArgs])
  }
}

实现一个宽高比 2 : 1 的 div

.div {
  box-size: border-box;
  width: 100%;
  padding-bottom: 50%
}

position: sticky 实现粘性布局

.nav {
  position: -webkit-sticky;
  position: sticky;
  width: 100%;
  height: 100px;
  background-color: #f00;
  top: 0;
  left: 0;
}

给斐波那契函数实现缓存功能

let fibCache = (function() {
    let cache = {};
    return function fib(n) {
        if (n < 2) {
            return n;
        }

        if (cache[n]) {
            return cache[n];
        } else {
            return cache[n] = fib(n - 1) + fib(n - 2);
        }
    }
})();
fibCache(10);

事件循环

JavaScript 是单线程的,所有的任务都需要排队,任务又分为同步任务和异步任务。
所有的同步任务都在主线程中排队执行,只有前一个任务执行完毕才会去执行下一个任务。异步任务则放在任务队列中排队,分为宏任务队列和微任务队列。
只有主线程执行栈为空时,才会读取微任务队列中的任务到执行栈中执行,只有当微任务队列为空时,才会读取宏任务队列中的任务到执行栈中执行。

什么是强缓存和协商缓存

浏览器请求一次资源后,可通过服务器设置响应头来控制资源的缓存方式。
浏览器命中强缓存时,直接从本地读取资源,并返回 200 状态码,不会向服务器发送请求。
浏览器向服务器发送请求,命中协商缓存时,直接从本地读取资源,并返回 304 状态码。

  • 强缓存: 通过响应头 expirescache-control 设置

    • expires(http1.0): 值为 GMT 格式的时间,在此时间内则命中强缓存。
    • cache-control(http1.1):取值 max-age=xxx,xxx 数值,单位秒,在该有效期内则命中强缓存。
    • expirescache-control 同时存在时,cache-control 优先级高于 expires
  • 协商缓存:通过字段 last-modified/if-modified-sinceetag/if-none-match 设置

    • last-modified/if-modified-since(http1.0):值为 GMT 格式时间,表示资源最后修改时间,当浏览器再次发送请求时,设置请求头 if-modified-since 的值为上次请求中 last-modified 的值,服务器比对 last-modifiedif-modified-since,如果相同则命中协商缓存。
    • etag/if-none-match(http1.1):值为一段字符串,表示文件的唯一标识,当浏览器再次发送请求时,设置请求头 if-none-match 的值为上次请求中 etag 的值,服务器比对 etagif-none-match,如果相同则命中协商缓存。

怎么处理 CSRF、XSS 漏洞

  • CSRF(Cross-site request forgery)跨站请求伪造。相关阅读:https://tech.meituan.com/2018...

    • 攻击

      • 用户登录 A 网站,并保留了登录凭证(cookie)
      • 用户访问了 B 网站
      • B 网站向 A 网站发送一个请求,浏览器会默认携带 cookie
      • A 网站对请求进行验证,确认是用户的凭证
      • A 网站执行 B 网站的请求
    • 防御

      • 同源检测:通过 http 请求头中的originreferer字段,确定请求的来源域名
      • 添加 token
      • 双重 cookie 验证
  • XSS(Cross-Site Scripting)跨站脚本攻击。相关阅读:https://tech.meituan.com/2018...

    • 攻击:攻击者将恶意代码插入到页面中
    • 防御:过滤敏感字段

怎么处理跨域问题

  • 1.服务器设置响应头:access-control-allow-origin,放开跨域问题;
  • 2.利用 JSONP 处理,因为 JSONP 是通过创建 script 标签的方式发送请求,所以只能用于 get 方法;
  • 3.通过服务器的反向代理方式。

排序算法

  • 冒泡排序

      // 冒泡排序
      function bubbleSort(arr) {
          let len = arr.length;
          for (let i = 0; i < len; i++) {
              for (let j = 0; j < len - i - 1; j++) {
                  if (arr[j] > arr[j + 1]) {
                      [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                  }
              }
          }
          return arr;
      }
  • 选择排序

      function selectionSort(arr) {
        let len = arr.length;
        let indexMin;
        for (let i = 0; i < len - 1; i++) {
          indexMin = i;
          for (let j = i + 1; j < len; j++) {
            if (arr[j] < arr[indexMin]) {
                indexMin = j
            }
          }
    
          if (i !== indexMin) {
            [arr[i], arr[indexMin]] = [arr[indexMin], arr[i]]
          }
        }
        return arr;
      }
  • 插入排序

      function insertSort(arr) {
        let len = arr.length;
        let temp;
        for (let i = 1; i < len; i++) {
          temp = arr[i];
          let j = i - 1;
          while (j >= 0 && temp < arr[j]) {
            arr[j + 1] = arr[j];
            j--;
          }
          arr[j + 1] = temp;
        }
        return arr;
      }
  • 快速排序

      function quickSort(arr) {
        function quick(arr, left, right) {
          if (arr.length < 2) return arr;
          let piovt = arr[Math.floor((left + right) / 2)];
          let indexLeft = left;
          let indexRight = right;
          while (indexLeft <= indexRight) {
            while (arr[indexLeft] < piovt) {
              indexLeft++;
            }
            while (arr[indexRight] > piovt) {
              indexRight--;
            }
    
            if (indexLeft <= indexRight) {
              [arr[indexLeft], arr[indexRight]] = [arr[indexRight], arr[indexLeft]];
              indexLeft++;
              indexRight--;
            }
          }
          if (left < indexLeft - 1) {
            quick(arr, left, indexLeft - 1);
          }
          if (right > indexRight + 1) {
            quick(arr, indexRight + 1, right);
          }
        }
        quick(arr, 0, arr.length - 1);
        return arr;
      }
  • 归并排序

      function mergeSort(arr) {
        let len = arr.length;
        if (len > 1) {
          let mid = Math.floor(len / 2);
          let left = mergeSort(arr.slice(0, mid));
          let right = mergeSort(arr.slice(mid, len));
          arr = merge(left, right);
        }
    
        function merge(left, right) {
          let indexLeft = 0;
          let indexRight = 0;
          let res = [];
          while (indexLeft < left.length && indexRight < right.length) {
            if (left[indexLeft] < right[indexRight]) {
              res.push(left[indexLeft++]);
            } else {
              res.push(right[indexRight++]);
            }
          }
          if (indexLeft < left.length) {
            return [...res, ...left.slice(indexLeft)];
          } else {
            return [...res, ...right.slice(indexRight)];
          }
        }
        return arr;
      }

实现一个浮点数相加的方法,要保证精度(0.1 + 0.2 = 0.3)

  function sum(num1, num2) {
    let str1 = num1.toString();
    let str2 = num2.toString();
    let point1 = str1.indexOf('.');
    let point2 = str2.indexOf('.');
    let intNum1 = parseInt(str1.split('.').join(''), 10);
    let intNum2 = parseInt(str2.split('.').join(''), 10);
    let offset = Math.abs(point1 - point2);
    let ret;

    if (point1 > point2) {
        intNum1 = intNum1 * Math.pow(10, offset);
        ret = (intNum1 + intNum2) / Math.pow(10, point1);
    } else if (point1 < point2) {
        intNum2 = intNum2 * Math.pow(10, offset);
        ret = (intNum1 + intNum2) / Math.pow(10, point2);
    } else {
        ret = (intNum1 + intNum2) / Math.pow(10, point1);
    }
    return ret
  }

rem 和 em

rem 是根据根节点的 font-size 变化,em 是根据父级节点的 font-size 变化。

css 选择器优先级

!important > 内联 style > ID 选择器 > 类选择器 > 标签选择器 > 通配符选择器

flex 1

flexflex-growflex-shrinkflex-basis的缩写,flex: 1即是flex: 1 1 auto的缩写。
相关阅读:
http://www.ruanyifeng.com/blo...
https://developer.mozilla.org...

实现上下左右居中

/* 方式一 */
.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 方式二 */
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  top: 50%;
  left: 50%;
  margin-top: -50px;
  margin-left: -50px;
}
/* 方式三 */
.box {
  display: flex;
  justify-content: center;
  align-item: center;
}

清除浮动的几种方式

移动端 1px 问题

  • 使用图片
  • box-shadow
  • transform: scale(0.5)
  • 设置viewportscale

深拷贝

  • JSON.parse(JSON.stringify()),不够全面。
  • function clone(target, memoMap = new WeakMap()) {
      if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (memoMap.get(target)) {
          return map.get(target);
        }
        memoMap.set(target, cloneTarget);
        for (const key in target) {
          cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
      } else {
        return target;
      }
    }

相关阅读:https://juejin.im/post/684490...

浏览器的渲染过程

  • 根据html构建 DOM
  • 根据css构建 CSSOM
  • 将 DOM 和 CSSOM 合并成 Render Tree
  • 根据 Render Tree 进行布局
  • 最后绘制

判断数组的方法

let arr = [];
// Array.isArray()
if (Array.isArray(arr)) console.log('is array');
// instanceof
if (arr instanceof Array) console.log('is array');
// toString()
if (Object.prototype.toString.apply(arr).toLowerCase() === '[object array]') console.log('is array');

css布局:左边固定右边自适应和左右固定中间自适应

  • 左边固定右边自适应

    • position

        .box {
          width: 200px;
          position: relative;
        }
        .left {
          position: absolute;
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
        }
        .right {
          margin-left: 50px;
          background-color: bisque;
        }
    • float

        .box {
          width: 200px;
        }
        .left {
          float: left;
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
        }
        .right {
          background-color: bisque;
        }
    • flex

        .box {
          width: 200px;
          display: flex;
        }
        .left {
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
        }
        .right {
          flex: 1;
          margin-left: 50px;
          background-color: bisque;
        }
  • 左右固定中间自适应

    • flex

        .box {
          width: 200px;
          display: flex;
        }
        .left {
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
        }
        .mid {
          flex: 1;
          background-color: chocolate;
        }
        .right {
          width: 80px;
          background-color: bisque;
        }
    • 浮动

        <div class="box">
          <div class='left'>左边左边左边左边左边左边左边</div>
          <div class='right'>右边右边右边右边右边右边右边</div>
          <div class="mid">中间中间中间中间中间中间中间中间</div>
        </div>
        .box {
          width: 200px;
        }
        .left {
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
          float: left;
        }
        .mid {
          background-color: chocolate;
          overflow: hidden;
        }
        .right {
          float: right;
          width: 50px;
          background-color: bisque;
        }
    • table布局

        .box {
          width: 200px;
          display: table;
        }
        .left {
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
          display: table-cell;
        }
        .mid {
          background-color: chocolate;
          display: table-cell;
        }
        .right {
          width: 50px;
          background-color: bisque;
          display: table-cell;
        }
    • 绝对定位

        .box {
          width: 200px;
          position: relative;
        }
        .left {
          width: 50px;
          height: 100px;
          background-color: aqua;
          opacity: 0.5;
          position: absolute;
          left: 0;
        }
        .mid {
          background-color: chocolate;
          position: absolute;
          left: 50px;
          right: 50px;
        }
        .right {
          width: 50px;
          background-color: bisque;
          position: absolute;
          right: 0;
        }

代码题

// 请写出输出结果
let ret = [1,2,10].map(parseInt);
console.log(ret); // ret: [1, NaN, 2]

// 请写出输出结果
let s = new String('s');
switch (s) {
  case '':
    console.log(1);
    break;
  case 's':
    console.log(2);
    break;
  default:
    console.log(3);
    break;
}
// 输出结果: 3
阅读 1.1k

一剑只能刺三只雁

44 声望
1 粉丝
0 条评论

一剑只能刺三只雁

44 声望
1 粉丝
文章目录
宣传栏