30

2020疫情期间前端妹子面试小记(附答案)

 3 years ago
source link: https://zhuanlan.zhihu.com/p/273341773
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
VjMvIbu.jpg!mobile

背景

我18年本科毕业,年前已有换工作想法,一直没有付诸行动,疫情爆发后回到老家,年后开始找工作,对于自己水平不是很清楚,之前找工作一直都挺顺利的。大学毕业没有留在实习单位继续做前端开发,那时候三大框架已经开始流行了,实习公司用的还是jquery。回老家学了一个月的车和vue、小程序后,凭着自学做的项目找了一周工作入职第一家公司。面试经验比较少,想着多面面看看对方公司的反馈。年前还没开始准备就面了腾讯,终止于二面,所以说还是要好好复习再面大厂。

面的岗位以两三年为主,虽然我是1.5年经验,但有的岗位年限要求没那么严格也可以试试,尤其写的优秀者可放宽年限要求。面了四五家,有上市公司,也有小公司,都是远程面试。

html、css部分

如何理解html语义化

html语义化是指从代码上展示页面的结构,而不是从最终视觉上来表现结构。

表现形式

html5新标签:

  • header-页眉、footer-页脚、aside-附属信息、nav-导航链接、section、article,
  • caption-表格标题、thead-表头、tbody-表格内容、tfoot-表尾
  • h1~h6,作为标题使用,且重要性递减

作用

  • 有利于构建良好的html架构,有利于搜索引擎建立索引和抓取
  • 页面结构清晰,有利于代码的维护和管理
  • 有利于不同设备(盲人阅读器、屏幕阅读器)的解析

px,em,rem区别

px 相对长度单位,是相当于显示器的分辨率而言的

em 相对长度单位,相对父元素的字体大小而言的

rem 相对长度单位,相对html根元素的字体大小而言的,css3新增元素

盒子模型

IE盒子模型 宽度=内容宽度+padding *2+border *2

w3c盒子模型 宽度=内容宽度

通过box-sizing切换,默认为content-box(w3c盒子模型),border-box时为IE盒子模型

BFC

块级格式化上下文,让BFC里面的元素与外面元素隔离,使里外元素的定位不会相互影响。触发条件:

  • 根元素
  • overflow不为visible
  • float
  • position:absolute或fixed
  • display:inline-block或table

应用:

  • 防止垂直方向margin重叠
  • 不和浮动元素重叠
  • 清除元素内部浮动

target和currentTarget区别

target是事件的真正目标

currentTarget是事件处理程序注册的元素

document.ready和window.onload区别

document.ready是dom树加载后执行,而window.onload是整个页面资源加载完后执行,所以document.ready比window.onload先执行

事件流

DOM2事件流分为三个部分:事件捕获、处于目标、事件冒泡。

事件冒泡 是指事件从执行的元素开始往上层遍历执行

事件捕获 是指事件从根元素开始从外向里执行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="btn">click Me</button>  
<script>
let btn=document.getElementById('btn');
btn.onclick=fucntion(e){
    console.log(e)
}
</script>
</body>
</html>

点击按钮后,事件冒泡的执行顺序是:button->body->html->document

事件捕获的执行顺序则相反:document->html->body->button

doctype作用,严格模式和混合模式的区别

<!doctype>声明位于文档的最前面,在html之前显示。用于告诉浏览器的解析器,用什么文档类型规范来解析文档。严格模式默认用浏览器支持的最高版本解析,混合模式以宽松的向后兼容的方式解析,doctype不存在或格式不正确时会让文档以混杂模式呈现

水平垂直居中

//方法一
display:flex;
justify-content:center;
align-items:center;

//方法二
display:table;
vertical-align:center;

//方法三:适用于已知宽高且父元素定位不为static
postion:absolute;
width:100px;
height:100px;
top:50%;
left:50%;
margin:-50px 0 0 -50px;

//方法四
position:absolute;
top:50%;
left:50%;
transform:translateY(-50%) translateX(-50%);

//方法五:适用于行内元素
display:inline-block;
width:100px;
height:100px;
text-align:center;
line-height:100px;

//方法六:适用于块级元素
display:block;
height:100px;
margin:0 auto;
line-height:100px;

回流和重绘区别

回流:当渲染树中元素尺寸、结构或者某些属性发生变化时,浏览器重新渲染部分或全部页面的情况叫回流。下列元素改变引发回流:

  • getBoundingClientRect()
  • scrollTo()
  • scrollIntoView()或者scrollIntoViewIfneeded
  • clientTop、clientLeft、clientWidth、clientHeight
  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • getComputedStyle()

重绘:当页面中元素样式变化不会改变它在文档流中的位置时,即不会使元素的几何属性发生变化,浏览器会将新样式赋给它并重新绘制页面(比如color、backgroundColor)

频繁回流和重绘会引起性能问题

避免方法:

  • 减少table布局使用
  • 减少css表达式的使用(如calc())
  • 减少DOM操作,用documentFragment代替
  • 将元素设为display:none;操作结束后把它显示回来,因为display:none不会引发回流重绘
  • 避免频繁读取会引发回流重绘的元素,如果需要最好是缓存起来
  • 对复杂动画元素使用绝对定位,使它脱离文档流
  • 减少使用行内样式

js部分

setTimeout、setInterval区别

两者都是定时器,设定一个150ms后执行的定时器不代表150ms后定时器会执行,它表示代码在150ms内会被加入队列,如果这个时间点队列没有其他逻辑在执行,表面上看代码在精确时间执行了。在队列中有其他逻辑时,代码等待时间会超过150ms

setTimeout 只执行一次

setInterval 执行多次,属于重复定时器

防抖节流

节流:多次触发事件时,一段时间内保证只调用一次。以动画为例,人眼中一秒播放超过24张图片就会形成动画,假设有100张图片,我们一秒播放100张过于浪费,一秒播放24张就够了。

防抖:持续触发事件后,时间段内没有再触发事件,才调用一次。以坐电梯为例,电梯10s运行一次。如果快要运行时进来一个人,则重新计时。

//节流
function throttle(fn,delay) {
  let timer=null
  return function () {
    if(!timer){
      timer=setTimeout(()=>{
        fn.call(this,arguments)
        timer=null
      },delay)
    }
  }
}
//防抖
function debounce(fn,delay) {
  let timer=null
  return function () {
    if(timer){
      clearTimeout(timer)
    }
    timer=setTimeout(()=>{
      fn.call(this,arguments)
    },delay)
  }
}

深浅拷贝

浅拷贝:

  • concat()
  • Object.assign()
  • slice()
  • 手写
function shallowCopy(obj){
  if(typeof obj==='function'&& obj!==null){
    let cloneObj=Array.isArray(obj)?[]:{}
    for(let prop in obj){
      if(obj.hasOwnProperty(prop)){
        cloneObj[prop]=obj[prop]
      }
    }
    return cloneObj
  }
  else{
    return obj
  }
}

深拷贝:

  • JSON.stringfy(JSON.parse())

上面的方法不能解决循环引用,也不能显示函数或undefined

  • 手写深拷贝
var deepClone=(obj,map=new WeakMap())=>{
  if(map.get(obj)){
    return obj
  }

  let newObj;
  if(typeof obj==='object'&& obj!==null){
    map.set(obj,true)
    newObj=Array.isArray(obj)?[]:{};
    for(let item in obj){
      if(obj.hasOwnProperty(item)){
        newObj[item]=deepClone(obj[item])
    }
  }
    return newObj;
  }
  else {
    return obj;
  }
};

继承

我觉得没啥好说的,红宝书里介绍的挺详细了,不过es6的class多了个extends

//原型链继承
function Parent(){
    this.property=true;
}
Parent.prototype.getValue=function(){
    return this.property;
}
function Son(){
    this.subProperty=false;
}
Son.prototype=new Parent();
let instance=new Son();

原型链继承继承了原型的属性和方法,但是有几个缺点:

  1. 原型链中包括引用类型的值时,会被所有实例共享
  2. 不能实现子类向超类的构造函数中添加属性

由此产生了借用构造函数继承,解决了原型链继承的缺点,它自身又有缺点:不能实现函数复用

//借用构造函数继承
function Parent(){
    this.property=true;
}
function Son(){
    Parent.call(this);
}
复制代码//组合继承
function Parent(){
    this.property=true;
    this.colors=['red','purple','orange']
}
Parent.prototype.getPro=function(){
    return this.property;
}
function Son(property,name){
    Parent.call(this,property);
    this.name=name;
}
Son.prototype=new Parent()

组合继承避免了原型链和借用构造函数的缺陷,是最常用的继承之一

//原型继承
var a = {
  friends : ["yuki","sakura"]
};
var b = Object.create(a);
b.friends.push("ruby");
var c = Object.create(a);
c.friends.push("lemon");
alert(a.friends);//"yuki,sakura,ruby,lemon"

原型继承缺点跟原型链继承一样,也是引用类型的属性会被所有实例共享

//寄生式继承,可以类比设计模式的工厂模式
function createAnother(obj){
  var clone = object(obj);
  clone.sayHi = function(){
    console.log("hello");
  };
  return clone;
}

寄生式继承不能做到函数复用

//寄生组合式继承
function Parent(name){
    this.name=name;
    this.colors=['red','white','gray']
}
Parent.prototype.getName=function(name){
    this.name=name
}
function Son(name,age){
    Parent.call(this,name);//第二次调用Parent()
    this.age=age;
}
Son.prototype=new Parent()//第一次调用Parent()
Son.prototype.constructor=Son;
Son.prototype.getAge=function(){
    return this.age;
}

寄生组合式继承避免了在子实例上创建多余的属性,又能保持原型链不变,还能正常使用instanceof()和isPrototypeOf(),是最理想的继承方式。

es6方法的继承:通过extends实现

class Parent(){
    constructor(){}
}
class Son extends Parent(){
    constructor(){
        super()
    }
}

了解的es6新特性

说的越多越好,比如Promise,箭头函数、数组扩展:includes()、find()、findIndex()...,Symbol、Map、Set... 我就不罗嗦了,看阮一峰的ES6就好了

es6的class的es5的类有什么区别

1.es6 class内部定义的方法都是不可枚举的
2.es6 class必须用new调用
3.es6 class不存在变量提升
4.es6 class默认使用严格模式
5.es6 class子类必须在父类的构造函数中调用super(),才有this对象;而es5是先有子类的this,再调用

数组去重

//方法一 使用ES6的Set
function filterArr(arr) {
  return new Set(arr)
}
//方法二:filter+indexOf()判断,如果数字不是第一次出现则被过滤
function filterArr2(arr){
  let newArr=arr.filter((item,index)=>{
    return arr.indexOf(item)===index
  })
  console.log(newArr)
}
//方法三:双重for循环
function filterArr3(arr){
  let isRepeat,newArr=[];
  for(let i=0;i<arr.length;i++){
    isRepeat=false
    for(let j=i+1;j<arr.length;j++){
      if(arr[i]===arr[j]){
        isRepeat=true
        break
      }
    }
    if(!isRepeat){
      newArr.push(arr[i])
    }
  }
  return newArr
}
//方法四:哈希表
function filterArr4(arr){
  let seen={}
  return arr.filter(function (item) {
    return seen.hasOwnProperty(item)?false:(seen[item]=true)
  });
}
//方法五:sort排序,相同的数字会排在相邻n个位置
function filterArr5(arr){
  let lastArr=[]
  const newArr=arr.sort((a,b)=>{
    return a-b
  })
  for(let i=0;i<newArr.length;i++){
    if(newArr[i]!==newArr[i+1]){
      lastArr.push(newArr[i])
    }
  }
  return lastArr
}

this

this绑定函数的执行上下文,谁调用它,它就指向谁。分为默认绑定、显式绑定、隐式绑定、apply/call/bind绑定、new绑定和箭头函数绑定

默认绑定:严格模式下this指向undefined,非严格模式this指向window

call、apply、bind都可以改变this的指向,但是apply接收参数数组,call接收的是参数列表 bind接收的是参数列表,但是apply和call调用就执行,bind需要手动执行

箭头函数绑定:箭头函数的this是父作用域的this,不是调用时的this,其他方法的this是动态的,而箭头函数的this是静态的

window.name='a'
const obj={
    name:'b',
    age:22,
    getName:()=>{
        console.log(this)
        console.log(this.name)
    },
    getAge:function(){
        setTimeout(()=>{
            console.log(this.age)
        })
    }
}
obj.getName();//window a
obj.getAge();//22

优先级:箭头函数>new绑定>显示绑定/apply/bind/call>隐式绑定>默认绑定

箭头函数和普通函数区别

  • 箭头函数没有prototype,所以箭头函数本身没有this
  • 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this
  • 箭头函数没有arguments,普通函数有
  • 使用new调用箭头函数会报错
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

new的原理

var obj={};
obj._proto_=F.prototype;
F.call(obj);
  1. 创建一个空对象
  2. this变量引用该对象,同时还继承了这个函数的原型
  3. 属性和方法被加入到引用的对象里
  4. 新创建的对象由this引用,最后隐式返回this

浏览器事件循环和node事件循环

浏览器事件循环:

  1. 同步任务在主线程执行,在主线程外还有个任务队列用于存放异步任务
  2. 主线程的同步任务执行完毕,异步任务入栈,进入主线程执行
  3. 上述的两个步骤循环,形成eventloop事件循环 浏览器的事件循环又跟宏任务和微任务有关,两者都属于异步任务。

js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入任务队列,再执行微任务,将微任务放入任务队列,他俩进入的不是同一个任务队列。往外读取的时候先从微任务里拿这个回调函数,然后再从宏任务的任务队列上拿宏任务的回调函数

宏任务:

  • script
  • 定时器 setTimeout setInterval setImmediate

微任务:

  • promise
  • process.nextTick()
  • MutationObserver

node事件循环:

  1. timer阶段
  2. I/O 异常回调阶段
  3. 空闲预备阶段
  4. poll阶段
  5. check阶段
  6. 关闭事件的回调阶段

手写系列

远程面试没考手写,但我觉得还是要会

手写promise

我的是简易版本

function myPromise(executor) {
    let self=this;
    self.status='pending';
    self.value=undefined;
    self.reason=undefined;

    function resolve(value) {
        if(self.status==='pending'){
            self.value=value
            self.status="resolved"
        }
    }
    function reject(reason) {
        if(self.status==='pending'){
            self.reason=reason
            self.status=status
        }
    }
    try{
        executor(resolve,reject)
    }
    catch (e) {
        reject(e)
    }
}

手写bind

Function.prototype.myBind=function(context,...args){
   const fn=this
    args=args?args:[]
    return function newFn(...newFnArgs) {
        if(this instanceof newFn){
            return new fn(...args,...newFnArgs)
        }
        return  fn.apply(context,[...args,...newFnArgs])
    }
}

手写call、apply

Function.prototype.myCall=function(context,...args){
    context=context||window
    args=args?args:[]
    const key=Symbol()
    context[key]=this
    const result=context[key](...args)//通过隐式绑定的方式调用函数
    delete context[key]//删除添加的属性
    return result//返回函数调用的返回值
}

Function.prototype.myApply=function(context,args){
    context=context||window
    args=args||[]
    const key=Symbol()
    context[key]=this
    const result=context[key](...args)
    delete context[key]
    return result
}

vue部分

vue的双向绑定原理

vue的双向绑定是通过数据劫持和发布者-订阅者模式实现的,数据劫持又是通过Object.defineProperty()实现的

Object.defineProperty(data,'a',{
    enumerable:true,//是否可枚举
    writable:true,//是否可写
    configurable:true,//是否可配置
    get(){
        return this.a//读取data对象的a属性时,触发get方法
    },
    set(val){
        this.a=val;//修改data对象的a属性时,触发set方法
    }
})
mvvm的数据变化更新视图,是通过Object.defineProperty()实现的;视图更新变化数据,是通过事件监听实现的。

发布者-订阅者的实现过程:
1. 实现一个监听器Observer,劫持并监听所有属性,如果有变化,就通知订阅者
2. 实现一个订阅者Watcher,收到属性的变化通知并执行响应的函数,从而更新视图
3. 实现一个解析器Compiler,可以扫描并解析每个节点的相关指令,初始化模板数据和对应的订阅器

vue的指令有哪些

v-if 用于条件渲染
v-show 用于条件渲染,两者的区别请参考下一个问题
v-for 用于列表渲染
v-on  监听事件
v-bind 动态绑定
v-html 渲染html元素
v-model 数据双向绑定

v-if和v-show区别

v-if 是惰性的,只有条件为真时才会切换,为假时什么也不做。确保切换过程中的事件监听器和子组件适当的被销毁和重建,适用于运行条件很少改变的场景。v-show 不管条件是否为真,总是会被渲染,适用于频繁切换的场景

v-for和v-if为什么不能放于同一级

v-for优先级高于v-if,放于同级可能会重复渲染两次v-if,建议把v-for放于v-if所在的外层元素

nextTick

原理:eventloop事件循环

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

v-for中key的原理

key 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。不指定key时,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试
就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

vue的生命周期

1.beforeCreate
 初始化界面前
2.created
  初始化界面后,拿到data,props,methods、computed和watch
3.beforeMount
 渲染dom前
4.mounted
 渲染dom后,拿到$el
5.beforeUpdate
 更新前
6.updated
 更新后,拿到更新后的dom
7.beforeDestroy
 卸载组件前
8.destroyed
 卸载组件后
9.activated
 被 keep-alive 缓存的组件激活时调用
10.deactivated
 被 keep-alive 缓存的组件停用时调用
11. errorCaptured
 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例
 以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

父子组件通信

父传子:prop

子传父:$emit

父子通信:
+ eventbus
+ vuex
+ ref结合$parent或 $children

跨层级较多:provide/inject

computed和watch区别

computed用于计算属性,只有它依赖的值改变才会触发,且这个值有缓存

watch用于监听一个属性的变化,属性变化就会触发

vnode的diff算法原理

虚拟dom是对真实dom的一种映射,新旧Vnode比较同层级的节点,然后根据两者的差异只更新有差异的部分,生成新的视图,而不是对树进行逐层搜素遍历,因此时间复杂度是O(n)。虚拟dom可以减少页面的回流和重绘,提升性能

webpack相关

webpack运行流程

分为初始化、编译、输出三个阶段.

初始化

  1. 从配置文件和shell文件读取、合并参数;
  2. 加载plugin
  3. 实例化compiler

编译

  1. 从entry发出,针对每个module串行调用对应loader翻译文件内容
  2. 找到module依赖的module,递归进行编译处理

输出

  1. 把编译后module组合成chunk
  2. 把chunk转换成文件,输出到文件系统

webpack性能优化

优化可以从两个方面考虑,一个是优化开发体验,一个是优化输出质量。

优化开发体验

  • 缩小文件搜索范围。涉及到webpack如何处理导入文件,不再赘述,不会的可以自行搜索。由于loader对文件转换操作很耗时,应该尽量减少loader处理的文件,可以使用include命中需要处理的文件,缩小命中范围。
  • 使用DllPlugin,提升构建速度
  • 使用happyPack开启多线程
  • 使用UglifyJs压缩代码(只支持es5),uglifyes支持es6,两个插件不能同时开启。使用ParalellUgifyPlugin开启多个子进程压缩,既支持UglifyJs又支持uglifyes
  • 使用自动刷新:监听到代码改变后,自动编译构建可运行代码并刷新页面
  • 开启模块热替换:在不刷新网页的同时实现实时预览

优化输出质量 减少首屏加载时间

  • 区分环境
  • 压缩代码
  • CDN加速
  • 使用Tree shaking
  • 提取公共代码
  • 按需加载

提升流畅度,即代码性能:

  • 使用PrePack优化代码运行时的效率
  • 开启Scope Hoisting,让webpack打包出来的代码更小、运行更快

loader的原理

loader能把源文件翻译成新的结果,一个文件可以链式经过多个loader编译。以处理scss文件为例:

  • sass-loader把scss转成css
  • css-loader找出css中的依赖,压缩资源
  • style-loader把css转换成脚本加载的JavaScript代码

plugin原理

plugin用于扩展webpack功能。

  • webpack启动后,在读取配置时会先执行new BasicPlugin(options)初始化一个BasicPlugin获得它的实例
  • 调用BasicPlugin.apply(compiler)给插件实例传递compiler对象
  • 插件实例获取compiler对象后,通过compiler.plugin(事件名,回调函数)监听webpack广播出的事例

http相关

url从输入到输出的过程1. 构建请求
构建请求行,请求行包括请求方法、请求http版本、URI
Get/Http/1.0
2. 查找强缓存
检查强缓存,命中则直接使用,否则检查协商缓存
3. DNS解析
域名与IP地址映射
4.建立tcp连接
chrome限制同一域名下最多6个tcp连接
+ 通过三次握手建立连接
三次握手过程:
1.客户端向服务器发送连接请求,传递一个数据包syn,此时客户端处于SYN_SEND状态
2.服务器接收syn报文后,会以自己的syn报文作为应答,传递数据包syn+ack,此时服务器处于SYN-REVD状态
3.客户端接收syn报文后,发送一个数据包ack,此时客户端处于ESTABLISHED状态,双方已建立连接
+ 进行数据传输
+ 通过四次挥手断开连接
四次挥手过程:
1. 客户端发送一个FIN报文,报文中指定一个序列号,此时客户端处于FIN_WAIT1状态,等待服务器确认
2. 服务器接收到FIN后,会发送ACK确认报文,表明已经收到客户端报文,此时服务端处于CLOSE_WAIT2状态
3. 服务器发送FIN,告诉客户端想断开连接,此时服务端处于LAST_CHECK阶段
4. 客户端收到FIN后,一样发送一个ACK作为应答,此时客户端处于TIME_WAIT阶段。需要过一段时间确认服务端收到自己的ACK报文
后才会进入CLOSED状态
5.发送http请求
6.网络响应
7.浏览器解析和渲染
分为构建dom树、样式计算、生成布局树。
8.生成布局
触发回流和重绘

介绍下半连接队列

服务器第一次接收到客户端的SYN后,会处于SYN-REVD阶段,此时双方还没有建立完全的连接,
服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称为半连接队列

已经完成三次握手并建立连接,就叫全连接队列

http和https区别

  1. http基于TCP/IP协议,https基于SSL/TLS协议
  2. http默认端口号为80,https默认端口号为443
  3. https安全性更强,需要CA证书
  4. https可以防止运营商劫持

可以介绍https的通信过程,涉及对称加密和非对称加密

tcp和udp区别

  1. tcp只能一对一通信,udp可以一对一、一对多、多对多通信,即支持多播和广播
  2. tcp首部开销消耗32字节,udp仅消耗8个字节
  3. tcp适合对数据安全性要求高的场景,比如文件传输,udp适合对数据实时性要求高的场景,比如视频通话、语音通话
  4. tcp是有状态连接,udp是无状态的连接
  5. tcp-可靠传输 udp-不可靠传输
  6. tcp-面向字节流 udp-面向报文

tcp怎么保证可靠性

1. 超时重传机制
2. 对失序数据进行重排序
3. 应答机制
4. 滑动窗口
5. 拥塞控制

http请求有哪几种

http1.0 :get、post、head

http1.1 :put、delete、connect、trace、options

简单请求 :

  1. 请求method只能是get、post、head
  2. 请求头只能是accept/accept-language/content-language/content-Type
  3. content-Type只能是text/plain、multipart/form-data、application/x-www-form-urlencoded

介绍http1.0|http1.1|http2.0

http1.0 :

  1. 完成连接即断开,导致重新慢启动和三次握手
  2. 线头阻塞,导致请求间相互影响

http1.1

  1. 用keep-alive支持长连接
  2. 用host字段指定对应的虚拟站点
  3. 新增功能: 断点续传、身份认证、状态管理、cache缓存->cache-control、expires、last-modified、etag

http2.0

  1. 二进制分帧层:应用层和传输层
  2. header头部压缩
  3. 服务端推送
  4. 多路复用

二进制分帧层可以扩展到计算机网络的OSI参考模型

客户端缓存

客户端缓存分为cookie、localStorage、sessionStorage、indexedDB,网上有关的文章很多,就不详细说了

浏览器缓存

强缓存

不向http发送请求,返回状态码304.

检查强缓存有两个字段: http1.0使用expires,代表过期时间,但是服务器时间和客户端时间可能不一致。为了弥补这个缺点,http1.1使用cache-control的max-age字段,cache-control有多个指令

  • public 允许客户端和代理服务器缓存
  • private 允许客户端缓存
  • no-store 不使用缓存
  • no-cache 使用协商缓存 两个字段都存在,cache-control优先级高于expires

协商缓存 向http发送请求,返回状态码200

检查协商缓存有两个字段: http1.0使用last-modified,即最后修改时间。

  1. 在浏览器向服务器发送请求后,服务器会在响应头上加上这个字段
  2. 浏览器接收后,如果再次请求,会在请求头上携带If-Modified-Since
  3. 服务器拿到If-Modified-Since字段后,会和服务器中该资源的最后修改时间对比,如果请求头中这个值小于最后修改时间,更新资源;否则返回304,告诉浏览器直接使用缓存

http1.1使用etag,etag是服务器根据当前文件内容,给文件生成的唯一标识,只要内容改变,这个值就会变。etag优先级高于last-modifed

缓存位置,按优先级从高到低分别是:

  • service worker
  • memory cache
  • disk cache
  • push cache

http状态码

列举一些常见状态码即可

200-请求成功
301-永久重定向
302和307-临时重定向
400-当前请求不能被服务器理解或请求参数有误
401-请求需要认证或认证失败
403-服务器禁止访问
404-资源未找到
405-方法未允许
500-内部服务器错误
502-网关错误
503-服务器处于超负载或停机维护

了解nginx吗

ngnix是个高性能反向代理服务器,有以下作用:

  • 解决跨域
  • 请求过滤
  • 配置gzip
  • 负载均衡
  • 静态资源服务器

ngnix解决跨域的原理:

  • 把后端域名设为前端服务的域名,然后设置相应的location拦截前端需要跨域的请求,最后将请求代理回服务端的域名
  • ngnix实现负载均衡的策略:轮询、最小链接数、最快响应时间

web安全

xss

跨站脚本攻击,指攻击者在网页上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时, 对客户端浏览器进行控制或获取用户隐私数据的方式

防范

  1. httpOnly防止截取cookie
  2. 用户输入检查
  3. 用户输出检查
  4. 利用CSP(浏览器的内容安全策略)

csrf

跨站请求伪造,劫持受信任用户向服务器发送非预期请求的方式。

防范

  1. 验证码
  2. referer check
  3. 增加token验证

二次封装axios

1.新建一个axios对象,定义好字段并设置默认值,比如超时时间、请求头
2.定义过滤字符串方法,过滤服务端为空字符串或null的属性
3.请求拦截器调用过滤字符串方法,遍历url上的字段,如果为数组或对象转为JSON对象
4.响应拦截器捕获错误,根据http状态码进行不同的处理,比如401跳转登陆页面,403返回您没有权限,502返回系统正在升级中,请稍后重试,
504返回系统超时,并弹出对应的消息提示框。消息提示框自定义

axios调用流程

查看axios源码,axios调用流程实质是Axios.prototype.request的实现,调用过程如下:
1.判断传入参数config是否是字符串,是则设置url,否则设置config为对象
2.调用mergeConfig方法,合并默认参数和用户传入的参数
3.如果设置了请求方法,将其转为小写,否则设置请求方法为get
4.将用户设置的请求和响应拦截器、发送请求的dispatchRequest组成promise链,最后返回promise实例。
这一步保证先执行请求拦截器,再执行请求,最后执行响应拦截器

聊项目

挑一到两个比较有代表性的项目讲,可以用SWOT方法,简要介绍项目的背景,项目的主要的技术难点,如何解决的,项目做完后的效果。我主要讲的是怎么推动Jenkins自动化部署在前端组里的应用

hr面

一般都是聊规划聊跳槽原因,掌握一些常见聊天技巧一般来说不会挂人,除非(薪资没谈拢)。。。

后记

数据结构和算法也有问到了,规模不大的公司一般排序问的比较多(插冒归基快选堆希),前端会这8种排序就行了。如果想面更好的公司,还得把数据结构和算法复习好了。

这篇文章如果对你有帮助的话,不妨点个赞再走。

原作者姓名:lyllovelemon

原出处:掘金

原文链接: 2020疫情期间一年半前端面试小记(附答案)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK