应届前端面试题复习(持续更细中。。。。)如果有用的话,麻烦留个👍吧~

169 阅读14分钟

前端面试押题

HTML部分

1.如何理解HTML中的语义化标签

1 标签语义化有助于构建良好的HTML结构,有利于搜索引擎的建立索引、抓取,利于SEO。简单来说,是想在H1标签中匹配到的关键词和在div中匹配到的关键词搜索引擎会吧那个结果放在前面。

2 有利于构建清晰的结构,更加有利于团队的维护和开发

2.HTML5里面新增了哪些标签?

1.新增了语义化标签
<header>:头部标签
<nav>导航标签
<article>内容标签
<aside>侧边栏标签
<footer>尾部标签
2.新增了多媒体标签:可以很方便的在页面中嵌入音频和视频
<audio>
<video>
3.  还新增了vanvassvg用于绘画的元素

3.vanvas、svg的区别

html5 新增了canvas画布,canvas绘制的图片放大后会失真,而SVG可绘制矢量图形。

css押题

1.BFC是什么?

BFC是Block Formatting Context 是块级格式上下文。当一个元素形成了BFC的时候,那么它内部的元素产生的布局就不会影响到外部的元素。外部元素的布局也不会影响到内部的元素。BFC就像是一个隔离区域,和其他区域互相不影响。

2.创建BFC:

(1) 浮动元素 float

(2) 绝对定位的元素position=absolute

(3) display=inline-block

(4) overflow:hidden

常见应用:

(1) 清除浮动,解决高度塌陷:overflow:hidden

(2) 解决外边距合并

2.如何实现垂直居中和水平居中

水平居中:

对于行内元素,可以对父级元素添加text-align:center

若是块级元素(div p ul li )等等,独占一行,支持宽度和高度。 要想实现水平居中可以使用 margin:0 auto

垂直居中:

单行文本可以设置line-hight = 父高

若是行内块级元素,也就是给它设置了display:inline-block属性,这种方法针对一些img等行内元素,比较常用,vertical-align:middle

3.css选择器的优先级

选择器的特殊性值分为四个等级,如下:
​
(1)标签内选择符x,0,0,02)ID选择符0,x,0,03class选择符/属性选择符/伪类选择符 0,0,x,04)元素和伪元素选择符0,0,0,x

4.如何清楚浮动?

1.隔墙法:

//在浮动元素末尾添加空标签
<div style="clear:both"></div>

2.给父级添加overflow:hidden

3.:after伪元素给父元素添加 clearfix类

.clearfix:after{
       content: "";
       display: block;
       height: 0;
       clear:both;
       visibility: hidden;
     }

5.两种盒子模型的区别:

box-sizing="content-box";
这种盒子是只对content设置了宽高,但是元素真正的宽高还要加上margin border padding 
box-sizing="border-box";
它把整个盒子看成是一个整体。给整个盒子一个宽高。如果你还给盒子设置了额外的边距和边框。那么中间的content蓝色部分就要受到挤压,变小。

js押题

1. js 的数据类型

js总共有6中基本的数据类型,分别是number string boolean undefined null Symbol BigInt

symbol主要解决全局变量冲突的问题

引用类型有对象 数组 函数 等

2.原型链是什么

在js中我们使用构造函数来新建一个对象,每一个构造函数内部都有一个protype属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数所有实例共享的属性和方法。

那么我们如何获取到原型呢?

1.p.__proto__

2.p.constructor.protype

3.Object.getPrototypeOf(p)

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因。

3. this指向问题

// 1. 函数独立进行调用   this指向window
function test(){
  console.log(this)   
}
 test()   //这里直接打印widowvar a = 0
var obj = {
    a:2,
    foo: function(){
        console.log(this)    //输出obj  this指向obj
       
        function test(){
            console.log(this)
        } 
        test(); //输出window  函数是独立进行调用的   这个test()后面不打分号会报错因为下面是立即执行函数
       
        // 立即执行函数也是指向window
        (function(){
            console.log(this)  //输出window
        })();
        
        //闭包函数
        function test2(){
            console.log(this)
        }
        return test2
    }
}
obj.foo()
obj.foo()()//指向window obj.foo()执行完成相当于test2  obj.foo()()===test2() 相当于是独立调用
​
​
//2.隐式绑定(出现隐式丢失和参数赋值)
var a =0 
function foo(){
    console.log(this)
}
var obj = {
    a:2,
    foo:foo
}
var bar = obj.foo
bar()// this 指向window  注意函数是在哪里执行的
​
​
var a=0
function foo(){
    console.log(this)
}
function bar(fn){
   fn() 
}
vat obj = {
    a:2,
    foo:foo
} 
bar(obj.foo)  // 打印window   obj.foo作为实参传进fn  fn是独立调用执行的3.显式绑定 bind apply和call
4.箭头函数的this指向  箭头函数默认不会使用自己的this,而是会和外层的this保持一致,最外层的this就是window对象。而且箭头函数中的this指向不能用call进行修改
const obj = {
    a: () => {
        console.log(this)
    }
}
obj.a()  //打出来的是window5.setTimeout中的this指向
const obj = {
    a: function() {
        console.log(this)
        window.setTimeout(() => { 
            console.log(this) 
        }, 1000)
    }
}
obj.a.call(obj)  //第一个this是obj对象,第二个this还是obj对象
//函数obj.a没有使用箭头函数,因为它的this还是obj,而setTimeout里的函数使用了箭头函数,所以它会和外层的this保持一致,也是obj;如果setTimeout里的函数没有使用箭头函数,那么它打出来的应该是window对象。
setTimeout(function(){
 console.log(this)  // 这个输出window
})
​

4. new 做了什么?

1.创建了一个空的对象
2.将空的对象的原型,指向于构造函数的原型
3.将空对象作为构造函数的上下文(改变this指向)、
4.对构造函数有返回值的处理判断

自己实现一个new的函数:

1.创建一个新的对象Object.create(Fn.prototype)

2.改变this的指向

//构造函数
function Fun(age,name){
    this.age = age 
    this.name = name 
}
//自己的new函数
function mynew(fn,...args){
    let obj = Object.create(fn.prototype)
    let res  = fn.apply(obj,args)
    return res instanceof Object?res: obj;
    //如果没有人在构造函数里面写return得到话 可以直接返回obj
}
mynew(Fun,18,'zhangsan')

5.立即执行函数是什么?

可以用来模拟块级作用域模块化开发可以用来模拟块级作用域模块化开发

//写法:
(function(){
    
})();  //注意要打分号
//同时可以使用IIFE解决锁定参数值的问题
let divs = document.querySelectorAll('div')

//这样达不到目的  假如有3个第div  全部输出3 
for(var i=0;i<divs.length;i++){
    div[i].addEventListener('click',fucntion(){
     console.log(i)                        
    })
}
//通过IIFE来解决这个问题
let divs = document.querySelectorAll('div')

for(var i=0;i<divs.length;i++){
    div[i].addEventListener('click',(function(){
        console.log(i)
    })(i))
}
 //也可以使用let关键字 来解决这个问题
for(let i=0;i<divs.length;i++){
    div[i].addEventListener('click',fucntion(){
     console.log(i)                        
    })
}
//理解这题的关键就是   首先var 那个函数里面直接用变量i的话i会变成全局变量   然后就是那个函数是循环完成之后再执行的 那时候i已经是3了   但是立即执行函数是立马执行的所以i还是对应的值  let的话有块级作用域包起来了

6.闭包是什么?

当一个嵌套的内部函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包

理解一:闭包是嵌套的内部函数

理解二:包含被引用变量(函数)的对象

闭包产生的条件:

函数嵌套

内部函数引用了外部函数的数据(变量、函数)

闭包的应用

1.将函数作为另一个函数的返回值

2.将函数作为实参传递给另一个函数调用

作用:

1.使函数内部的变量在函数执行完之后,任然存活在内存之中(延长了局部变量的生命周期)。就相当于这个函数里面的变量在其他函数里面 要引用到,这个函数执行完了,但是另一个函数没执行完。所以还不会消失

2.让函数外部可以操作(读写)函数内部的局部变量,比如返回一个函数,使得让外部只能进行特定的操作

闭包的缺点:

函数执行完后,函数内的局部变量没有释放,占用内存时间会变长

容易造成内存泄漏

7.如何实现多种方式的继承

//1.原型链式继承: 引用值共享的问题
fucntion Super(){
    this.a = 111
    this.b = [1,2,3,4]
}
Super.prototype.say = function(){
    console.log(222)
}
function Sub(){
  
}
Sub.prototype = new Super()
var sub1 = new Sub() 
var sub2 = new Sub()
​
sub1.a = 222  //会在sub1上新增一个a:222 的属性
sub1.b.push(5)
​
console.log(sub1.a)   //可以访问到  222 
console.log(sub2.a)   //  111//引进类型得数据 两个实例之间有关联了
console.log(sub1.b)   //可以访问到 [1,2,3,4,5]
console.log(sub2.b)   //可以访问到 [1,2,3,4,5]
sub1.say()
​
//2.盗用构造函数继承: 解决引用值共享的问题  但是没办法拿到原型上的方法
function Super(){
    this.a = [1,2,3,4]
}
​
Super.prototype.say = function(){
    console.log(222)
}
​
function Sub(){
    //想办法把 this.a = [1,2,3,4] 拿进来执行一次
    Super.call(this)  //super()这个函数里面的this 指向的是window
}
​
var sub1 = new Sub()
var sub2 = new Sub()
sub1.a.push(5)
​
console.log(sub1.a)   //[1,2,3,4,5]
console.log(sub2.a)   // [1,2,3,4]//3.组合继承:(伪经典继承) Super会调用两次
function Super(){
    this.a = [1,2,3,4]
}
​
Super.prototype.say = function(){
    console.log(222)
}
​
function Sub(){
    //想办法把 this.a = [1,2,3,4] 拿进来执行一次
    Super.call(this)  //super()这个函数里面的this 指向的是window
}
​
Sub.prototype = new Super()
Sub.constructor = Sub
​
var sub1 = new Sub()
var sub2 = new Sub()
sub1.a.push(5)
​
console.log(sub1.a)   //[1,2,3,4,5]
console.log(sub2.a)   // [1,2,3,4]//4.寄生组合继承:(经典继承)解决
function Super(){
    this.a = [1,2,3,4]
}
​
Super.prototype.say = function(){
    console.log(222)
}
​
function Sub(){
    //想办法把 this.a = [1,2,3,4] 拿进来执行一次
    Super.call(this)  //super()这个函数里面的this 指向的是window
}
​
//用Object.create()这个api进行替换
//如果直接new Super相当于 Sub.prototype上还有Super的属性a[1,2,3,4] 但是用Object.create(Super.prototype)就没有属性了,只有原型
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
​
var sub1 = new Sub()
var sub2 = new Sub()
sub1.a.push(5)
​
console.log(sub1.a)   //[1,2,3,4,5]
console.log(sub2.a)   // [1,2,3,4]//5.圣杯模式继承
//6.class关键字
class Person{
  constructor(name){
      this.name = name
  }
  drink(){
      console.log('喝水');
  }
}
​
class Student extends Person{
    constructor(name,age){
        super(name) 
        this.age = age 
    }
    study(){
        console.log('学习');
    }
}
​
class Seacher extends Person{
    constructor(name,sex){
        super(name)
        this.sex = sex
    }
    teach(){
        console.log('教书');
    }
}
​

js手写题

1.手写节流和防抖

节流:在规定时间间隔内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频发触发变成少量触发

防抖:前面所有的触发都被取消,最后一次执行在规定时间之后才会触发,如果连续的快速触发,只会执行一次。

//节流函数
 let btn = document.querySelector('button')
   btn.addEventListener('click',throttle(function(){
       console.log('发送消息');
   },1000))
//我们使用 闭包   然后判断两次点击的时间间隔是否满足条件  满足执行(注意参数和this指向) 不满足不执行
 function throttle(fn,time){
     let begin = 0 
     return function(){
        let self = this 
        let arg = arguments //防止出现参数丢失的情况
        let cur = Date.now()
        if(cur-begin>=time){
            fn.apply(self,arg)
            begin = cur
        }
     }
   }
​
​
//防抖函数
//如果不把timer这个定时器指针定义外return函数的外部的话,就会开启多个定时器
 function debounced(fn,time){
    let timer = null
    return function(){
       clearTimeout(timer)
       let arg = arguments
       timer = setTimeout(() => {
            fn.apply(this,arg)
        }, time);
    }
  }

2.手写发布订阅

 /*    events={
         "click":[fn1,fn2]
         "abc":[fnA,fnB]
     }
  往消息队列中添加内容 on
  删除消息队列中的内容 off
  触发消息队列中的内容 emit */
​
  class EventEmitter{
      constructor(){
          //事件对象 存放订阅的名字和事件
          this.events = {}
      }
      //事件订阅函数
      on(type,fn){
          //先判断一下有没有这个属性  type是字符串  所以用events[type]  想想 
          // this.$on('aa',handlerA)
         if(!this.events[type]){
             //如果没有这个属性 初始化一个数组
             this.events[type]=[]
             this.events[type].push(fn)
         }else{
             //如果有这个属性 往数组里面添加
             this.events[type].push(fn)
         }
      }
      //事件发布函数
      emit(type){
        if(!this.events[type]) return 
        else {
            this.events[type].forEach(element => {
                element()
            });
        }
      }
      off(type,fn){
        //判断有没有订阅
        if(!this.events[type]) return 
        //判断有没有fn这个参数  有fn删掉这个方法  没有fn过滤调整个方法
        else{
            if(!fn) delete this.events[type]
            else{
                this.events[type] = this.events[type].filter(item=>item!==fn)
            }
        }
      }
    }

3.手写ajax请求

//1.使用原生的xhr发请求
cosnt xhr = new XMLHttpRequest()
xhr.open()
xhr.send()
xhr.onreadystatechange = function(){
    if(readyState==4&&xhr.status>=200&xhr.status<300){
        console.log(xhr.response)
    }
}
//2.使用axios发送请求  fetch也是这个写法 就是把axios换成fetch
axios({
    method:'post',
    url:'http://127.0.0.1.:8000/server',
    headers:{
        
    },
    data:{
        name:'zs',
        age:19
    }
})

4.手写promise

function Mypromise(excutor){
    this.status = 'pending'  // promise的状态
    this.data // promise存储数据结果的data
    this.callbacks = [] //保存的回调函数
    
    try{
       excutor(resolve,reject) 
    }catch(error){
        reject(error)
    }
    
    function resolve(value){
        if(this.status!=='pending') return
        this.status = 'resolved'
        this.data = value 
        if(this.callbacks.length!==0){
            settimeout(()=>{
                this.callback.foreach(item=>{item.OnResolved(value)})
            },0)
        }
    }
    
     function reject(reason){
        if(this.status!=='pending') return
        this.status = 'rejected'
        this.data = reason
        if(this.callbacks.length!==0){
            settimeout(()=>{
                this.callback.foreach(item=>{item.OnRejected(reason)})
            },0)
        }
     }
}
​
Mypromise.prototype.then = function(Onresolved,Onrejected){
    const self = this
    return New Mypromise((resolve,reject)=>{
        function handle(callback){
            try{
                let res = callback(self.data)
                if(res instance of Mypromise){
                    res.then(
                    value=>{resolve(value)},
                    reason=>{reject(reason)}
                    )
                }else{
                    resolve(res)
                }
            }catch(error){
                reject(error)
            }
        }
        
        if(self.status=='pending')
            self.callbacks.push({
                OnResolved(value){handle(Onresolved)},
                OnRejected(reason){handle(Onrejected)}
            })
        else if(self.status=='resolved'){
            setTimeout(()=>{
               handle(Onrejected)   
            })
        }else{
             setTimeout(()=>{
               handle(Onresolved)   
            })
        }
    })
}

5.手写promise.all

Mypromise.all = function(promises){
    return new Mypromise((resolve,reject)=>{
        //保存所有成功的返回值
        const values = new Array()
        promises.foreach((p,index)=>{
          p = p instanceof MyPromise? p :Mypromise.resolve(p)
          p.then(value=>{
              values[index] = value 
               if(values.length==promises.length){
               resolve(values)
               }
          },reason=>{reject(reason)})
        })
    })
}

6.手写深浅拷贝

//浅拷贝
    function shallowCopy(object){
        //先判断一下是不是对象  不是直接返回了
        if(!object||typeof object!=='object') return
        //判断是创建一个数组还是对象
       let newobj = obj instanceof Array ?[]:{};
        //遍历object 只有自己的属性才拷贝   遍历会把原型链上所有的属性都遍历出来
        for(let key in object){
        if(object.hasOwnProperty(key)){
           newobj[key] = object[key]
         }
        }
       return newobj
      }  
​
​
//深拷贝
 function deepCopy(object) {
     //这里return object是退出递归的条件
       if (!object || typeof object !== "object") return object;
       let newObject = Array.isArray(object) ? [] : {};
       for (let key in object) {
       if (object.hasOwnProperty(key)) {
           //这里递归调用自己
         newObject[key] = deepCopy(object[key]);
        }
       }
       return newObject;
      }
  

7.数组去重

//使用set和扩展运算符
let a = [1,2,3,4,5,5,3,2]
let set = new Set(a)//得到{1,2,3,4,5}
console.log(set);
console.log([...set]);//得到[1,2,3,4,5]//也可以用indexOf 查找出第一次出现的数字  再用新的数组去接   但是不能用splice去除原数组上的不然会出现bug 因为在循环的过程中数组的长度发生了改变
 var  a  = [1,2,3,4,5,2,3,4,2]
  function quchong(arr){
    let newarr =[]
    for(let i =0;i<arr.length;i++){
      if(arr.indexOf(arr[i])===i){
        newarr.push(arr[i])
      }
    }
    return newarr
  }
  console.log(quchong(a));

DOM押题

1.简述DOM事件模型

Dom事件流描述的是从页面中接收事件的顺序

事件发生时会在元素节点之间按照特定的顺序传播,这个传播的顺序即为DOM事件流。

事件捕获:由Dom最顶层节点开始,然后逐级向下传播到最具体的元素接受过程

事件冒泡:事件开始时候由最具体的元素接受,然后逐渐向上传播到DOM最顶层节点的过程。

js代码只能执行捕获或者冒泡的其中一个阶段

addEventListener最后一个参数就是事件捕获的boolean参数。默认为false

阻止事件冒泡:e.stopPropagation

2.事件委托

事件委托的原理:不是每个子节点都设置事件监听器,而是只在父节点上设置事件监听器,然后利用冒泡原理影响每个子节点。

<body>
<ul>
  <li>shabi 1</li>
  <li>shabi 2</li>
  <li>shabi 3</li>
  <li>shabi 4</li>
</ul>
 <script>
   let ul = document.querySelector('ul')
   ul.addEventListener('click',function(){
     alert('煞笔来咯')
   })
 </script>
</body>

3.可拖拽的div

网络押题

1.Get和Post请求的区别有哪些

1.作用

get请求用来获取资源,post请求用来传输实体主体

2.参数

get请求的携带的参数以查询字符的形式出现在URL上,而post的参数存储在实体主体(请求体)中。

3.安全性,这里的安全性是指能否改变服务器的状态和数据

那么get请求时无法改变的,但是post请求却可以,因为这个post请求的目的时传送实体主体的内容,这个内容可能是用户上传的表单数据,上传成功后,服务器可能把这个数据存储在数据库中。

那么get方法就是幂等的,就是多次请求客户端得到的结果都是一样的。

post请求就不是幂等的,可能会改变数据库的状态,多次请求的结果不一样。

2.Http的缓存有哪些方案

1.缓存的作用

通过http协议,在服务器和浏览器之间简历连接时候需要消耗时间,内容较多的响应在客户端和服务器之间进行多次往返通信才能获得完整的响应,拖延了服务器可以使用和处理内容的时间。增加了访问服务器的数据和资源的成本,利用浏览器的缓存机制重用以前获取的数据可以优化响应的时间减轻服务器的负担。

2.缓存的过程

浏览器每次发起请求的时候,都会在浏览器缓存中查找该请求的结果以及缓存标识。

浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。

3.缓存的类型

强制缓存:向浏览器缓存查找该请求的结果,并根据该结果的缓存规则来决定是否使用该缓存的结果的过程

有三种类型:

1.不存在该缓存结果和缓存标识,则直接向服务器发起请求。

图片

2.存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存(暂不分析),如下图

图片

3.存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果

图片

控制强制缓存的是Expires和Cache-Control,Cache-Control的优先级更高

Expires其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。是用的绝对时间来控制

Cache-Control:max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效,使用相对时间来进行控制。是更优的选择。

协商缓存:是强制缓存失效之后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识来决定是否使用缓存。

图片

控制协商缓存的字段有

Last-Modified是服务器响应请求时候,返回该资源文件在服务器最后修改的时间。在响应头里面

Last-Modifed-since则是客户端再次发起该请求时候,携带上次请求返回的Last-Modified值,通过此字段告诉服务器资源上次请求返回的最后被修改的时间。在请求头里面,服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件

Etag是常服务器生成并返回在Etag中的验证码,常常是文件内容的哈希值或者某个其他指纹码。客户端不必了解指纹码是如何生成的,只需要在下一个请求中将其发送给服务器(浏览器默认会添加):如果指纹码仍然一致,说明资源未被修改,服务器会返回304 Not Modified,这样我们就可以跳过下载,利用已经缓存了的资源,并且该资源会继续缓存120s。 在响应头里面

if-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。

总结:

浏览器缓存分为强制缓存协商缓存,强制缓存优先于协商缓存进行。

  • 若强制缓存(Expires和Cache-Control,Cache-Control优先级高于Expires)生效则直接使用缓存
  • 若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高),协商缓存由服务器决定是否使用缓存
  • 若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存

图片

3.HTTP和HTTPS的区别

HTTPS是HTTP先和SS进行通信,再由SSL和TCP进行通信。因此,HTTPS有防伪造、防窃听、放篡改的功能。

HTTPS采用混合加密的方式。使用非对称加密的方式来对对称加密的秘钥进行传输,然后以后的通信使用对称加密的方式来加密,然后使用CA证书来对身份进行验证

4.HTTP状态码

类别原因短语
1xxInformation接受的请求正在处理
2xxSuccess请求正常处理完毕
3xxRedirection需要进行附加操作完成请求
4xxClient Error服务器无法处理请求
5xxServer Error服务器处理请求出错

200 OK 请求已经正常处理

204 No Content 请求处理正常 但是没有内容返回

206 Partial Content 客户端进行了范围请求,服务器成功执行

301 Moved Permanently 请求的资源URL已经更新 ,永久性质

302 Found 请求的资源已经被分配到新的URL,临时性质

304 Not Modified 和重定向没有关系,表示击中协商缓存

400 Bad Request 请求报文中存在语法错误

401 Unauthorized 发送的请求需要通过有HTTP认证,

403 Forbidden 请求资源的访问被服务器拒绝了

404 Not Found 服务器上无法找到请求的资源

501 Internal Server Error 服务器在执行请求的时候出现了错误

503 Service Unavailable 服务器暂时处于超负载

5.说说同源策略和跨域的解决方法

同源策略:同源策略是一种安全机制,为了预防某些恶意行为(例如cookie窃取等等),浏览器限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。而满足同源必须具备三方面的协议:协议相同、域名相同、端口相同。

同源策略的三种解决方案:

  1. JSONP :

    只支持get请求,

    2.CORS代理

跨域资源共享,是官方的跨域解决方案。通过设置一个响应头来告诉浏览器,该请求允许跨域。

response.setHeader('Access-Control-Allow','*') 后面的参数表示可以跨域的url ,*表示所有的url都可以

响应头设置了,把服务器启动一次。然后再删了,以后依然没有跨域问题。好神奇

3.使用webpack进行proxy反向代理

4.nginx反向代理

6.HTTP的不同版本的改进过程

HTTP0.9

只支持get请求,不支持其他方式的请求。没有请求头

HTTP1.0

新增了 post delete等请求方式

添加了请求头和响应头的概念

但是缺点有无状态和短链接、不安全。数据以明文方式传输,容易被窃取

无状态可以通过cookie、session、token来解决

短链接就是每次请求都要建立一次TCP连接

HTTP1.1

1.新增了put options一些请求方法

2.新增了cache-control等缓存控制的请求头、新增了Range头部,可以控制请求资源的某一部分

3.支持长连接。新增Connection:keep-alive头部,使得一个TCP连接允许进行多次http请求。

4.支持管道化连接。没有管道化的时候是

请求1 > 响应1 --> 请求2 > 响应2 --> 请求3 > 响应3 请求一个响应一个

支持管道化之后是

请求1 --> 请求2 --> 请求3 > 响应1 --> 响应2 --> 响应3 连续请求几个 但是响应的顺序是按照请求的顺序来的 无法解决队头阻塞的问题

HTTP2.0

1.实现头部压缩: 对于许多相似的请求,可以在客户端和服务器同时维护及一张信息表,所有字段都在这个表中,以后只发送对应的头部字段的索引号。

2.二进制分帧: 1.1版本中报文的头信息是必须是文本ascii码,数据体可以是文本也可以是二进制。2.0版本是一个彻底的二进制协议,头信息和数据体都是二进制的

3.多路复用 :客户端可以并行发送多个请求,服务器也会并行的响应。服务器会对每个请求使用类似时间片的方式处理,这样可以避免HTTP1.1中的队头堵塞情况。

4.支持服务器推送 :HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。用服务器推送,提前给客户端推送必要的资源 ,这样就可以相对减少一些延迟时间。

7.TCP的三次握手和四次挥手的过程

三次握手:

1.主机A的TCP向主机B的TCP发送连接请求报文段,其首部的同步位SYN置为1。同时随机选择一个序号seq=x

2.主机B的TCP收到连接请求之后,则回发连接请求。在确认报文段把SYN和ACK置为1,确认号ack为x+1,ACK=1表示ack有效。ack=x+1表示希望收到A下一个请求的序列号为x+1

3.主机A的TCP收到B接受连接的请求确认之后,还要给B发出确认。ACK=1 ack=y+1而自己的序列号seq=x+1

为什么要三次握手:

1.三次握手的建立连接的过程就是互相确认初始序号的过程,告诉对方什么样的报文段能够被正确接受。如果只有两次握手的话,服务器没办法知道自己的序号是否被确认了。

2.可以防止滞留的请求报文直接打开服务器的连接。让服务器等待主机A。浪费资源

IMG_3151

四次挥手:

TCP的连接是全双工的,通信双方都可以向对方发送和接受消息,因此断开连接需要双方的确认。

1.第一次挥手,客户端忍为没有数据要再发送给服务端,它就给服务器发送一个FIN报文段。申请断开客户端的连接,然后进入FIN-Wait-1状态。

2.第二次挥手,服务端接收到客户端释放连接请求后,向客户端发送一个确认报文段,表示已经接收到了客户端释放连接的请求,以后将不再接受客户端发送过来的数据。但是服务器还是可以向而客户端发送数据,服务器进入close-wait状态。客户端收到确认之后,进入FIN-Wait-2的状态。

3.第三次挥手,服务器发送完所有的数据之后,向客户端发送FIN报文段,申请断开服务器到客户端的连接,然后进入LAST-ACK的状态

4.第四次挥手,客户端接受到FIN请求之后,向服务器发送一个确认应答,进入TIME-WAIT阶段。该阶段会等段一段时间,如果该事件内服务端没有重发请求的话,客户端进入CLOSED状态。如果收到服务器的重发请求就重新发送确认报文段,服务器如果收到了客户端的确认报文则进入CLOSED状态。连接关闭

最后一次挥手中,客户端会等待一段时间再关闭的原因,是为了防止发送给服务器的确认报文段丢失或者出错,从而导致服务器 端不能正常关闭。

IMG_3152

8.Session Cookie LocalStorage SessionStorage的区别

由于HTTP的无状态特性,服务器无法从网络连接上知道客户的身份。

cookie就是客户端保存用户信息的一种机制,用来记录用户的一些信息。

原理:web服务器通过在http响应消息头增加Set-Cookie响应头字段将Cookie信息发送给浏览器,浏览器则通过在http请求消息中增加Cookie请求头字段将Cookie回传给web服务器。

Cookie的不可跨域名性

说当浏览器访问baidu时,只会带baidu的Cookie,而不会带其他网站的Cookie,这就是Cookie的不可跨域名性 。 Cookie在客户端是由浏览器来管理的。浏览器可以保证各个网站只能操作各个网站的Cookie,从而保证用户的隐私安全。cookie一般4k大小

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。 客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了

session id 存储在cookie里面 ,就像是session里面还有你的购物信息,相关信息,但是session id只是一个身份认证的字符,然后存储在cookie里面,但是session存储在服务端里面

localStorage是一个存储在浏览器上的哈希表

生命周期是永久的一般存放的数据大小为5M,不清除会一直保存下去

SessionStorage是会话存储,关闭页面就会消失

localStorage.setItem("key","value");    //以“key”为名称存储一个值“value”localStorage.getItem("key");    //获取名称为“key”的值localStorage.removeItem("key"); //删除名称为“key”的信息。localStorage.clear();   //清空localStorage中所有信息

token 的编码技术,通常基于 base64,或增加加密算法防篡改,jwt 是一种成熟的编码方案

9.TCP的可靠数据传输

10.TCP中的拥塞避免

当网络中如果出现太多的分组时候,网络的性能会开始下降这种情况称为拥塞。

拥塞控制就是为了防止过多的数据注入到网络中,可以使得网络中的路由器或者链路不致过载

拥塞控制的方法:慢开始 拥塞避免 快重传 快速恢复

发送方维护一个叫做拥塞窗口cwnd的状态变量,其值取决于网络的拥塞程度,并且动态变化。

只要网络没有出现拥塞,拥塞窗口就再增大一些,只要出现了拥塞,拥塞窗口就减小一些。

还要维护一个慢开始门限:

当cwnd<ssthresh时候,使用慢开始算法

当cwnd>ssthresh时候,使用拥塞避免算法

当cwnd=ssthresh时候,使用慢开始算法

1.执行慢开始算法的时候,cnwd窗口随着传输轮次按照知数规律进行增长,当拥塞窗口达到慢开始门限的时候,改为执行拥塞避免算法。拥塞窗口按照线性规律进行增长。

2.当重传计时器超时之后,网络可能出现了拥塞。则将ssthresh值更新为发生拥塞时cwnd值的一半。将cwnd值减少为1并重新开始执行慢开始算法。

拥塞避免1

快重传和快速恢复是后来补充的两个算法。

因为个别报文段会在网络中丢失,但实际上网络并未发生拥塞

如果连续收到三个重复确认(重复确认表示没有收到该报文段,但想收到该报文段),就将相应的报文段立即重传。这就是快重传。

快速恢复:当发送方一旦收到3个重复确认的时候,开始执行快速恢复算法。

1.发送方将慢开始门限和拥塞窗口的cwnd值调整为当前窗口的一半。开始执行拥塞避免算法

拥塞窗口线性增大。

Vue2押题

1.vue2的声明周期钩子有哪些

beforecreate:无法访问到data中的数据

created:可以通过vm当问到data中的数据 methods中配置的方法

beforemount:页面呈现的是未经Vue编译的dom结构 ,所有的dom操作无效

mounted:可以进行dom操作 可以在这里进行开启定时器 发送网络请求 订阅消息 绑定自定义事件 等初始化操作

beforeupdate:数据是新的页面是旧的

updated:数据是新的 页面也是新的

beforeDestroy:data methods都是可用状态 马上要执行销毁过程 一般在这里进行关闭定时器 取消订阅消息 解绑事件等收尾操作

Destroyed:

2.vue2的组件通信方式有哪些

父传子:自定义属性props

<template>
    <div>
        <h3>我是父组件</h3>
        
    </div> 
    <my-comp2 :message="msg"></my-comp2>
</template>
   data:{
   msg:'123455'
}
​
​
//子组件
<template>
    <div>
        <h4>我是子组件</h4>
        <h4>访问父组件msg数据:{{message}}</h4>
    </div> 
</template>
  props:['message']
​

子传父:自定义事件

父组件:
<template>
  <div class="app">
    <h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
    <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) -->
    <!-- <Student @atguigu="getStudentName" @demo="m1"/> -->
​
    <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student" @click.native="show"/> <!-- 🔴native -->
  </div>
</template><script>
  import Student from './components/Student'
  import School from './components/School'
​
  export default {
    name:'App',
    components:{School,Student},
    data() {
      return {
        msg:'你好啊!',
        studentName:''
      }
    },
    methods: {
      getStudentName(name,...params){
        console.log('App收到了学生名:',name,params)
        this.studentName = name
      },
      m1(){
        console.log('demo事件被触发了!')
      },
      show(){
        alert(123)
      }
    },
    mounted() {
      this.$refs.student.$on('atguigu',this.getStudentName) // 🔴绑定自定义事件
      // this.$refs.student.$once('atguigu',this.getStudentName) // 绑定自定义事件(一次性)
    },
  }
</script>
​
​
子组件
<template>
    <div class="student">
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <h2>当前求和为:{{number}}</h2>
        <button @click="add">点我number++</button>
        <button @click="sendStudentlName">把学生名给App</button>
        <button @click="unbind">解绑atguigu事件</button>
        <button @click="death">销毁当前Student组件的实例(vc)</button>
    </div>
</template><script>
    export default {
        name:'Student',
        data() {
            return {
                name:'张三',
                sex:'男',
                number:0
            }
        },
        methods: {
            add(){
                console.log('add回调被调用了')
                this.number++
            },
            sendStudentlName(){
                // 触发Student组件实例身上的atguigu事件
                this.$emit('atguigu',this.name,666,888,900)
                // this.$emit('demo')
                // this.$emit('click')
            },
            unbind(){
        // 🔴解绑
                this.$off('atguigu') //解绑一个自定义事件
                // this.$off(['atguigu','demo']) //解绑多个自定义事件
                // this.$off() //解绑所有的自定义事件
            },
            death(){
        // 销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效
                this.$destroy()
            }
        },
    }
</script><style lang="less" scoped>
    .student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>

兄弟之间传递:全局事件总线 或者vuex或者消息订阅与发布

this.$bus.$on('hello',data) //监听事件
this.$bus.$emit('hello',data=>{}) // 触发事件 发送数据

3.vuex是什么?怎么理解

vuex是用来集中管理各个组件中要用到的数据的仓库,以方便共享数据。对于小项目来说,组件间的数据共享可以用那些方法,但是对于较大型的项目,使用vuex就会比较方便。

vuex管理的数据是全局数据,各个组件间都可以访问得到。

一般数据可以放在state里面,若没有异步操作可以通过actions进行修改数据,如果有异步操作比如网络请求,可以先通过mutations然后触发actions里面的事件,再修改state的数据。

4.vue-router是什么?怎么理解

根据不同的url地址显示不同的内容来构建单页面应用程序,实现的手段就是前端路由。

前端路由是一组映射关系,key就是对应的url,value是对应要展示的页面。

实现的原理是:

hash路由:location.hash+hashchange事件

history路由:history.pushState()+popState事件来监听url的改变 replaceState事件是h5新增的 ,不会引起页面的刷新

hash路由 location.hash='#/about' 其中url#后面的都是不带入网络请求的

5.如果用路由守卫实现权限控制和加载进度

全局守卫

const router = createRouter({..})
//全局前置守卫
router.beforeach((to,from)=>{
    next() // 表示放行
})
//全局后置守卫
router.afterEach((to,from)=>{
    
})

路由独享守卫

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]
​

组件内守卫

beforeRouteEnter(to,from,next){
    
}
​
beforeRouteLeave(to,from,next){
    console.log()
    next(0)
}
​

6.vue2是如何实现双向绑定的

6.vue2是如何实现双向绑定的

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调 Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观

Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

Watcher(订阅者) : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notify()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile(指令解析器) : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图

7.react中的diff算法

传统 diff 算法的时间复杂度是 O(n^3),这在前端 render 中是不可接受的。为了降低时间复杂度,react 的 diff 算法做了一些妥协,放弃了最优解,最终将时间复杂度降低到了 O(n)。

1.tree diff:

只对比同一层的 dom 节点,忽略 dom 节点的跨层级移动。如果 dom 节点发生了跨层级移动,react 会删除旧的节点,生成新的节点,而不会复用

2.component diff:如果不是同一类型的组件,会删除旧的组件,创建新的组件

3.element diff:对于同一层级的一组子节点,需要通过唯一 id 进行来区分

  • 如果没有 id 来进行区分,一旦有插入动作,会导致插入位置之后的列表全部重新渲染
  • 这也是为什么渲染列表时为什么要使用唯一的 key。

前端工程化押题

1.webpack中常见的loader和plugins有哪些?二者有什么区别

webpack中的loader能够让webpack去处理哪些非js文件(webpack自身只理解js)。而loader可以将所有类型的文件转化为webpack可以处理的有效模块。

常见的loader有:

1.处理css样式资源:

css-loader 负责将Css文件编译成为webpack能够识别的模块

style-loader 会动态创建一个style标签 里面放置webpack中css模块

2.处理less资源的、处理sass资源的、处理stylus

less-loader 负责将less文件编译成为css文件

sass-loader负责处理sass资源成为css文件

stylus-loader 负责将styl文件编译成为css文件

3.Babel

babel 将es6语法编写的代码转换为向后兼容的javascript语法,以便能够在当前版本和旧版本的浏览器中运行、

使用babel-loader

plugins用来扩展webpack的功能:

常见的plugins有

1.Eslint 用来检查js语法的工具

new ESLintWebpackPlugin(),

2.处理html资源

new HtmlWebpackPlugin()

2.webpack如何解决跨域问题

主要是通过webpack-dev-server这个插件,然后本地启动了一个使用express创建的服务器

我们将请求发送给webpack dev-server,由这个代理服务器向api地址发送请求

向dev-server添加proxy反向代理

3.tree-shaking 的实现原理

4.如何提高webpack的构建速度

我们从 4 个角度对 webpack 和代码进行了优化:

  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。

5.babel的编译过程

第一步 解析:通过解析器将代码解析成为抽象语法树AST

第二部 转换:通过Babel-traverse plugin对抽象语法树进行深度优先遍历,遇到需要转换的,就直接在AST对象上对节点进行添加更新和删除操作,比如说遇到箭头函数就转换为普通函数得到新的AST树

第三步 生成:通过babel-generator将AST树生成es5代码

6.tree-shaking的工作原理

tree-shaking 的实现一是先标记出模块导出值中哪些没有被用过,二是使用terser删掉这些没有用过的导出语句

标记过程分为三个步骤:

make阶段,收集模块导出变量并记录到模块依赖关系图ModuleGragh中

seal阶段 遍历ModuleGragh标记模块导出变量有没有被使用

生成产物,若变量没有被其他模块使用则删除对应的导出语句

Typescript押题

1.Ts和js的区别是什么?为什么要使用TS