一、前端面试题 - js篇

1. 四种定时器及区别

setTimeout:定时器,只执行一次
setInterval:间隔定时器,执行n次
setImmediate:浏览器完全结束当前运行的操作之后立即执行指定的函数
requestAnimationFrame: 专门为实现高性能的帧动画而设计的API,但是不能指定延迟时间,而是根据浏览器的刷新频率而定(帧)


2. 浏览器宏任务、微任务

浏览器的微任务MicroTask和宏任务MacroTask
宏任务:比较费时、比较慢的任务,例如setTiemout、setInterval、setImmediate、I/O、UI渲染
微任务:Promise、MutationObserver(监听节点变化)、process、nextTick

浏览器从上之下读取代码,先执行同步代码,遇到异步代码就放到事件循环(堆栈)中去,当同步代码执行完成后,从堆栈中以“先进先出”为原则,依次执行异步代码。
步骤如下:
1.执行完主逻辑中的同步任务
2.取出微任务队列(MicroTask Queue)中的任务执行,直到队列被完全清空
3.取出宏任务队列(MacroTask Queue)中的一个任务执行。
4.取出微任务队列(MicroTask Queue)中的任务执行,直到队列被完全清空
5.重复 3 和 4,直到宏任务队列(MacroTask Queue)被清空。


3. 修改this指向

前端js中this指向及改变this指向的方法
call: 立即执行该函数 - 序列化参数 fn.call(this, 参数1, 参数2, 参数3...)
apply: 立即执行该函数- 数组参数 fn.apply(this, [参数1, 参数2, 参数3...])
bind: 不执行函数,只返回一个可供执行的函数

fn.bind(Person, 4, 5); //只是更改了this指向,没有输出
fn.bind(Person, 4, 5)(); // 更改this指向并执行方法

4. 闭包

js中的闭包

图4-1 什么是闭包

闭包的三个特性:

  • 函数嵌套函数
  • 内部函数可以使用外部函数的参数和变量
  • 参数和变量不会被垃圾回收机制回收

js闭包及常见的应用场景
使用场景:

  • 点击li标签获取是第几个Li
  • 函数防抖和函数节流
  • 定时器传参


    图4-2 定时器传参

5. 浏览器垃圾回收机制
图5-1 什么是垃圾回收机制
  • 标记清除
    浏览器给每个变量都添加标记,当这个变量被使用时就去除这个变量的标记,垃圾回收会清除带标记的变量并释放它们的内存。
  • 引用计数
    当声明的变量被赋予引用类型的值时引用次数+1,当引用类型发生改变的时候引用次数-1,垃圾回收引用次数为0的。

6. 函数节流和函数防抖

JS函数节流和函数防抖

6.1. 为什么使用函数节流和函数防抖

图6-1 为什么使用函数节流和函数防抖

简单来说就是防止短时间内频繁的触发事件

6.2. 什么是函数节流和函数防抖
  • 函数防抖
    短时间内多次触发同一事件,只执行最后触发的一次,中间的不执行。
  • 函数节流
    短时间内多次触发同一事件,间隔n秒执行一次
    图6-2 应用场景
6.3 代码实现

其实就是闭包+定时器

  • 函数防抖
    在滑动过程中,只执行最后一次,输出我滑动 函数防抖执行
window.onload = function () {
      // 函数防抖
      function debounce(fn, time) {
        let timer = null;
        return function() {
          timer && clearTimeout(timer);
          timer = setTimeout(() => {
            fn();
            console.log('函数防抖执行');
          }, time);
        }
      }
      document.getElementById('box').addEventListener('mousemove', debounce(() => {
        console.log('我滑动');
      }, 2000))
    }
  • 函数节流
    在滑动过程中,隔2s输出一次我滑动 函数节流执行
window.onload = function () {
      // 函数节流 (定时器)
      function throttle(fn, time) {
        let timer = null;
        return function() {
          if (!timer) {
            timer = setTimeout(() => {
              fn();
              console.log('函数节流执行');
              timer = null;
            }, time);
          }
        }
      }

      // 函数节流 (时间戳)
      function throttle1(fn, time) {
        let timer = null;
        let prevTime = +new Date();
        return function() {
          let now = +new Date();
          if (now - prevTime > time) {
            fn();
            console.log('函数节流执行');
            prevTime = now;
          }
        }
      }

      document.getElementById('box').addEventListener('mousemove', throttle1(() => {
        console.log('我滑动');
      }, 2000))
    }

7. 数组去重的几种方式
var arr = [1,2,3,4,1,2,3,4,3,3];
// 1、 利用set+扩展运算符
var arr1 = [...new Set(arr)];

// 2、 利用set+Array.from
var arr2 = Array.from(new Set(arr));

// 3、 循环 + find
var arr3 = [];
for (let i = 0; i < arr.length; i++) {
     if (!arr3.find(it => it === arr[i])) {
         arr3.push(arr[i]);
      }
}

// 4、 对象
let obj = {};
for (let i = 0; i < arr.length; i++) {
    if (!Object.values(obj).find(it => it === arr[i])) {
          obj[i] = arr[i];
    }
}
console.log(Object.values(obj));

// 5、 filter
var arr4 = arr.filter((item, index, array) => array.indexOf(item) === index);

8. for...of、for...in、forEach区别
  • for...of
    可以遍历数组、类数组、set、map
    不能遍历对象
    可以使用continune、break跳出循环
    语法简洁
  • for...in
    可以遍历对象、数组
    遍历顺序是随机的
  • forEach
    不能遍历对象
    不可以使用continune、break、renturn跳出循环

9. jq的on、bind区别

on可以给动态添加的元素也绑定相同事件;可以将子元素的事件委托给父元素处理(事件代理);
bind无法给动态添加的元素绑定相同事件;也无法实现事件代理


10. class 继承和静态方法
  • 静态方法


    图10-1 静态方法
  • 继承
    子类 必须 在 constructor( )调用super( )方法,否则新建实例时会报错。
    子类是没有自己的 this 对象的,它只能继承自父类的 this 对象,然后对其进行加工,而super( )就是将父类中的this对象继承给子类的。没有 super,子类就得不到 this 对象。
    图10-2 继承

es5继承:
属性继承:使用call修改this指向父类.call(this)
方法继承:主要使用prototype,分为拷贝继承(for...in循环对象,将属性一一复制)、类式继承(使用new F(),F为空方法)
es6 class 类静态方法和继承


11. ES6、7

ES6新特性
ES6新特性

    1. const、let
    1. 箭头函数
    1. rest 剩余参数,和arguments类似,是个数组,箭头函数中使用rest不能使用arguments function(...params){}
    1. class(extends+super+static)
    1. 模板字符串
    1. for...in、for...of、forEach
    1. 扩展运算符 [...arr]
    1. 解构赋值 [a, b, c] = [1, 2, 3] {a: aa, b: bb, c: cc} = {a: 1, b: 2, c: 3}
    1. 对象的简洁写法:键名和键值一致时可简写 {a}
    1. 函数默认值:三等undefined时,默认值才生效
    1. startsWith、endsWith
    1. set、map、weakSet、weakMap
    1. 模块export、export default、import(见12)
    1. promise(见13)
    1. 生成器 function* yield(见13)

ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性
ES7新特性

  • arr.includes 认为NaN是相等的


    图11-1 NaN
  • 求幂 var a = 3 ** 2 等同 Math.pow(3, 2)

ES8新特性

  • async/await(见13)
  • Object.values/Object.entries
// Object.entries返回key、value数组
let obj = {a: 1, b: 2, c: 3}
JSON.stringify(Object.entries(obj))
[["a",1],["b",2],["c",3]]
  • 字符串填充
    str.padStart(长度,'填充字符默认空格'); 返回字符串
console.log('react'.padStart(10))         // "       react"
console.log('0.00'.padEnd(5, 0))          // 0.000
console.log('backbone'.padEnd(10))       // "backbone  "

// 长度比字符串本身长度小,返回字符串本身
console.log('hello word'.padEnd(2))       // "hello word"
// 长度比字符串本身长度大,填充字符串
console.log('hello word'.padEnd(20))     // "hello word          "
  • getOwnPropertyDescriptors:返回对象obj所有自身属性(非原型)描述。
  • 函数参数列表和调用中的尾逗号 function a(a, b, c, ){....}

ES9新特性

  • for await of 遍历时,会等待前一个 Promise 对象的状态改变后,再遍历到下一个成员


    图11-2

12. export、export default、import

因为前端的导入导出主要分为commonjsES6modules两种标准,其中commonjs(module.exports、require)主要用于node环境,而ES6modules(export、export default、import)主要用于浏览器环境,但是ES6modules是兼容commonjs标准的, 详情见js中的export、import、export default等导入导出全解

图12-1
13. 同/异步、promise、function* yield、async/await

promise链式写法、promise.all

13.1 为什么引入async/await

一个列表页面需要根据用户信息来呈现数据,用户需要先登录,登录成功后再调用列表接口,就会出现多层嵌套即回调地狱;为了解决回调地狱,es6引入了promise,但是如果需要多次请求时,就会出现一推的then,也就是promise的链式写法,导致代码不直观,所以又出现了生成器 Generator,Generator 函数是一个普通函数,但是有两个特征:
① function关键字与函数名之间有一个星号 function* name(){}
② 函数体内部使用yield表达式,定义不同的内部状态;
每次调用next方法都会执行一个yield,也就是说Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行,但是Generator 虽然语法简洁了些却不好知道什么时候执行第一阶段什么时候执行第二阶段,所以async/await就出生了,它可以以异步方式实现同步操作

13.2 同/异步

同步:等待程序返回后,才执行后续操作(会发生阻塞)
异步:不需要等待程序返回,就可以执行后续操作

13.3 promise
  • 介绍
    promise是异步编程的解决方案(解决回调地狱);它有3个特性:
    ① 3种状态:进行中(pending)、已成功(fulfilled)、已失败(rejected);
    ② 状态一旦改变就无法更改(进行中 -> 已成功 或 进行中 -> 已失败);
    ③ 一旦新建就会立即执行,无法取消,如果不设置回调函数内部的错误不会跑到外部去;

  • promise的链式写法

    图13-1 promise链式写法

  • promise.all
    并行执行多个promise,只有每个promise返回成功才成功,有一个不成功就返回失败

  • promise.race
    并行执行多个promise,以返回最快的promise的状态为准

  • promise.resolve、promise.reject
    promise.resolve、promise.reject 将现有对象转promise并设定状态,如果是promise则原样返回;
    resolve对应调用then,reject调用catch

    图13-2 promise.resolve、promise.reject

13.4 function* yield
图13-3 function* yield
13.5 async/await

理解 JavaScript 的 async/await

图13-4

图13-5

图13-6

图13-7

图13-8


14. webSocket、http、https
  • http
    超文本传输协议,用于web浏览器和服务器的信息传递;
    明文传输,所以不安全;
    无状态即不会记录上一次连接的数据
  • https
    加密的传输协议,也是用于web浏览器和服务器间的信息传递;
    是基于ssl+http的加密传输,所以安全,而且可以验证网站的真实性
    https原理:
    ① 用户使用https的URL访问服务器时,要求与服务器建立ssl连接;
    ② 服务器收到这个请求后,回将网站的证书信息返回给客户端;
    ③ 客户端收到证书后,与服务器约定ssl的安全等级;
    ④ 客户端根据这个安全等级,生成会话密钥,然后对这个会话密钥进行加密再传给服务器;
    ⑤ 服务器收到加密的密钥后对其解密来获取会话密钥;
    ⑥ 服务器通过加密的密钥与客户端进行通信。


    图14-1 https原理

http与https区别:
① http明文传输不安全,https加密传输安全,而且https可以验证网站真实性;
② http端口默认80,https端口默认443;
③ https需要申请证书,大部分安全性好的证书都需要花钱;
④ https协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电,首屏白屏时间比http时间长

  • webSocket
    webSocket是一种全双工通信方式,即客户端与服务器可以同时双向通信,客户端与服务器进行一次握手就可以建立持久性的连接,而且服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息,这样又节省了流量也不需要前端定时请求,避免了资源浪费。
    单双工:数据只能单向传递;
    半双工:数据能双向传递,但是不能同时双向传递;
    全双工:数据能够同时双向传递;

  • TCP、UDP
    TCP:面向连接的传输协议,传输可靠,传输数据量大,有序,速度慢,对系统资源要求多,程序复杂度多;
    UDP:面向非连接,传输不可靠(可能丢包)、传输量小,无序,速度快,对系统资源要求少,程序结构比较简单
    TCP三次握手:
    ① 客户端与服务器发送连接请求;
    ② 服务器接收到连接请求后,向客户端确认请求;
    ③ 服务器向客户端发送数据。
    为什么是3次握手?
    因为在客户端向服务器发送请求,服务器接收到请求这段时间,如果客户端发生断网等情况,此时这个请求在客户端相当无效连接,如果服务器不向客户端确认请求,那么此时服务器就会和客户端建立连接并且一直等待客户端响应,就造成了资源浪费。
    TCP四次挥手:
    ① 客户端向服务器发送断开连接的请求;
    ② 服务器接收到客户端的断开连接的这个请求后,向客户端确认并准备释放连接的报文;
    ③ 客户端确认请求后,等待接收服务端发送报文;
    ④ 客户端接收到报文后关闭连接。
    为什么是四次回收?
    因为客户端发送断开请求,但此时服务器可能还有数据需要处理,所以需要告诉客户端你再等等,我还有数据要处理并通知客户端准备接受剩下的数据了哈,然后客户端收到服务器的消息后就等待服务器发送剩下数据,这就是第2、3次握手。


相关文章

二、 前端面试题 - vue篇
三、 前端面试题 - 安全篇

你可能感兴趣的:(一、前端面试题 - js篇)