前端面试题整理(持续更新中...)

858 阅读12分钟

一、js相关

1. 闭包

  • 概述:闭包说的通俗一点就是打通了一条在函数外部访问函数内部作用域的通道。正常情况下函数外部是访问不到函数内部作用域变量的,表象判断是不是闭包:函数嵌套函数,内部函数被return 内部函数调用外层函数的局部变量。
  • 优点:可以隔离作用域,不造成全局污染。
  • 缺点:由于闭包长期驻留内存,则长期这样会导致内存泄露。
  • 如何解决内存泄露:将暴露全外部的闭包变量置为null。
  • 适用场景:封装组件,for循环和定时器结合使用,for循环和dom事件结合.可以在性能优化的过程中,节流防抖函数的使用,导航栏获取下标的使用。
  • ps:闭包就是为了延续局部变量的生命周期。

2. 说一下JS中的原型链的理解?

2.1 什么是原型、原型链

  • 原型

      ①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象。
      ②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象。
      ③所有引用类型的__proto__属性指向它构造函数的prototype。
    
    
      var a = [1,2,3];
      a.__proto__ === Array.prototype; // true
    
    
  • 原型链

      当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
    
    

    举例,有以下代码

1.png

2.png

  ①一直往上层查找,直到到null还没有找到,则返回undefinedObject.prototype.__proto__ === null
  ③所有从原型或更高级原型中的得到、执行的方法,其中的this在执行时,指向当前这个触发事件执行的对象。

3. 说一下JS继承(含ES6的)

3.1 什么是继承

通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法。

3.2 如何实现继承

构造函数继承、原型、原型链继承、拷贝继承(混入继承:mixin)。

  1. 原型继承 让新实例的原型等于父类的实例
  function Person(name) {
    this.name = name
    this.sun = function () {
      console.log(this.name)
    }
  }

  Person.prototype.age = 10

  function Fly() {
    this.name = 'fly'
  }

  Fly.prototype = new Person()
  var fly = new Fly()
  console.log(fly.age) // 10 
  console.log(fly instanceof Person) // true

  • 特点: 实例可继承属性有: 实例的构造函数属性,父类的构造函数属性,父类的原型属性。(新实例不会继承父类的实例属性)
  • 缺点:
    • 新实例无法向父类构造函数传参。
    • 继承单一。
    • 所有实例都共享父类实例的属性,原型上的属性是共享的,一个实例改变了原型属性,另一个实例的原型属性也被修改。
  1. 原型链继承 用call和apply将父类构造函数引入子类函数。
  function Person(name) {
    this.name = name
    this.sun = function () {
      console.log(this.name)
    }
  }

  Person.prototype.age = 10

  function Fly() {
    Person.call(this, 'fly')
    this.age = 18
  }

  var fly = new Fly()
  console.log(fly.name) //fly
  console.log(fly.age) // 18 
  console.log(fly instanceof Person) // false
  • 特点:
    • 只继承了父类构造函数的属性,没有继承父类原型上的属性。
    • 解决了原型链继承缺点。
    • 可以继承多个构造函数(call多个。
    • 在子实例中可以向父实例传参。
  • 缺点
    • 只继承了父类构造函数的属性。
    • 无法实例构造函数的复用。
    • 每个新实例都有父类构造函数的副本。
  1. 组合继承(原型链继承+构造函数)
  function Father(name) {
    this.faName = 'Father'
  }
  Father.prototype.getfaName = function () {
    console.log(this.faName)
  }
  function Child(args) {
    this.chName = 'Child'
    Father.apply(this, []) //第二次调用父类构造函数
  }
  Child.prototype = new Father() //第一次调用父类构造函数
  Child.prototype.constructor = Child
  Child.prototype.getchName = function () {
    console.log(this.chName)
  }
  • 缺点:
    • 两次调用父类构造函数:(第一次是在创建子类原型的时候,第二次是在子类构造函数内部)。
    • 子类继承父类的属性,一组在子类实例上,一组在子类原型上(在子类原型上创建不必要的多余的属性)(实例上的屏蔽原型上的同名属性)。
  1. es6继承 es6继承是目前比较新,并且主流的继承方式,用class定义类,用extends继承类,用super()表示父类。
  class Person {
    // 私有的属性定义在构造函数中  
    constructor(name, age) {
      this.name = name
      this.age = age
    }
    // 公有的方法  
    say() {
      console.log(this.name, this.age)
    }
  }

  class LS extends Person {
    constructor(name, age, job) {
      super(name, age)
      this.job = job
    }
  }

  const ls = new LS('李四', 20, 'web前端')
  console.log(ls.name, ls.age) // 李四 20
  console.log(ls.job) // web前端

4. 说一下JS原生事件如何绑定

JS原生绑定事件主要为三种:

  • html事件处理程序
  • DOM0级事件处理程序
  • DOM2级事件处理程序
  1. html事件处理程序 html事件现在早已不用了,就是在html各种标签上直接添加事件,类似于css的行内样式,缺点是不好维护,因为散落在标签中,也就是耦合度太高。例如:
  <button onclick=”事件处理函数”>点我</button>
  1. DOM0级事件,目前在PC端用的还是比较多的绑定事件方式,兼容性也好,主要是先获取dom元素,然后直接给dom元素添加事件。例如:
  var btn=document.getElementById(‘id元素’)
    btn.onclick=function() {
    //要处理的事件逻辑
  }
	// DOM0事件如何移除呢?很简单:btn.onclick=null;置为空就行
  • 优点:兼容性好
  • 缺点:只支持冒泡,不支持捕获
  1. DOM2级事件,移动端用的比较多,也有很多优点,提供了专门的绑定和移除方法。例如:
  var btn=document.getElementById(‘id元素’)
  //绑定事件
  btn.addEventListener(‘click’,绑定的事件处理函数名,false)
  //移除事件
  btn.removeEventListener(‘click’,要移除的事件处理函数名,false)

  • 优点:支持给个元素绑定多个相同事件,支持冒泡和捕获事件机制。

5. 说一下JS原生常用dom操作方法?

  1. 查找:
  • getElementByid
  • getElementsByTagName
  • querySelector
  • querySelectorAll
  1. 插入:
  • appendChild
  • insertBefore
  1. 删除:
  • removeChild
  1. 克隆:
  • cloneNode
  1. 设置和获取属性:
  • setAttribute(“属性名”,”值”),getAttibute(“属性名”)

6. 说一下ES6新增特性?

  1. 变量部分新增 let 和const声明关键字, 实现变量的块级作用域.
  2. 新增箭头函数, 箭头函数this的指向是固定的. (react中有体现)
  3. 新增扩展运算符(...), 用来接收入参, 展开数组/json等.
  4. 新增模板字符串(${变量名})
  5. 新增json的序列化JSON.stringify(json) 和反序列化JSON.parse(jsonStr)
  6. 更好的面向对象, 新增 class (类) constructor(构造函数) extends (继承) super (超类) 来支持面向对象开发.
  7. 模块化
  8. 异步操作 promise
  9. babel编译, ES6 在一些低版本的浏览器下不兼容了, 可以通过 babel编译, 将含有ES6代码的js文件, 编译成普通的js代码文件。

7. JS设计模式有哪些(单例模式观察者模式等)

JS设计模式有很多,但我知道的有单例模式,观察者模式

  • 单例模式:就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
  • 观察者模式: 观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。 总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。

8. 说一下你对JS面向对象的理解

  1. 定义
  • 对象:万物皆对象
  • 类:对对象的细分
  • 实例:类中具体的事物
  1. 以es面向对象为例 es6通过class关键字来定义类
 class Person {
    // 私有的属性定义在构造函数中  
    constructor(name, age) {
      this.name = name
      this.age = age
    }
    // 公有的方法  
    say() {
      console.log(this.name, this.age)
    }
  }

  class LS extends Person {
    constructor(name, age, job) {
      super(name, age)
      this.job = job
    }
  }

  const ls = new LS('李四', 20, 'web前端')
  console.log(ls.name, ls.age) // 李四 20
  console.log(ls.job) // web前端

  • 特点:
    • es6的class 有了专门的构造器 constructor,构造器和类分开了,定义方法:不需要原型来定义了,直接再类里面定义方法。
    • es的继承:关键字:extends 继承、super 超级。
    • constructor  构造器里面的this是指向创建的实例对象。
    • 方法里面的this都是指向,谁调用这个方法就指向谁。

9. 说一下JS数组常用方法(至少6个)

在开发中,数组使用频率很频繁,JS数组常用方法有:push,pop,unshift,shift,splice,join,concat,forEach,filter,map,sort,some,every好多,不过都是平时开发中很常用的方法,大家可以补充一点儿es6的。

10. 说一下JS数组内置遍历方法有哪些和区别

JS数组内置遍历(遍历就是循环的意思)方法主要有:

  • forEach 这个方法是为了取代for循环遍历数组的,返回值为undefined。

      let arrInfo = [4, 6, 6, 8, 5, 7, 87]
      // item:代码遍历的每一项,
      // index:代表遍历的每项的索引,
      // arr:代表数组本身
      arrInfo.forEach((item, index, arr) => {
        // 遍历逻辑
      })
    
  • filter 是一个过滤遍历的方法,如果返回条件为true,则返回满足条件为true的新数组。

      let arrInfo = [4, 16, 6, 8, 45, 7, 87]
      let resultArr = arrInfo.filter((item, index, arr) => {
        // 例如返回数组每项值大于9的数组
        return item > 9
      })
    
  • map 这个map方法主要对数组的复杂逻辑处理时用的多,特别是react中遍历数据,也经常用到,写法和forEach类似。

  • some 这个some方法用于只要数组中至少存在一个满足条件的结果,返回值就为true,否则返回fasel, 写法和forEach类似。

  • every 这个every方法用于数组中每一项都得满足条件时,才返回true,否则返回false, 写法和forEach类似。

11. 说一下JS作用域和作用域链

JS作用域也就是JS识别变量的范围,作用域链也就是JS查找变量的顺序。

  • 作用域:JS作用域主要包括全局作用域、局部作用域和ES6的块级作用域

    • 全局作用域:也就是定义在window下的变量范围,在任何地方都可以访问。
    • 局部作用域:是只在函数内部定义的变量范围。
    • 块级作用域:简单来说用let和const在任意的代码块中定义的变量都认为是块级作用域中的变量,例如在for循环中用let定义的变量,在if语句中用let定义的变量等等。
    • 注:尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对bug查找不利。
  • 作用域链:所谓的作用域链就是由最内部的作用域往最外部,查找变量的过程.形成的链条就是作用域链。

12. 说一下从输入URL到页面加载完中间发生了什么?

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回需要的数据
  5. 浏览器解析渲染页面
  6. 连接结束
  • 输入了一个域名,域名要通过DNS解析找到这个域名对应的服务器地址(ip),通过TCP请求链接服务,通过WEB服务器(apache)返回数据,浏览器根据返回数据构建DOM树,通过css渲染引擎及js解析引擎将页面渲染出来,关闭tcp连接。

13. 说一下JS事件代理(也称事件委托)是什么?

JS事件代理就是通过给父级元素(例如:ul)绑定事件,不给子级元素(例如:li)绑定事件,然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,在原生js里面是通过event对象的。

  • targe属性实现

      var ul = document.querySelector("ul");
      ul.onclick = function(e){//e指event,事件对象
      var target = e.target || e.srcElement; //target获取触发事件的目标(li)
        if(target.nodeName.toLowerCase() == 'li'){//目标(li)节点名转小写字母,不转的话是大写字母
          alert(target.innerHTML)
        }
      }
    
    
  • jq方式 jq方式实现相对而言简单 $(“ul”).on(“click”,“li”,function(){//事件逻辑}) 其中第二个参数指的是触发事件的具体目标,特别是给动态添加的元素绑定事件,这个特别起作用。

14. 说一下JS数据类型有哪些?

  • 基本数据类型:number,string,Boolean,null,undefined,symbol(ES6新增)
  • 复合类型:Object,function

15. 说一下call,apply,bind区别

call,apply,bind主要作用都是改变this指向的,但使用上略有区别,说一下区别:

  • call和apply的主要区别是在传递参数上不同,call后面传递的参数是以逗号的形式分开的,apply传递的参数是数组形式。
  • bind返回的是一个函数形式,如果要执行,则后面要再加一个小括号 例如:bind(obj,参数1,参数2,)(),bind只能以逗号分隔形式,不能是数组形式。

二、git相关

1. 你们公司项目是如何管理的?

主要通过git来进行项目版本控制的。

2. 说几个git常用命令?

  • git add
  • git status
  • git commit –m
  • git pus
  • git pull等

3. 说一下多人操作同一个文件,如果出现冲突该如何解决?

当遇到多人协作修改同一个文件时出现冲突,我先将远程文件先git pull下来,手动修改冲突代码后,再git add ,git commit,git push再上传到远程仓库。如果pull也pull不下来提示冲突的话,可以先通过git stash暂存下来,然后再pull拉取,然后git stash pop,取出原来写的,手动修改,然后提交。

三、Ajax相关

1. 说一下你是如何与后端进行数据交互的?

和后端通过ajax来进行数据交互的,通过统一制定的接口文档,来实现前后端高效开发,如果接口文档不能详细说明,或者接口文档上的参数请求不出数据,我会主动和后端工程师沟通,直到完成跟接口相关的业务开发。当然这其中为了验证一些接口问题,会用到一些辅助工具,比方说,runapi这种在线测试工具。

2. 如果后端数据接口没有准备好,你是如何工作的?

如果后端接口还没有准备好,我会和后端工程师沟通,通过制定接口返回数据的格式,然后前端通过一些mock数据的工具(上家公司使用的是easymock,贼简单)来批量生成假数据,可以让前端和后端同时开发,而无需等待后端数据接口写好再开发,这样提升项目整体的开发效率。

3. 说一下你对ajax的同源策略的理解?

ajax同源策略是因为安全的考虑,ajax不允许访问不同域名下的资源即所谓同源策略的意思。

4. 说一下什么情况下会产生跨域及产生跨域的解决方案和实现原理?

产生跨域的情况有:不同协议,不同域名,不同端口以及域名和ip地址的访问都会产生跨域。

5. 跨域的解决方案目前有三种主流解决方案

跨域是浏览器做出的限制,和后端没关系。

  1. jsonp jsonp实现原理:主要是利用动态创建script标签请求后端接口地址,然后传递callback参数,后端接收callback,后端经过数据处理,返回callback函数调用的形式,callback中的参数就是json。
  2. 代理(前端代理和后端代理) 前端代理我在vue中主要是通过vue脚手架中的config中的index文件来配置的,其中有个proxyTable来配置跨域的。
  3. CORS CORS全称叫跨域资源共享,主要是后台工程师设置后端代码来达到前端跨域请求的。
  • 注:现在主流框架都是用代理和CORS跨域实现的。

6. 说一下原生ajax的交互过程(即流程)

  1. 先创建XHR对象即XMLHttpRequest()。
  2. 然后open准备发送,open中有三个参数一是提交方式get和post,二是接口地址,三是同步和异步。
  3. 第三步是用send发送。
  4. 第四步再发送的过程中通过onreadystatechange来监听接收的回调函数,可以通过判断readyState==4和status==200来判断是否成功返回,然后通过responseText接收成功返回的数据。

7. 说一下你对同步和异步的理解

  1. 同步即sync,形象的说就是代码一行行执行,前面代码和请求没有执行完,后面的代码和请求就不会被执行,

    • 缺点:容易导致代码阻塞。
    • 优点:程序员容易理解(因为代码从上往下一行行执行,强调顺序)。
  2. 异步:即async,形象的说就是代码可以在当前程序没有执行完,也可以执行后面的代码。

    • 缺点:程序员不易理解(因为不是按顺序执行的)。
    • 优点:可以解决代码阻塞问题,提升代码执行效率和性能。
  3. 异步解决方案主要有三个:

    • 回调函数
    • promise(重点掌握)
    • generator(了解)
    • async和await(重点掌握)

8. ajax缓存如何解决?

通过在文件名后面添加随机数(也称为文件指纹)来实现,主要原理是浏览器对访问过的文件,首先会检测第二次请求的文件url在浏览器是否缓存过,如果缓存过就使用,否则如果是一个新的文件url,则从服务器重新请求。

9. 说一下javaScript原生,jQuery,vue,react,小程序ajax与后台交互主要用的什么技术

  • javaScript原生Ajax:用的是XMLHttpRequest对象。
  • jQuery中的Ajax: .ajax(),.ajax(),.getJSON(),.get(),.get(),.post()等。
  • vue中的Ajax: vue-resource(vue1.x中用),axios(主流)。
  • 微信小程序Ajax: 用的是小程序内置的wx.request()写法和jquery的$.ajax()类似,参数url,success,data,method,fail等。

10. 说一下你对http状态码的了解多少?

  • 1xx(临时响应)  表示临时响应并需要请求者继续执行操作的状态代码.
  • 2xx (成功)  表示成功处理了请求的状态码。 常见的2开头的状态码有:
    • 200 – 服务器成功返回网页。
  • 3xx (重定向)  表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向 常见的3字开头的状态码有:
    • 301   (永久移动)  请求的网页已永久移动到新位置。 服务器返回此响应时,会自动将请求者转到新位置。 
    • 302   (临时移动)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。  
    • 304   (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。 
  • 4xx(请求错误) 这些状态代码表示请求可能出错,妨碍了服务器的处理。 常见的4字开头的状态有:
    • 404 – 请求的网页不存在
  • 5xx(服务器错误)  这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。 常见的以5开头的状态码有:
    • 500(服务器内部错误)服务器遇到错误,无法完成请求。  
    • 503(服务不可用)服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

11. 深拷贝,浅拷贝

用es6的Object.assign({},{})进行对象合并,如果是数组可以用es6的Array.from,或是es6的扩展运算符...arr,如果使用es5需要用循环来做浅拷贝,如果是深拷贝需要用递归的形式来实现.当然也可以使用JSON.parse(JSON.stringify(对象))的方式实现深拷贝。