0%

前端八股(JS部分)

JavaScript高频问题

  1. JS的组成部分
    • ECMA Script:JS的核心内容,基础语法。如var, for, 数据类型等。
    • 文档对象模型(DOM):DOM将HTML页面规划为元素构成的文档。
    • 浏览器对象模型(BOM):对浏览器窗口进行访问和操作(如提供浏览器特性,获取浏览器API)。
  2. JS内置对象
    • String, Boolean, Number, Array, Object, Function, Math, Date, RegExp
    • 常用内置对象:
      • Math: abs(), sqrt(), max(), min(), ...
      • Date: new Date(), getYear(), getHour(), ...
      • String: concat(), length, slice(), split(), ...
  3. 操作数组的方法
    • push(), pop(), sort(), shift(), unshift(), reverse(), concat(), join(), map(), filter, every(), some(), reduce(), isArray(), findIndex(), splice()
    • 哪些方法会改变原数组:push(), pop(), shift(), unshift(), sort(), reverse(), splice()
  4. 检测数据类型
    • typeof:判断基础数据类型,无法判断引用数据类型(class)。typeof 12345
    • instanceof():只能判断引用数据类型,无法判断基本数据类型。[] instanceof Array
    • constructor:几乎可以判断基本数据类型和引用数据类型。('abc').constructor === String
    • Object.prototype.toString.call():解决以上方式的问题。Object.prototype.toString.call(2/'abc'/true/[]/{})
  5. 闭包特性
    • 函数嵌套函数,内部函数被外部函数返回并保存下来时会产生闭包。
      1
      2
      3
      4
      5
      6
      7
      function fn(param) {
      return function() {
      console.log(param);
      }
      }
      var foo = fn('abcd');
      foo()
      输出abcd
    • 特性:
      • 可以复用变量且该变量不会污染全局(变量始终保存在内存中,不会被垃圾回收机制回收)。
      • 缺点:闭包较多时对内存占用较多,导致页面性能下降。只有在IE浏览器中会导致内存泄露。
    • 使用场景:防抖、节流、需避免全局污染时。
  6. 前端内存泄漏
    • JS中已分配内存空间的对象为能及时释放清除,会导致长期占用内存的现象,导致内存资源可用性变差,最终导致运行效率降低,甚至内存泄漏。
    • JS的垃圾回收机制:自动释放长时间不用的内存资源。
    • 导致内存泄漏的原因:为声明直接赋值的变量、未清空的定时器、过度闭包、引用元素未被清除等。
  7. 事件委托(事件代理)
    • 利用事件冒泡机制,将子元素事件(event)绑定到父元素上。
    • 若子元素阻止事件冒泡(event.stop()),则委托不成立。
    • addEventListener('click', func_name, false)默认false,false控制事件冒泡,true为事件捕获。
    • 好处:提高性能,减少事件绑定,减少内存占用。
  8. 基本数据类型与引用数据类型
    • 基本数据类型:String, Number, Boolean, undefined, null等保存在中,直接保存数据。
    • 引用数据类型(复杂数据类型):Object, Function, Array等保存在中,类似于C语言的指针类型(或C++引用),保存引用数据类型的地址。加入声明两个引用类型变量引用同一个地址时,改变其中一个另一个也改变。
  9. 原型链
    • 原型就是一个普通对象,用于构造函数的实例,共享属性和方法;所有实例中引用的原型都是同一个对象。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person() {
      this.say = function() {
      console.log('Hello');
      }
      }
      var p1 = new Person();
      var p2 = new Person();
      p1.say(); //p1和p2的say方法相互独立,需要存两个实例。
      p2.say();
    • 使用prototype可以把方法挂在原型上,不同实例共享同一内存。
      1
      2
      3
      4
      5
      Person.prototype.look = function() {
      console.log('Look');
      }
      p1.look();//p1和p2的look方法指向同一内存。
      p2.look();
    • __proto__可以理解为指针,实例对象中的属性指向构造函数的原型(prototype)。
    • 参照C++虚函数表机制。
    • JS中若把属性定义在Prototype上,只读时内存共享,一旦需要写入则创建拷贝(遮蔽)。
    • 原型链:一个实例对象在调用属性、方法时一次按照实例本身(Person)、构造函数原型(Person.prototype)、原型的原型(Object.prototype)上去查找。
  10. new操作符
    1. 创建空对象。
    2. 把空对象和构造函数通过原型链连接。
    3. 把构造函数的this绑定到新的空对象。
    4. 根据构造函数返回的类型判断,若为值则返回对象值,若为引用则返回引用。
  11. JS的继承机制
    • 原型链继承:方便简洁,容易理解;对象实例继承所有属性和方法,无法像父类构造函数传参。Child.prototype = new Parent()
    • 借用构造函数继承:在子类构造函数内部调用父类构造函数;使用apply()call()方法将父对象构造函数绑定在子对象上。在子类中写Parent.call(this, param)。解决了原型链实现继承无法向父类传参的问题;方法都在构造函数中定义,无法实现函数复用,且父类原型中定义的方法对子类不可见,所有类型只能使用构造函数模式。
    • 组合式继承:解决了原型链继承和构造函数继承的缺点;然而无论什么情况下都会调用两次父类构造函数(一次是创建子类原型时,另一次在子类构造函数内部)。
    • ES6的class类继承:Class通过extends关键字实现继承,实质是先创造出父类this对象在调用子类构造函数修改this。子类构造方法中必须调用super方法且只有在调用了super后才能使用this。简单易懂;对于不支持ES6的浏览器无法适用。
  12. JS的设计原理
    • JS引擎:编译器。
    • 运行上下文:浏览器中可调用的API(外部接口)。
    • 调用栈:函数调用顺序的数据结构。用于同步执行、递归、错误堆栈等场景。(JS是单线程,只有一个主线程)
    • 事件循环:由于JS是单线程,为防止卡死引入了异步机制。事件循环为调用栈为空时,从任务队列里取出一个回调,压入调用栈执行,重复这个过程。把耗时任务交给“外部系统 (浏览器内核的 Web APIs 或 Node 的 libuv)”,等结果好了,再通过 事件循环 (Event Loop) 安排执行回调函数。
    • 回调:是一个被当作参数传递的函数,在合适的时候被调用。在异步编程里,回调通常是“异步任务完成后要执行的代码”。
  13. JS中this的指向
    • 全局对象中this的指向:指向window。
    • 全局作用域或普通函数中的this:指向window。
    • this永远指向最后调用它的对象。(非箭头函数)
    • new关键词会改变this的指向。
    • applycall以及bind都可以改变this的指向。(非箭头函数)
    • 箭头函数中的this在定义时已被确定,箭头函数无this,看外层是否有函数,有就是外层函数的this,没有就是window。
    • 匿名函数中this永远指向window。(匿名函数的执行环境具有全局性)。
  14. Script标签中asyncdefer
    • 没有async与defer时:浏览器会立刻加载并执行指定的脚本。
    • async:加载和渲染后面元素的过程将与Script的加载和执行并行执行(异步)。
    • defer:加载Script与加载渲染元素异步完层,执行则在所有元素解析完成之后。
  15. setTimeout的最小执行时间
    • setTimeout:4ms
    • setInterval: 10ms
    • 由HTML5规定(浏览器可能会影响)
  16. ES6和ES5的区别
    • ES5: ECMA Script 5(ECMAScript2009);
    • ES6: ECMA Script 6(ECMAScript2015);
  17. ES6的新特性
    • 新增块级作用域(let,const)
      • 不能在同一个作用域内重复声明。
      • 不存在变量提升。
      • 存在暂时性死区问题
    • 新增定义类(class)
    • 新增一种数据类型(symbol):代表独一无二的值,即使内容一致。
    • 新增结构赋值:从数据或者对象中取值赋给变量。
    • 新增函数参数默认值
    • 新增数组API
    • 新增对象和数组的扩展运算符
    • 新增Promise
      • 表示未来才会完成的值,用于解决回调地狱的问题。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        const p = new Promise((resolve, reject) => {
        setTimeout(() => {
        resolve("任务完成"); // 或 reject("出错了")
        }, 1000);
        });

        p.then(result => {
        console.log(result);
        }).catch(error => {
        console.error(error);
        });
      • 自身有all,reject,resolve,rece这些方法。原型上有then,catch方法。
      • 把异步操作序列话,按预期返回结果。
      • 三种状态:pending(初始),fulfilled(操作成功),rejected(操作失败)。一旦状态转变就会凝固。
      • async与await:同步代码做异步操作,必须搭配使用。是Promise的语法糖。在函数面前添加async会自动返回一个Promise。遇到await暂停执行,等待Promise完成再继续。
    • 新增模块化(import,export)
    • 新增set和map数据结构
    • 新增generator
    • 新增箭头函数
      • 不能作为构造函数使用,不能用new。
      • 箭头函数没有arguments。
      • 不能使用call,apply,bind去改变this的执行
      • 无prototype。
  18. call, apply, bind的区别
    • 都是改变this指向和函数调用。
    • call与apply类似,只是传参方法不同。
    • call传参数列表。
    • apply传一个数组。
    • bind传参后不会立即执行,会返回一个改变了this指向的函数,这个函数可传参,通过bind()()来调用。
    • call方法性能比apply好一些,bind无法构造函数。
  19. 递归的问题
    • 递归:函数内部调用函数本身(递归函数必须有退出条件,否则会导致栈溢出)。
  20. 深拷贝的实现
    • 完全拷贝一个新的对象,在堆中重新开辟新的空间。拷贝的对象被修改后,原对象不受影响。
    • 主要针对引用数据类型。
    • 扩展运算符:只能针对第一层深拷贝,对于多层对象只是浅拷贝。
      1
      2
      3
      4
      5
      let obj1 = {
      name: "jack",
      age: 23
      };
      let obj2 = {...obj};
    • JSON.parse(JSON.stringify()):原对象中若有函数则不会被拷贝过来。
    • 利用递归函数:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function copy(origin, isDeepCopy=true) {
      let result = {};
      if (origin instanceof Array) {
      result = [];
      }
      for (let key in origin) {
      let v = origin[key];
      result[key] = (!!isDeepCopy && typeof v === 'object' && v !== null) ? copy(v, isDeepCopy) : v;
      }
      return result;
      }
  21. 事件循环
    • JS是一个单线程脚本语言。
    • 主线程:唯一线程上任务。
    • 执行栈:任务栈,存储上下文与执行顺序。
    • 任务队列:任务调度。
    • 宏任务/微任务:异步任务,微任务优先级更高。
    • 主线程先执行同步任务,再执行任务队列里的微任务或宏任务。执行完再去查询任务队列,或者执行主任务。
  22. AJAX
    • 创建交互式网页应用的开发技术,再不重新加载整个页面的情况下与服务器交换数据并更新部分内容。
    • 通过XMLHttpRequest对象向服务器发送异步请求,返回数据后通过JS操作DOM的形式更新页面。
    • 过程:
      • 创建XMLHttpRequest对象。
      • 通过XMH对象里的open()方法与服务器建立连接。
      • 构建请求所需的数据,并通过XMH的send()方法发送至服务器。
      • 通过XMH对象的onReadyStateChange事件监听服务器与本地的通信状态。
      • 接受并处理服务器响应的结果。
      • 将处理结果更新到HTML页面中。
  23. GETPOST的区别
    • GET一般获取数据,POST一般提交数据。
    • GET参数在URL中,安全性较差。
    • POST参数在body中,数据长度无限制。
    • GET请求刷新服务器或退回无影响,POST请求退回时重新提交数据。
    • GET请求会被缓存,POST不会。
    • GET会被保存在浏览器的历史记录中,POST不会。
    • GET只能进行url编码,POST支持多种数据结构。
  24. Promise的内部原理与优缺点
    • Promise对象封装了一个异步操作且可以获取成功或失败的结果。
    • Promise主要解决回调地狱的问题,若之前异步任务较多且之间包含依赖关系就只能用回调函数解决,这样会导致代码可读性差、可维护性差形成了回调地狱。
    • Promise的三种状态:Pending,Fulfilled,Rejected。只能从Pending变为后者其中之一且不可逆。
    • Promise无法被取消,一旦创建立即执行,无法中断。
    • 如果不设置回调,Promise内部抛出的错误无法反馈到外面。
    • 原理:
      • 构造一个Promise实例需要传递一个函数作为参数,该函数有两个形参切都是函数类型,一个是resolve另一个是reject。
      • Promise上还有then方法,用来指定状态发生改变时的确定操作,resolve执行第一个函数,reject执行第二个函数。
  25. Promise与async、await的区别
    • 都是异步处理请求方式。
    • Promise是ES6,async和await是ES7.
    • async与await都是基于Promise实现的,都是非阻塞的。
    • 优缺点:
      • Promise是一个返回对象需要用then和catch来捕获,且书写方式是链式的容易造成代码重叠不好维护。而async和await通过try-catch来捕获异常。
      • async await最大的优点是让代码看起来像同步一样,只要遇到await就会立刻返回结果然后再执行后面的操作。而Promise.then()的方式会出现请求还没返回就执行了后面操作的情况。
  26. 浏览器的存储方式
    • cookies:H5标准前的本地存储方式;兼容性好,请求头自带cookie;存储量小容易造成资源浪费,使用麻烦(需要封装)。
    • localStorage:H5加入的标准方式,以key-value的形式存储。操作方便、永久存储、兼容性好;保存值的类型可能被限定,浏览器隐私模式下不可读取,不能被爬虫。
    • sessionStorage:类似于localStorage;当前页面关闭后会被立即清理,会话级别的存储方式。
    • indexedDB:H5标准存储方式,key-value存储,可以快速读取,适合web场景。
  27. token的存储位置
    • token:验证身份的令牌,一般是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串。
    • 若存在localStorage里,后期每次请求接口都需要把它当作一个字段传给后台,容易被XSS攻击。
    • 若存在cookie中会自动发送,不能跨域,会有CSRF攻击。
    • 一般存在localStorage/sessionStorage中,具体看需求。
  28. token的登录流程
    1. 客户端用账号密码请求登录。
    2. 服务端收到请求后验证账号密码。
    3. 验证成功后,服务端签发一个token发送给客户端。
    4. 客户端收到token保存后。
    5. 后续客户端每次向服务端发送资源请求时,都需要携带这个token。
    6. 服务端收到请求后验证token。成功则返回资源。
  29. 页面渲染的过程
    • DNS解析-建立TCP连接-发送HTTP请求-服务端处理请求-渲染页面-断开TCP
    • 渲染页面:
      • 获取HTML和CSS资源,把HTML解析成DOM树。
      • 解析CSS为CSSOM。
      • 把DOM和CSSOM合并为渲染树。
      • 把渲染树每个节点渲染到页面上(绘制)。
  30. DOM树与渲染树
    • DOM树是HTML标签的一一对应,包括head和隐藏元素。
    • 渲染树不包含head和隐藏元素。
  31. 精灵图与base64
    • 精灵图:多张图拼接后压缩,前端若要请求多张图片可以只请求一次。
    • base64:单张图片二进制信息转译为字符串(字符串大小可能比原图大)。
  32. svg格式
    • 基于XML语法格式的图像格式,可缩放矢量图。
    • svg可直接插入页面中,用JS或css对其进行操作。<svg></svg>
    • 也可以作为图像文件被引入。
    • 可以转为base64引入。
  33. JWT
    • JSON Web Token通过JSON形式在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输。
    • 用作信息传输、授权。
    • 如果用session来验证会导致服务端负载过大,不利于负载均衡。
    • JWT认证流程:
      • 前端把账号密码发送给后端接口。
      • 后端核对信息,把用户id等信息作为JWT负载,把它和head分别进行base64编码后拼接的签名,形成一个JWT Token。
      • 前端每次请求时都会把JWT放在请求头的Authorization位置。
      • 后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期等)。
      • 验证后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果。
    • JWT简洁、可跨语言使用。
  34. npm的底层环境
    • node package manager,node的包管理和分发工具,已经成为分发node的标准。是JS的运行环境。
    • 组成:网站、注册表(每一个包基本信息的数据库),命令行工具。
  35. HTTP协议规定包含的响应头和请求头
    • 请求头信息:
      • Accept:浏览器告诉服务器所支持的数据类型。
      • Host:浏览器告诉服务器像访问的主机。
      • Referer:浏览器告诉服务器我从哪里来(防盗链)。
      • User-Agent:浏览器类型、版本。
      • Date:浏览器告诉服务器我是什么时候访问的。
      • Connection:连接方式。
      • Cookie
      • X-Request-With:请求方式。
    • 响应头信息:
      • Location:告诉浏览器你要去找谁。
      • Server:告诉浏览器服务器的类型。
      • Content-Type:告诉浏览器返回的数据类型。
      • Refresh:控制定时刷新。
  36. 浏览器的缓存策略
    • 强缓存(本地缓存):不发起请求,直接使用缓存中的内容,浏览器把JS,CSS,image等内容存到内存中,下次访问时直接从内存中取。
    • 弱缓存(协商缓存):需要向后台发请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器使用缓存里的内容。
    • 强缓存的触发:HTTP1.0用时间戳响应标头;HTTP1.1用Cache-Control响应标头。
    • 弱缓存的触发:HTTP1.0用if-modified-since响应头last-modified;HTTP1.1用if-none-match响应头Etag。
  37. 同源策略
    • 浏览器的基本(核心)安全策略,URL中协议、域名以及端口号完全相同即为同源。不一样则产生跨域。
    • 三个允许跨域加载资源的标签:img,link,script。
    • 跨域是可以正常发送请求的,后端也会返回结果,只不过该结果被浏览器拦截。
    • 解决跨域的方式:JSONP(服务端允许),CORS(服务端和客户端同时配置),websocket,反向代理(中转站)。
  38. 防抖和节流
    • 都是应对页面中频繁触发事件的优化方案。
    • 防抖:触发事件后,在一段时间内只执行最后一次。如果在这段时间内事件又被触发,就重新计时。
      • 频繁和服务端交互
      • 输入框自动保存时间
    • 节流:触发事件后,在固定时间间隔内只执行一次。不管事件触发多少次,都会按照时间间隔来执行。
      • 页面滚动监听 → 每 200ms 计算一次位置。
      • 鼠标拖拽 → 每 100ms 更新一次位置。
  39. JSON
    • JSON是纯字符串形式数据,适合在网络中传输,不提供任何方法。
    • JSON数据存储在.json文件中,或者以字符串形式在数据库、cookie中。
    • JS提供了JSON.parse(), JSON.stringify()方法。
    • 使用场景:定义接口、序列化、生成token、配置文件。
  40. 数据请求失败的做法
    • 在渲染数据的地方给默认值。
    • if判断,没有数据可以隐藏。
  41. 无感登录
    • 无感刷新token,token失效时自动更新。
    • 在响应拦截器中拦截,token过期后调用刷新token的接口。(常用)
    • 后端返回过期的时间,前端判断token的过期时间,主动调用刷新token的接口。
    • 写定时器,定时刷新token。
    • 流程:
      • 登录成功后保存token和refresh_token。
      • 响应拦截器中对401状态码引入刷新token的api方法调用。
      • 替换保存本地新的token。
      • 把错误对象里的token替换。
      • 再次发送未完成请求。
      • 如果refresh_token也过期,清除所有token返回登录页面。
  42. 大文件上传
    • 分片上传:把需要上传的文件按一定规则分割成大小相同的数据块。
      • 初始化分片上传任务,返回本次上传的唯一标识。
      • 按照一定规则把各个数据块上传。
      • 发送完成后服务端判断数据完整性,若完整则合并。
    • 断点续传:
      • 服务端返回从哪里开始/浏览器自己处理。
  43. varletconst的区别
    • 作用域:
      • var在函数内声明则只作用于函数内部,其他情况都是全局作用域。
      • let/const是在花括号内有效。
    • 变量提升:
      • var声明会被提升(hoist),初始化为undefined。声明之前访问为undefined。
      • let/const也会被提升,但是会有TDZ(Temporary Dead Zone),在声明之前访问会报错。
    • 重复声明:
      • var可以重复声明同名变量。
      • let/const在同一作用域内不允许重复声明。
    • 是否可以重新赋值:
      • var/let:可以重新赋值。
      • const:声明后必须立刻赋初值,且不能重复赋值。
    • 对象/数据的const特殊性:const只是保证变量绑定(引用)不可辨,但对象/数组的内容可以被改变。