2021前端面试复习

5,955 阅读40分钟

一个在教育行业苦干两年的前端菜鸟,离职后就在准备复习面试,记录一下这次前端复习过程。前端知识是在是太多,持续更新中... 希望对真正找工作的你有所帮助,文末我整理了一下面试文章,感兴趣的可以看看,尤其是2021高频前端面试题汇总系列,帮助很大。文章有所不足的地方还指明。

2021前端面试复习

HTML5、CSS3

HTML5

HTML5新增特性

  • 语义化标签:headernavfootersection...

  • 媒体标签:audio音频、video视频

  • 表单类型属性:emailnumber时间控件color颜色拾取器placeholderautofocus自动获取焦点...

  • cavas绘图

  • web存储:localStoragesessionStorage

行内元素、块级元素有哪些

行内元素:aspanimginput...

块级元素:divullioldtdhliph1-6

iframe的优缺点

优点

  • 原封不动的吧嵌入网页展示出来
  • 增加代码的可重用
  • 用来加载速度较慢的内容

缺点

  • iframe阻塞onload事件加载
  • 网页内容无法被搜索引擎识别,对SEO不友好
  • 会产生很多页面,不利于管理

canvas和SVG的区别

canvas:通过javaScript来绘制2D图形,是逐像素进行渲染

  • 依赖分辨率
  • 不支持事件处理
  • 能够以.png或.jpg的格式进行保存
  • 适合做游戏

SVG:基于XML描述的2D图形语言,是矢量图

  • 不依赖分辨率
  • 支持事件处理
  • 适合做大型区域渲染

回流重绘

回流当DOM变化影响了元素,比如元素的尺寸、布局、显示隐藏等改变了,需要重写构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这个时候一定会发生回流。

重绘当一个元素的外观发生变化,但是没有改变布局,重新渲染元素的外观。比如background-colorcolor

回流必将引起重绘,而重绘不一定会引起回流

如何避免回流重绘:

  • 避免使用table布局
  • 尽可能在DOM树的最末端改变class
  • 避免设置多层内联样式
  • 开启GPU加速
  • 将动画效果应用到position属性为absolute或者fixed的元素上

src和href的区别

srcsrc指向外部资源的位置,将指向的内容嵌入到文档中当前标签所在的位置,如js脚本、img图片、iframe

href用于在当前文档和引用资源之间确立联系,一般是用在linka等元素

CSS3

CSS3新增特性

  • 新增CSS选择器、伪类
  • 特效:text-shadowbox-shadow
  • 渐变:gradient
  • 旋转过度:transformtranstion
  • 动画:animation

盒模型

盒模型都是有四部分组成:contentpaddingbordermargin

标准盒模型和IE盒模型的区别在于设置widthheight时,对应的范围不同

  • 标准盒模型的widthheight只包含了content

  • IE盒模型的widthheight包含了bordermarginpadding

通过修改元素的box-sizing属性来改变元素的盒模型

  • box-sizeing: content-box表示标准盒模型(默认值)
  • box-sizeing: border-box表示IE盒模型(怪异盒模型)

trastion和aniamtion的区别

transtion:属于过度属性,强调过度,需要一个事件进行触发(如鼠标进入、离开)类似flash的补间动画,设置一个开始帧和结束帧

aniamtion:属于动画属性,它的实现不需要触发事件,设定好后可自动执行,且可以循环播放。也是类似补间动画,但是可以设置多个关键帧

元素水平垂直居中

  • 绝对定位:先将元素的左上角通过top:50%left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心。

    .parent {    
      position: relative;
    } 
    .child {    
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%,-50%);
    }
    
  • 绝对定位:设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。

    .parent {
        position: relative;
    }
     
    .child {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        margin: auto;
    }
    
  • 使用flex弹性盒子布局,通过align-items:centerjustify-content:center设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。

    .parent {
        display: flex;
        justify-content:center;
        align-items:center;
    }
    

p、em、rem的区别

  • px 固定像素单位,不能随其它元素的变化而变化
  • em是相对于父元素的单位,会随着父元素变化而变化
  • rem是相对于根元素html,它会随着html元素变化而变化

rem常用在移动端项目,设置根元素的fong-size,其它元素会随着根元素的变化而变化,从而达到不同手机屏幕的自适应大小。通常会配合postcss-pxtorem插件进行使用

如何解决1px问题

  • 直接写0.5px
  • 利用伪元素,先放大再缩小
  • 使用viewport缩放来解决

什么是BFC布局,如何创建BFC布局?

BFC布局为块格式化上下文(Block Formatting Context,BFC), 是CSS布局的一个概念,里面的元素不会影响到外面的元素。

创建BFC

  • 元素设置浮动:float有值并不为空
  • 元素设置绝对定位: position(absolute、fixed)
  • overfilow值为:hiddenautoscroll
  • display值为:inline-blocktable-celltable-captionflex

BFC作用

  • 解决margin重叠问题:由于BFC是一个独立的区域,内部元素和外部元素互不影响,将两个元素变为BFC,就解决了margin重叠问题
  • 创建自适应两栏布局:可以用来创建自适应两栏布局,左边宽高固定,右边宽度自适应。
  • 解决高度塌陷问题:在子元素设置浮动后,父元素会发生高度的塌陷,也就是父元素的高度为0解决这个问题,只需要将父元素变成一个BFC。

link和@import的区别

  • link是HTML提供的标签,不仅可以加载CSS文件,还可以定义RSS、rel连接属性等
  • @import是CSS提供等语法规则,只有导入样式表带作用。
  • link标签引入的CSS被同时加载,而@import引入的CSS将在页面加载完毕后被加载
  • @import是CSS2.1才有的语法,存在兼容性,而link作为HTML标签不存在兼容性问题

CSS选择器优先级

  • @important
  • 内联样式
  • ID选择器
  • 类选择器/属性选择器/伪类选择器
  • 元素选择器/伪元素选择器
  • 关系选择器/通配符选择器

JS基础

基础数据类型

stringnumberbooleanobjectfunctionundefinednullsymbol

nullundefined的区别:null表示对是一个空的对象(object)、undefined是申明了但没赋值,在使用typeof检测类型时,null为objectundefinedundefined

null == undefined // true

null === undefined //false

值类型(基本数据类型):stringnumberbooleanundefinednullsymbol

引用类型:objectfunctionarray

值类型和引用类型区别:

  • 值类型保存在中,占用空间固定,当一个方法执行时,每个方法都会创建自己的内存栈,方法中定义的变量会存放在这个内存栈中。当方法执行结束时,这个内存栈也会被销毁。所以,栈中存储的是基础变量。而引用变量存储在栈中是指向堆中的数组或对象的引用地址。这也就是为何修改引用类型总会影响到其它指向这个地址的引用变量。
  • 值类型可以使用typeof进行数据类型检测
  • 引用类型保存在中,占用空间不固定。创建对象会保存到堆内存中,这个内存不回随着方法结束而销毁,因为这个对象还可能被另外一个变量所引用,只有当一个对象没有被任何变量引用时,系统的垃圾回收机制会将它回收。
  • 引用类型使用instanceof检测数据类型
  • 使用new()方法构造出来的对象是引用类型

闭包

闭包就是能够读取到其它函数内部变量的函数,创建一个最简单的闭包,就是在一个函数内部创建另外一个函数,创建的函数可以访问到当前函数的局部变量。

闭包优点:

  • 创建全局私有变量,避免变量全局污染
  • 可以实现封装、缓存等

闭包缺点:

  • 创建的变量不能被回收,容易消耗内存,使用不当会导致内存溢出

变量提升、作用域、作用域链

变量提升

js代码在解析的时候,会将所有的变量函数,提升到代码的最上面。变量提升,提升的只是变量申明,并不会吧变量赋值提升上来

console.log(i) // 4
var i = 4
作用域

作用域是一个变量或函数的可访问范围,作用域控制着变量或函数的可见性和生命周期

  1. 全局作用域:可以全局访问

    • 最外层函数和最外层定义的变量拥有全局作用域
    • window上的对象属性方法拥有全局作用域
    • 为定义直接复制的变量自动申明拥有全局作用域
    • 过多的全局作用域变量会导致变量全局污染,命名冲突
  2. 函数作用域:只能在函数中访问使用哦

    • 在函数中定义的变量,都只能在内部使用,外部无法访问
    • 内层作用域可以访问外层,外层不能访问内存作用域
  3. ES6中的块级作用域:只在代码块中访问使用

    • 使用ES6中新增的letconst什么的变量,具备块级作用域,块级作用域可以在函数中创建(由{}包裹的代码都是块级作用域)

    • letconst申明的变量不会变量提升,const也不能重复申明

    • 块级作用域主要用来解决由变量提升导致的变量覆盖问题

      var i = 3
      function fnc() {
        console.log(i);
        var i = 6;
      }
      fnc() // undefined
      
作用域链

变量在指定的作用域中没有找到,会依次向上一层作用域进行查找,直到全局作用域。这个查找的过程被称为作用域链。

call、apply、bind区别

  • 都可以用作改变this指向
  • callapply的区别在于传参,callbind都是传入对象。apply传入一个数组。
  • callapply改变this指向后会立即执行函数,bind在改变this后返回一个函数,不会立即执行函数,需要手动调用。

new操作符干了什么操作

  1. 创建一个空对象

  2. 设置原型,将对象的原型设置到函数的prototype对象上

  3. 改变this指向,将this指向该对象,并执行构造函数。

  4. 判断函数的返回类型,如果是值类型,返回创建的对象。如果是引用类型,返回这个引用类型的对象。

原型、原型链

  • 原型: 每个对象在内部初始化的时候,都会初始化一个prototype原型属性 ,而对象的 _proto_属性,指向它的原型对象。

  • 原型链: 当我们访问英国对象属性时,如果这个属性不存在,那么就会去它的原型对象上进行查找,而这个原型对象又会有自己的原型,会这样一直查找,知道找到顶级对象object为止。这个查找的过程被称为原型对象。

继承

原型链继承

利用对象的原型链,将子类Son.prototype指向父类的构造函数创建出来的实例对象new Person()

🌰

function Person() {
  ...
};
  
function Son() {
  ....
};

// 关键代码
Son.prototype = new Person();

优点:

  • 子类可以继承父类构造函数、原型上的属性方法

缺点:

  • 父类引用类型的实例对象被共享,容易造成修改的混乱。
  • 创建子类的时候不能向父类传参
构造函数继承

利用.call()或者.apply()方法,在子类中借用父类的构造函数,初始化父类构造函数。

🌰

function Person(name) {
  ...
};
  
function Son(name) {
	// 关键代码  
  Person.call(this, name)
  ...
}

优点:

  • 子类在继承父类时,可以向父类构造函数中传参。
  • 不会造成子类势力之间引用属性共享。

缺点:

  • 只能继承父类构造函数中的属性方法,无法访问原型上的方法。
  • 每个子类都会创建一个父类副本
组合继承

组合继承,将原型链继承和构造函数继承融合在一起。

🌰

function Person(name) {
  ...
};
  
function Son(name) {
  // 借用构造函数继承关键代码
	Person.call(this, name);
};

// 原型链式继承关键代码
Son.prototype = new Person();
// 将子类的构造函数指向自己
Son.prototype.constructon = Son;

优点:

  • 结合前面两种继承方式的优点,子类的实例可以访问到父类原型上的属性方法
  • 子类的实例之间不会被共享

缺点:

  • 调用了两次父类构造函数
原型式继承

用函数包装一个对象,返回这个函数的调用(也就是ES5的Object.create的模拟实现),将传入的对象作为创建对象的原型

🌰

function create(obj) {
  // 创建一个空的的构造函数
	function F() {};
  
  // 将空的构造函数原型指向传递进来的对象
  F.prototype = obj;
  
  // 返回一个实例对象
  return new F();
}


const obj = {
  name: 'zs',
  ...
};

const newObj1 = create(obj);
const newObj2 = create(obj);

优缺点和原型链式继承一样,引用类型还是会被共享。

寄生式继承

在原型式继承基础上,在函数内部来做增强函数,返回对象。

🌰

function createObj(obj){
  // 获取继承的子类对象,也就是上面的create方法实现
  const newObj = Object.create(obj);
  
  // 函数增强
  newObj.say = function() {
    ...
  }
    
  // 返回对象
  return newObj;
}

类似原型式继承,但在原有的基础上可以自定义属性方法,依旧没有解决引用值被共享问题。(跟借用构造函数模式一样,每次创建对象都会创建一遍方法。)

寄生组合式继承

结合寄生继承和组合式继承优缺点,组合式继承缺点为调用了两次父类构造函数,优点为解决了引用值被共享问题。而寄生式继承缺点为没有解决引用值被共享问题,只要将两者结合就得到完美的继承方式。

🌰

function createObj(son, parent) {
  // 寄生式继承,利用父类构造函数的原型对象创建出一个新的对象,解决组合式继承创建两个父类问题
  const newObj = Objcet.create(parent.prototype);
  
  // 将新对象的constructor构造函数执行子类
	newObj.constructor = son;
  
  // 将子类构造函数的原型指向心的  原型式继承
  son.protoytype = newObj; 
  
}

function Person(name) {
  ...
};

function Son(name) {
  // 构造函数继承
  Person.call(this, name);
  ...
};
  
  
// 实现继承
createObj(Son, Person)
// 更简洁的方式  在组合式继承的基础上  直接将son的原型通过API指向person的原型
Son.prototype = Object.create(Person.prototype);

const obj1 = new Son('zx');
const obj2 = new Son('lw');

深拷贝、浅拷贝

浅拷贝

浅拷贝只是复制对象的值类型,通过Object.assign或者扩展运算符即可实现

深拷贝

通过递归实现

function deepClone(obj) {
  // 判断是否为对象
  if (!obj || typeof obj !=='object') return obj;
  
  // 根据obj类型创建数组还是对象
  let newObj = obj instanceof Object ? {} : [];
  
	// 循环遍历obj,处理子元素为对象,递归拷贝
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      newOb[key] = deepClone(obj[key])
    };
  };
  
  return newObj;
};

事件循环机制EventLoop(浏览器)

栈、队列理解
  • 栈(Stack)中的任务后进先出,js中的执行栈是一个存储函数的栈结构,栈中的任务执行遵循先进后出的原则依次执行。
  • 队列(Queue)中的任务先进先出,js运行时创建一个任务队列,用来处理列表(事件)和待执行的回调函数
宏观任务、微观任务

js中的任务分为两种:宏观任务(MacroTask|Task)微观任务(MicorTask)

  • 宏任务:script全部代码setTimeoutsetIntervalI/OUI Rendering
  • 微任务:Promise.thenProcess.nexTick(Node独有)MutationObserver
同步任务、异步任务

js有一个主线程和一个执行栈(调用栈),所有的任务都会放到执行栈中等待被主线程执行。

js代码在执行时,所有函数都会压入执行栈中。同步任务会按照后进先出原则依次执行,直到执行栈清空。异步任务会在异步任务有了结果后,将注册的回掉函数放入异步任务队列中,等待主线程空闲后(执行栈中的同步任务执行完毕)

异步队列中的任务又分为宏观任务微观任务,当当前执行栈清空后,处理异步队列中的任务时,首先会判断是否有微观任务可执行,如果有就将微观任务压入执行栈中执行。当微观队列中的任务在执行栈被执行完毕,再来异步队列中将宏观任务放入执行栈。

简单的来说:

  1. 执行同步代码,这属于宏观任务
  2. 所有代码执行完毕,执行栈清空,执行异步队列中任务
  3. 异步队列中,先执行微观任务
  4. 微观任务执行完毕,再执行宏观任务

节流防抖

节流(throttle):在n秒内只允许执行一次,

防抖(debounce):在n秒内多次触发但不执行,而是在n秒后执行,如果n秒内触发则重新计算。

事件冒泡、事件委托

事件发生的三个阶段:捕获阶段目标阶段冒泡阶段

  • 事件冒泡:在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发事件。
    • 如何阻止:
      • 普通浏览器:event.stopPropagation()
      • IE浏览器:event.cancelBubble = true;
  • 事件委托:利用浏览器事件冒泡机制。事件在冒泡的过程中会传到父节点,并且父节点可以通过事件对象获取到目标节点,可以吧子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件
    • 事件委托可以不必要为每一个子节点都绑定监听事件,减少内存上的消耗。
    • 使用事件委托还可以实现事件的动态绑定,比如新增一个子节点,并不需要为此单独增加一个监听事件,可以由父元素中的监听函数来处理。

对DOM元素进行增删改查

 document.createElement()
element.removeAttribute()
element.removeChild()
element.innerHTML()
element.setAttribute()
getElementById()
getElementsByClassName()
getElementsByTagName()
querySelector()
querySelectorAll()

ajax、axios、fetch区别

ajax

  • 基于原生的XHR开发
  • 本身针对MVC编程,不符合现在前端MVVM潮流

axios

  • 从浏览器中创建XMLHttpRequest

  • 支持promise

  • 支持请求拦击和响应拦截

  • 从node.js创建http请求

  • 客服端支持防止CSRF/XSRF

fetch

  • 浏览器原生实现的请求方式,ajax的替代品
  • 只对网络请求报错,对400、500都当做成功的请求,需要封装
  • fetch默认不会带cookie,需要添加配置项
  • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费
  • fetch没有办法原生监测请求的进度,而XHR可以

ES6

var、let、const区别

  • var声明变量可以重复声明,而let不可以
  • var是不受限于块级作用域,而let受限
  • var存在变量提升,letconst不存在变量提升
  • const声明的变量不可变
  • const声明之后必须赋值,否则会报错

Promise

Promise是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

它有三种状态

  • pending初始状态
  • fulfilled操作成功
  • rejected操作失败。

Promise状态改变只有两种可能

  • pending------>fulfilled
  • pending------>rejected

Promise构造函数接收一个参数和一个带有resolvereject参数的回调函数。

  • resolve的作用是将Promise状态从pending变为fulfilled,在异步操作成功时调用,并将异步结果返回,作为参数传递出去
  • reject的作用是将Promise状态从pending变为rejected,在异步操作失败后,将异步操作错误的结果,作为参数传递出去

Promise实例方法

  • promise.then() 对应resolve成功的处理
  • promise.catch()对应reject失败的处理
  • promise.call()将多个Promise实例,包装成一个新的Promise实例,返回的实例就是普通的Promise。有一个失败,代表该Primise失败。当所有的子Promise完成,返回值时全部值的数组
  • promise.race()类似promise.all(),区别在于有任意一个完成就算完成(例如:将异步和定时器放在一起,设置请求超时)

箭头函数和普通函数的区别

  • 箭头函数时匿名函数,不能作为构造函数,不能使用new
  • 箭头函数不绑定arguments
  • 箭头函数没有自己的this,将所在的上下文的this作为自己的this
  • 没有prototype
  • call()applay()bind()方法不能改变箭头函数中的this指向

forEach和map的区别

  • forEach返回值是undefined,不可以链式调用
  • map()返回一个新的数组,不改变原数组。forEach改变原数组。

Set、Map的区别

Set

  • 创建: new Set([1, 1, 2, 3, 3, 4, 2])
  • add(value):添加某个值,返回Set结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

Map

  • set(key, val):Map中添加新元素
  • get(key): 通过键值查找特定的数值并返回
  • has(key): 判断Map对象中是否有Key所对应的值,有返回true,否则返回false
  • delete(key): 通过键值从Map中移除对应的数据
  • clear(): 将这个Map中的所有元素删除

区别

  • Map是一种键值对的集合,和对象不同的是,键可以是任意值
  • Map可以遍历,可以和各种数据格式转换
  • Set是类似数组的一种的数据结构,但在Set中没有重复的值

谈谈你对ES6对理解

  • 解构赋值
  • 扩展运算符
  • 模版字符串
  • 箭头函数
  • async/await
  • Class
  • 引入Moldule语法
  • class

Vue

基础知识

MVVM

MVVM是一种软件架构模式,在vue中 M 代表model层(数据模型),负责数据模型。V代表View层(视图层),VM代表ViewModel(视图模型),它是ModelView之间的桥梁,数据会绑定到viewModel层,并自动将数据渲染到页面层,视图变化时会通知viewModel更新数据

Vue生命周期

创建前后:

  • beforeCreated(创建前): 数据观测和初始化事件还未开始,不能访问datacomputedwatchmethods上的数据方法。
  • created(创建后):实例创建完成,可以访问datacomputedwatchmethods上的数据方法,但此时渲染节点还未挂在到DOM上,所以不能访问。

挂载前后:

  • beforeMounted(挂载前): Vue实例还未挂在到页面html上,此时可以发起服务器请求
  • mounted(挂载后):Vue实例已经挂在完毕,可以操作DOM

更新前后:

  • beforeUpdate(更新前): 数据更新之前调用,还未渲染页面
  • updated(更新后):DOM重新渲染,此时数据和界面都是新的。

销毁前后:

  • beforeDestoryed(销毁前):实例销毁前调用,这时候能够获取到this
  • destoryed(销毁后):实例销毁后调用,实例完全被销毁。

watch和computed的区别

watch:监听属性,用来监听数据的变化,没有缓存,当监听到的数据发生变化时都会执行毁掉函数

computed:计算属性,被监听的值有缓存,只有它依赖的属性值发生变化后,下一次获取computed的值时才会重新计算computed的值。(只有依赖发生变化后才会重新计算)

v-for中key的作用

key是为了更高效的对比虚拟DOM中的每个节点是否相同,避免页面更新时重复更新节点

v-if和v-show的区别

v-if元素不可见 删除dom元素

v-show元素可见 通过设置元素的display:none样式属性

组件中的data为什么是一个函数

因为对象是一个引用类型,如果data时一个对象的情况下会造成多个组件共用一个data,data为一个函数,每个组件都会有自己的私有数据空间,不会干扰其他组件的运行。

Vue组件通信

父子组件

父传子

  • props
  • $children
  • $refs

子传父

  • $emit
  • $parent
兄弟组件
  • provied
  • inject
  • eventBus
  • Vuex

Vuex的基本使用

Vuex用于vue中的数据状态管理,有五种属性:

  1. stateVuex的基本数据,用于存储变量
  2. getter:从state派生出来的数据,当相遇state的计算属性,在这里可以对state数据进行过滤、筛选等操作
  3. mutation:提交更新state数据的方法
  4. action:和mutation功能相似,都是用来提交更新,但action提交的是mutation,而不是直接变更数据,并且action可以包含异步操作
  5. module:模块化Vuex,每个模块都有自己的statemutationactoiongetter
mutation和action的区别
  • mutation更专注于修改state,必须是同步执行。
  • action提交的是mutation,而不是直接更新数据,可以是异步的。
  • action可以整合多个mutation

Vuex和localstory的区别

  • Vuex存储在内存中,页面关闭刷新就会消失。而localstorage存储在本地,读取内存比读取硬盘速度要快
  • Vuex应用于组件之间的传值,localstorage主要用于不同页面之间的传递
  • Vuex是响应式的,localstorage需要刷新

路由守卫

  • 全局前置钩子:beforeEachbeforeResolveafterEach
  • 路由独享守卫:beforeEnter
  • 组件内钩子:beforeRouterEnterbeforeRouterUpdatebeforeRouterLeave

hash和history的区别

hash

hash模式是vue开发中的默认模式,地址栏URL携带##后为路由。

原理是通过onhashchange()事件监听路由hash的变化,这个好处就是当hash值发生变化,不需要向后端发起请求,window就可以监听事件的改变,并按照规则加载项对应的代码。除此之外,hash值的变化对应的URL都会被浏览器记录下来,这样就能实现浏览器历史页面的前进后退。

history

vue还提供history模式,在history模式下URL中没有#,相比hash模式更加好看。但是需要后台配置支持。

history的原理是利用HTML5中hostory提供的pushStatereplaceState这两个API,这两个API记录了浏览器历史栈,并且当在修改URL时不会触发页面刷新和后台请求。

动态路由

定义方式
  • params传参
    • 路由配置: /index/:id
    • 路由跳转:this.$router.push({name: 'index', params: {id: "zs"}});
    • 路由参数获取:this.params.id
    • 最后形成的路由:/index/zs
  • query传参
    • 路由配置:/index正常的路由配置
    • 路由跳转:this.$rouetr.push({path: 'index', query:{id: "zs"}});
    • 路由参数获取:this.query.id
    • 最后形成的路由:/index?id=zs
routerrouter 和route
  • $router是指整个路由对象,可以使用this.$router.push({name: ;index'})进行页面跳转
  • $route时指当前页面的路由对象,可以使用this.$route.parmas.id来获取当前路由对象传递进来的参数

Vue性能优化

原理知识

双向绑定原理

data在初始化的时候,会实例化一个Observe类,在它会将data数据进行递归遍历,并且通过definereactive方法,这个方法通过Object.defineProperty方法,给每个值添加上一个getter和一个setter。在数据读取的时候会触发getter进行依赖(Watcher)收集,当数据改变时,会触发setter,对刚刚收集的依赖进行触发,并且更新watcher通知视图进行渲染。

依赖收集

依赖收集发生在defineReactive()方法中,在方法内new Dep()实例化一个Dep()实例,然后在getter中通过dep.depend()方法对数据依赖进行收集,然后在settter中通过dep.notify()通知更新。整个Dep其实就是一个观察者,吧收集的依赖存储起来,在需要的时候进行调用。在收集数据依赖的时候,会为数据创建一个Watcher,当数据发生改变通知每个Watcher,由Wathcer进行更新渲染。

Object.defineProperty()数据劫持缺陷

该方法只能监听到数据的修改,监听不到数据的新增和删除。vue2中会对数组的新增删除方法push、pop、shift、unshift、splice、sort、reserve通过重写的形式,在拦截里面进行手动收集触发依赖更新。

在vue2中,需要数据里添加或删除时,使用vue.$set/vue.$delete进行操作。

在Vue3中,改用proxy对对象进行代理,返回一个代理对象,只需要操作新对象就可以。

双向绑定原理

Vue双向绑定是一个指令v-model,可以将数据动态绑定到视图上,同时视图中变化也可以改变改值。他的本质是 v-bindv-on 的语法糖。在 ⼀个组件上使⽤ v-model ,默认会为组件绑定名为 value 的 prop 和名为 input 的事件。

nextTick的实现

vue中的nextTick是浏览器eventLoop是应用。nextTick是将回调函数放到一个异步队列中,保证在异步更新DOM的watcher后面,从而获取到更新后的DOM。

Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;

怎么理解 vue 中的虚拟 DOM

虚拟DOM,就是用一个JS对象来描述一个DOM节点。Vue是数据驱动视图的,数据发生变化视图就要随之更新,在更新视图的时候难免要操作DOM,而操作真实DOM又是非常耗费性能的,这是因为浏览器的标准就把 DOM 设计的非常复杂,所以一个真正的 DOM 元素是非常庞大的。VNode类中包含了描述一个真实DOM节点所需要的一系列属性,tag表示节点的标签名,text表示节点中包含的文本,children表示该节点包含的子节点等。

模版编译原理

模版编译主要过程:template ---> ast ---> render,分别对象三个方法

  • parse 函数解析 template
  • optimize 函数优化静态内容
  • generate 函数创建 render 函数字符串

调用parse方法,将template转化为AST(抽象语法树),AST定义了三种类型,一种html标签,一种文本,一种插值表达式,并且通过 children 这个字段层层嵌套形成了树状的结构。

optimize方法对AST树进行静态内容优化,分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。

generateAST抽象语法树编译成 render字符串,最后通过new Function(render)生成可执行的render函数

diff算法逻辑

diff算法发生在视图更新阶段,也就是当数据发生变化的时候,diff会对新就虚拟DOM进行对比,只渲染有变化的部分。

当数据发生变化的时候,依赖对应的watcher会通知更新,生成一个新的vnode,新的vnode会去和旧的vnode进行对比更新。

整个更新的过程就是调用path函数,主要做了三件事:

  • 创建节点:新的vnode中有而旧的vnode中的节点,在旧vnode中进行创建
  • 删除节点:新的vnode中没有二旧的vnode中有,在旧的vnode中删除
  • 更新节点:新的vnode和旧的vnode中都有,以新的vnode位主,更新旧的vnode

new Vue的流程

合并配置,调用一些初始化函数,触发生命周期钩子函数,调用$mount开启下一个阶段。

keep-live原理

keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

通过include、exclude来匹配和排除缓存,max定义缓存的上限。

keep-alive内部其实是一个函数式组件,没有template标签。在render中通过获取组件的nameinclude、exclude进行匹配。匹配不成功,则不需要进行缓存,直接返回该组件的Vnode。

匹配成功就进行缓存,获取组件的keythis.cache中进行查找,如果存在就直接将缓存的组件实例覆盖到当前的Vnode上,然后将当前组件的keykeys中进行删除,然后在push(key)添加到尾部,这样做是为了改变key当前的位置,也就实现了max功能。

不存在的话,就需要对组件进行缓存。将当前组件push(key)添加到尾部,然后再判断当前缓存的max是否超出指定个数,如果超出直接将第一个组件销毁(缓存淘汰策略LRU)。

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

性能优化

网站优化

  • 使用浏览器缓存机制
  • 资源懒加载、预加载
  • 骨架屏
  • 合理使用雪碧图、字体图标、

代码优化

  • 减少回流重绘
  • 减少DOM的操作
  • 节流防抖
  • 使用事件委托
  • 将CSS文件放在头部、js文件放在底部

请求优化

  • 使用CDN加速
  • 开启nginx,Gzip压缩
  • 使用强缓存、协商缓存
  • 减少、合并请求

webpack优化

  • 按需加载
  • 代码打包体积压缩
  • 移除console
  • 压缩图片、字体等本地资源
  • 分离css文件,单独进行打包

浏览器相关

跨域

浏览器的同源策略会导致跨域问题

同源策略

同源指的是:协议端口号域名必须一致。

同源策略的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作

解决跨域问题

  • CORS:服务器开启跨域资源共享
  • JSONP:利用JavaScript标签不存在跨域限制,只支持GET请求
  • Nginx:反向代理

本地存储

Cookie

  • 存储小,只有4k
  • 不同域之间不能共享
  • 不安全,容易被拦截

SessionStorage

  • 存储在内存中,体积相对较大
  • 页面关闭,数据会删除
  • 相对Cookie安全

LocalStorage

  • 体积大,可以存储更多内容
  • 生命周期长,需要手动删除
  • 存储在硬盘,不会像cookie一样被请求携带

从输入一个 URL 地址到浏览器完成渲染的整个过程

  1. 浏览器输入URL并回车
  2. 浏览器查找当前是否有缓存
  3. DNS解析URL得到IP
    1. 浏览器DNS缓存
    2. host文件缓存
  4. 建立TCP连接(三次握手)
  5. 发送HTTP请求
  6. 服务器处理请求,浏览器得到响应数据
  7. 浏览器解析渲染页面
    1. 解析DOM生成DOM
    2. 解析CSS生成CSS
    3. 合并DOM树和CSS树,生成渲染树
    4. 浏览器开始渲染页面(回流重绘发生在这个阶段)
  8. TCP连接关闭(四次挥手)

网络协议、安全相关

TCP、UDP协议

TCPUDP都是在传输层定义的两种传输协议。基于UDP协议传输不能保证数据准确无误的送达,但UDP不仅可以支持一对一的传输方式,还可以支持一对一、一对多等形式。也不需要像TCP一样建立连接,所以传输速度快。

TCP的目的是提供可靠的数据,并且需要在传输前建立连接(三次握手)。只支持一对一进行传输。

区别:

  • TCP协议可靠,UDP协议不可靠
  • TCP面向连接,UDP采用无连接
  • TCP可以保证数据顺序,UDP不能
  • TCP一对一传输,UDP可以一对多、多对一等形式

HTTP和HTTPS区别

  • HTTP是明文传输,不安全。HTTPS基于SSL进行加密传输,比较安全。
  • HTTPS需要CA证书,HTTP不需要。
  • HTTP端口为80HTTPS端口为443

HTTP状态码

  • 1XX: 请求正在处理
  • 2XX:正常状态码
    • 200 :请求处理成功
    • 201 : 请求成功并且服务器创建了新资源
    • 202 :服务器已经接收请求,但尚未处理
  • 3XXX:重定向状态
    • 301 :请求重定向
    • 302: 临时重定向
    • 303: 临时重定向,使用get请求新的url
    • 304:浏览器缓存相关
  • 4XX:错误状态码
    • 400: 服务器无法理解请求格式,需要修改请求内容后再次发起请求
    • 401: 请求未授权
    • 403: 禁止访问
    • 404: 服务器上无法找到请求资源
  • 5XX:服务器错误
    • 500: 服务端错误
    • 503: 服务器暂时无法处理请求

HTTP三次握手、四次挥手

三次握手

三次握手是在建立TCP连接时,客户端和服务端总共发送三个包。进行三次握手的主要目的就是为了确认双方的接受能力和发送能力都是正常的,为后面传输可靠数据做准备。

报文:

  • 序号:表示发送的数据字节流,确保TCP传输有序,对每个字节编号
  • 确认序号:发送方期待接收的下一序列号,接收成功后的数据字节序列号加 1。只有ACK=1时才有效。
  • ACK:确认序号的标志,ACK=1表示确认号有效,ACK=0表示报文不含确认序号信息
  • SYN:连接请求序号标志,用于建立连接,SYN=1表示请求连接
  • FIN:结束标志,用于释放连接,FIN=1表示关闭本方数据流

三次握手:

  1. 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。
  2. 务器收到客户端的 SYN 报文之后,会以自己的SYN报文作为应答,并且也是指定了自己的初始化序列号 ISN。同时会把客户端的 ISN + 1 作为ACK的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
  3. 客户端收到SYN报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。

四次挥手

  1. 客户端会发送一个FIN报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
  2. 服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
  3. 如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
  4. 客户端收到 FIN 之后,一样发送一个ACK报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

为什么需要四次挥手

因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

HTTP缓存

强缓存

使用强制缓存策略,如果缓存资源有效,就直接使用缓存资源,不需要向服务器发送请求。强制缓存通过两种方式来设置,在request headers中的Expires属性和Cache-Contorl属性。

Expires属性,指定资源的过期时间。在过期时间以内,改资源可以被缓存使用,不需要向浏览器发送请求。这个时间依赖于服务器时间,会存在服务器时间和客户端时间不一致。

Cache-Control属性:

  • private: 仅浏览器可以缓存
  • public:浏览器和代理服务器都可以缓存
  • max-age=xxx 过期时间,单位为秒
  • no-cache 不进行强缓存,但会有协商缓存
  • no-store 不强缓存,也不协商缓存

如果request header中,Cache- Control的值中有max-age=xxx,这个时候走强缓存。如果值为no-cache,表明没有命中,走协商缓存。如值为no-store,不使用缓存。

协商缓存

如果没有命中强制缓存,在设置协商缓存情况下,先向服务器发送一个请求,如果资源没有发生修改,则返回一个304的状态,让浏览器使用本地的缓存副本。如果资源发生修改,则返回修改后的资源。在request headers中的Etag属性和Last-Modified属性,来进行设置。其中,ETage优先于Last-Modified

命中协商缓存条件:

  • Cache-Control: no-cache
  • max-age时间过期

Last-Modified(文件的修改时间):

服务器在响应头中添加Last-Modified属性,来指出资源最后一次修改时间。当浏览器发起请求时,会在request headers中添加一个If-None-Match属性,值为上一次请求的资源返回的Last-Modified值。服务端会通过这个属性和资源最后一次修改时间进行对比,以此来判断资源是否修改。如果资源没有修改,请求返回304状态,客户端使用本地缓存。如果资源有修改,则返回修改的资源。

这种方式有一个缺点,Last-Modified标记的时间只能精确到秒。

ETag(文件改动):

同样在服务器返回资源的时候,在头信息中添加ETag属性,这个属性是资源的唯一标识符。当资源改变时,这个值也会改变。在一下次请求资源时,会在request headers中添加一个If-None-Match属性,值为上一次请求的资源返回的ETag值。服务端会通过这个属性和资源最后一次修改时间进行对比,以此来判断资源是否修改。这种方式比Last-Modified更加准确。

区别

  • 强缓存优先级高于协商缓存
  • 强缓存不需要发请求,协商缓存需要。
  • 强缓存返回的状态码为200,协商缓存返回304
  • ctrl+F5强制刷新会跳过所有缓存,而F5刷新跳过强缓存,但是会检查协商缓存。

POST和GET的区别

  • 传递的参数不同,POST传递的参数在request body中,GET传递的参数在url后拼接
  • POST相对GET请求安全
  • GET请求长度有限制,POST没有
  • GET请求会被浏览器主动缓存,POST不会,要手动设置
  • GET请求一般用于查询,POST一般用于提交某种信息进行某些修改操作

XSS、csrf攻击

XSS(跨站脚本攻击)

Xss(cross-site scripting) 是一种代码注入攻击,攻击者往Web页面里插入恶意 html 标签或者 javascript 代码。在骗取用户点击后获取用户,获取用户信息。

避免方式:

  • url参数通过encodeURIComponent方法进行转义
  • 尽量不使用InnerHtml插入HTML内容
  • 对用户输入的地方和变量都需要仔细检查长度和对 ”<”,”>”,”;”,”’” 等字符做过滤

CSRF(跨站请求伪造)

CSRFCross-site request forgery)攻击者盗用你的身份信息,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

避免方式:

  • 添加验证码验证
  • 使用token
    • 服务端给用户生成一个token,加密后传递给用户
    • 用户在提交请求时,需要携带这个token
    • 服务端验证token是否正确

前端工程化

webpack的loader和plugin的区别

loader

loader是导出一个函数的javascript模块,webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。如babel-loader、Css-loader、image-loader、url-loader、Saas-loader...

plugin

Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。如html-webpack-plugin、mini-css-extract-plugin、uglifyjs-webpack-plugin

手写系列🤮

节流防抖

new操作符

call、bind、apply实现

发布订阅模式

class EventEmitter {
  constructor() {
    // 事件列表
    this.eventList = {};
  }

  // 监听
  on(name, callBack) {
    // 以 name 为 key  创建容器   如果有容器就不用创建
    if (!this.eventList[name]) {
      this.eventList[name] = []
    }

    // 把事件放入容器
    this.eventList[name].push([callBack])
  };

  // 触发
  emit(name, data) {
    if (!this.eventList[name]) {
      return new Error('没有找到事件!')
    };

    // 从容器中取出事件进行调用
    this.eventList[name].forEach((item) => {
      item(data)
    })
  };

  // 只触发一次
  once(name, callBack) {
    if (!this.eventList[name]) return;
    // 利用off  在callBack执行后  关闭订阅
    function onceFn(callBack) {
      callBack();
      this.off(name, callBack);
    };
    this.on(name, onceFn)
  };

  // 关闭监听  若第二个参数没有  移除 name 下所有的事件
  off(name, callBack) {
    if (!this.eventList[name]) return;

    if (callBack) {
      // 只移除对应的callBack
      this.eventList[name] = this.eventList[name].filter((item) => {return item !== callBack});

      // name容器长度为0 直接删除整个 name 事件订阅发布
      if (this.eventList[name].length === 0) {
        delete this.eventList[name];
      };
    } else {
      // 没有 callBack  直接删除整个 name 事件订阅发布
      delete this.eventList[name];
    };

  };

};

函数柯里化实现

promise

实现一个队列

数组去重

深浅拷贝

交换两个变量的值

数组扁平化

输出结果

闭包

var name = "The Window";   
  var object = {   
    name : "My Object",   
    getNameFunc : function(){   
      return function(){   
        return this.name;   
     };   
    }   
};   
alert(object.getNameFunc()());  //"The Window"
function aaa(){
 var a=0;
 function bbb() {
  a++;
  alert(a);
 }
 return bbb
}
var ccc=aaa();
ccc();  //结果为1
ccc();  //结果为2
var ddd=aaa();
ddd();  //结果为1
ddd();  //结果为2

github 同步

参考:

2021」高频前端面试题

2021年我的前端面试准备

震惊!前端300基础面试题+答案、分类学习整理(良心制作)持续更新

高级知识点