面试题总结

225 阅读21分钟

html

1.DIV+CSS布局的好处

1.代码精简,样式和结构相分离,易于维护
2.代码量减少,页面加载更快,提升用户体验
3.允许更多酷炫的页面效果,丰富页面
4.符合W3C标准,保证网站不会因网页升级而淘汰

2.H5的新标签元素有哪些

结构标签

  • article 用于定义一篇文章
  • header 用于定义页面的头部
  • footer 用于定义页面的底部
  • nav 导航条链接
  • section 定义一个区域

多媒体标签

  • vedio 定义一个视频
    • autoPlay 是否自动播放
    • controls 是否展示控制器
  • audio 音频
    • autoPlay 是否自动播放
    • loop 重复播放次数(loop = '-1' 表示无限播放,等于其他数字等于播放次数)
  • source 定义媒体资源
    • 用于audio/video标签内部,并支持不同格式的媒体文件,type属性用于转码格式
      <audio>
        <source src="音频资源" type="audio/转码格式">
      </audio>
    
     <audio>
        <source src="horse.mp3" type="audio/mpeg">
      </audio>
    

canvas 绘图

Web 标签

  • mark 用于标黄
    <p>
        <mark>这条内容会有黄色背景</mark>
    </p>
  • progress 显示任务过程,安装,加载
    • value 当前状态值
    • max 最大状态值
    <progress max="100"></progress>

Form 新表单类型和属性

  • 类型 (email,number,url,range,tel,search,date...)
    <input type="email"/> // 输入邮箱地址
  • 属性(placeholder,required,min,max,step,multiple...)
    <input placeholder="请输入数字" step="1"/>

CSS

1.如何解决a标签点击后hover事件失效的问题?

改变a标签CSS属性的排列顺序,遵循 LoVe HAte原则

link->visited->hover->active

错误代码示范:

a:hover{
    color: green;
}
a:visited{ /*visited在hover后面,此时的hover事件失效 */
    color: red;
}

正确代码:

a:visited{ 
    color: red;
}
a:hover{
    color: green;
}
  • a:link 未访问时的样式,一般省略用a
  • a:visited 已经访问后的样式
  • a:hover 鼠标移上去的样式
  • a:actived 鼠标按下时的样式

2.盒模型

盒模型是由 margin,padding,content,border 组成

两者区别:在设置了宽高属性时,两者所包含的范围不同,标准模式下只包含content内容;怪异模式下则是包含了content+padding+border

标准盒模型(W3C盒模型)

    box-sizing:content-box // 盒子的大小就是content的大小

IE盒模型(怪异盒模型)

    box-sizing:border-box // 盒子的大小是content+padding+border

3.垂直居中的实现方式

  1. 当盒子没有宽高
.parent {
    position: relative
}
.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
  1. 当盒子有宽高
.parent {
    position: relative
}
.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  1. 当盒子有宽高已知
.parent {
    position: relative
}
.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px; /*负自身height的一半*/
    margin-left: -50px; /*负自身width的一半*/
}
  1. flex布局
.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}

4. 组件样式穿透

普通样式穿透
  .search-box >>> button:focus{
        color: lightsalmon;
        border: 1px solid #dedede;
    }
scss语法
::v-deep  button{
        border:none;
    }

less语法
/deep/ button{
        border:none;
    }

5. px、em、rem的区别及使用场景

区别:
  • px是固定像素,无法根据页面的大小改变
  • rem是相对于根元素来设置字体大小,其长度不是固定,更适用于响应式布局
  • em是相对于其父元素来设置字体大小,其长度不是固定,更适用于响应式布局

6. 响应式

响应式网站设计是一个网站能够兼容多端,而不是为每个终端做一个特定的版本

  • 原理:通过媒体查询@media查询不同设备屏幕尺寸做处理
  • 兼容:页面头部需要设置meta声明的viewport

7. Flex

即弹性布局,C3新增的一种布局方式,通过设置display: flex使盒子成为一个flex容器。在flex布局下,float,vertical-align属性都将失效。

  • flex-direction:设置主轴方向

      1.row(默认值): 从左往右
      2.column: 垂直从上往下
      3.row-reverse: 从右往左
      4.column-reverse: 从下往上
    
  • jusity-content: 在主轴的对齐方向

      1. flex-start(默认值): 左对齐
      2. center: 居中对齐
      3. flex-end: 右对齐
      4. space-between: 两端对齐,项目之间间隔相等
      5. space-around: 每个项目木两侧的间隔相等
    
  • align-items: 在交叉轴的对齐方向

      1. flex-start: 起点对齐
      2. center: 居中对齐
      3. flex-end: 终点对齐
      4. baseline: 项目的第一行文字的基线对齐
      5. stretch(默认值): 如果项目未设置高度或设为auto,将占满整个容器的高度。
    
  • flex-wrap: 指定排行排列不下时的换行方式

      1. nowrap(默认值): 不换行
      2. wrap: 换行,第一行在上方
      3. wrap-reverse: 换行,第一行在下方
    

8. 回流和重绘

定义:当js对页面的节点进行操作时,则会产生重绘和回流

  • 重绘:颜色等不影响布局改变就是重绘
  • 回流:位置大小等改变就是回流

tips:重绘不一定会造成回流,但回流一定会触发重绘

引起回流的原因

    1. DOM的增加或者删除
    2. 元素尺寸,大小,边距,宽高等改变
    3. 浏览器窗口的变化
    4. 操作某些样式(offset,client,scroll等等)   

9. BFC的理解,如何创建BFC

BFC(Block Formatting Context),即块级格式化上下文,它是页面中渲染的一个区域,并且有属于自己的渲染规则:

  • 内部的盒子会在垂直方向上,自上而下的排列
  • 对于同一个BFC中,上下相邻的两个盒子的margin会发生重叠
  • BFC的区域不会与浮动元素的区域块重叠
  • 计算BFC的高度时需要计算浮动元素的高度
  • 每个元素的左margin值和容器的左border相接触
  • BFC是独立的容器,容器内部元素不会影响外部元素

触发BFC的条件包含但不限于

  • 根元素: body
  • 浮动元素: float除none以外的值
  • 设置绝对定位: position(absolute、fixd)
  • overflow: hidden、auto、scroll
  • display: flex、inline-block、table等

应用场景

1. 防止盒子塌陷(margin重叠)
<style>
    p {
        color: #f55;
        background: #fcc;
        width: 200px;
        line-height: 100px;
        text-align:center;
        margin: 100px;
    }
</style>
<body>
    <p>Haha</p >
    <p>Hehe</p >
</body>

页面效果:

image.png

两个P元素之间的间距为100px,正确应该是200px,此时发生了margin重叠(塌陷),如果第一个pmargin: 80px,两者间的间距还是100px盒子塌陷以最大的值为准

解决: 将其中一个p标签包裹起来使其成为一个独立的BFC

<style>
    .wrap {
        overflow: hidden;// 新的BFC
    }
    p {
        color: #f55;
        background: #fcc;
        width: 200px;
        line-height: 100px;
        text-align:center;
        margin: 100px;
    }
</style>
<body>
    <p>Haha</p >
    <div class="wrap">
        <p>Hehe</p >
    </div>
</body>

页面效果:

image.png

2. 清除内部浮动
<style>
    .par {
        border: 5px solid #fcc;
        width: 300px;
    }
 
    .child {
        border: 5px solid #f66;
        width:100px;
        height: 100px;
        float: left;
    }
</style>
<body>
    <div class="par">
        <div class="child"></div>
        <div class="child"></div>
    </div>
</body>

页面效果:

image.png

解决: BFC在计算高度时,浮动元素也会参与,所以可以给.par区域块设置成BFC,则内部浮动元素计算高度时候也会计算

.par {
    overflow: hidden;
}

页面效果:

image.png

3. 自适应多栏布局
举个栗子:两栏布局
<style>
    body {
        width: 300px;
        position: relative;
    }
 
    .aside {
        width: 100px;
        height: 150px;
        float: left;
        background: #f66;
    }
 
    .main {
        height: 200px;
        background: #fcc;
    }
</style>
<body>
    <div class="aside"></div>
    <div class="main"></div>
</body>

页面如下:

此时盒子两个元素的左margin值和容器的左border相接触,虽然.aside为浮动元素,但是.main的左边距依旧会与容器盒子的左border相接触

image.png

解决: 通过设置.main区域为新的BFC,这时新的BFC不会与浮动的.aside元素重叠。会根据容器的宽度和.aside的宽度自动变窄 页面如下:

image.png

小结: 可以看到上面几个案例,都体现了BFC实际就是页面一个独立的容器,里面的子元素不影响外面的元素

10. iframe标签

规定一个内联框架。内联框架就是在一个页面中嵌入另一个页面

优点

  • 可以减少数据的传输,减少网页的加载时间
  • 使用起来方便
  • 方便开发,减少代码的重复率

缺点

  • 部分使用会产生跨域
  • 会产生很多的页面,不易于管理
  • 浏览器的后退按钮无效

iframe的常用属性

  • height:框架作为普通元素的高度
  • width: 框架作为普通元素的宽度
  • src:框架的地址,可使用页面的地址,也可以是图片地址
  • scrolling: 框架是否滚动
  • name: 框架名称
  • frameborder: 框架是否显示边框

JS

1.null 和 undefined 的区别

  • null 表示 ,此处不应该有值; undefined 表示未定义
  • 转换Number类型中结果不同
    Number(null) -> 0
    Number(undefined) -> NaN

使用场景上:

null

  • 作为函数的参数,表示该函数的参数不是对象
  • 作为对象原型链的终点

undefined

  • 变量被声明了但没有赋值,代码运行时就会报该变量undefined的错误
  • 调用函数时,应该提供的参数没有提供,该参数则undefined
  • 对象没有赋值属性,该属性的值为 undefined
  • 函数没有返回值时,默认返回undefined

2.数组去重

  • Array.from(new Set(arr))或[...new Set(arr)]
    var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
    console.log(Array.from(new Set(arr))
    // console.log([...new Set(arr)])
  • 循环嵌套利用splice去重
    function unique(origin) {
        let arr = [].concat(origin);
        for(let i = 0; i < arr.length; i++) {
            for(let j = i + 1; j < arr.length; j++) {
                if(arr[i] === arr[j]) {
                    arr.splice(j,1);
                   j--;
                }
            }
        }
        return arr;
    }
    var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
    console.log(unique(arr));
  • 利用includes去重
    function unique(arr) {
        let res = [];
        for(let i = 0; i < arr.length; i++) {
            if(!res.includes(arr[i]) {
                res.push(arr[i])
            }
        }
        return res;
    }
    var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
    console.log(unique(arr));

3.如何理解JS中的this关键字

this指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用。

4.call、apply、bind相同和不同

call 和 apply 的不同在于传入的参数不同

call 接收单个的参数, apply 接收数组

call 的语法:

    f.call(thisObj,arg1,arg2,...)

call 的作用:

  • 改变函数的this指向
    var a = {
        name:'完美日记' 
    }
    
    function b () {};
    b.call(a); // 输出 {name:'完美日记'}
  • 调用函数
    var a = {
        name:'完美日记' 
    }
    
    function b (a, b) {
        console.log(a + b);
    };
    b.call(a, 1, 2); // 3
  • 实现继承
    function Father (name,age) {
        this.name = name;
        this.age = age;
    }
    function Son (name,age) {
        Father.call(this,name,age);
    }
    var son = new Son('南瓜',1);
    console.log(son); // Son {name: "南瓜", age: 1}

apply 的语法

f.apply(thisObj,[...args])

例子:

   function Father (name,age) {
       this.name = name;
       this.age = age;
   }
   function Son (name,age) {
       Father.apply(this,[name,age]);
   }
   var son = new Son('南瓜',1);
   console.log(son); // Son {name: "南瓜", age: 1}

bind 的语法

   f.bind(thisObj,args1,arigs2,...)

例子:

   var a = {
       name: '南瓜'
   }
   
   function b (c, d) {
       console.log(this);
       console.log(c + d);
   }
   b.bind(a)(); // {name: "南瓜"}
   b.bind(a,1,2)(); // 3

总结

相同:都是用来改变函数执行时的上下文(改变函数运行时this的指向)
不同:

  • apply 和 call 都可以调用函数,call 接收的足一个参数,而 apply 接收的是个数组
  • bind 和 call 的传参方式相同,但不会调用函数,且返回值是一个原函数拷贝

5.什么是闭包?在什么场景中使用过。

  • 闭包指有权访问另一个函数作用域中变量的函数
  • 闭包的主要作用:延伸了变量的作用范围。
    function fn () {
        var num = 10;
        function fun () {
            console.log(num);
        }
        fun();
    }
    fn();
    
    // fun 函数作用域 访问了另外一个 fn 里的局部变量,就产生了闭包,此时fn也叫闭包函数。
    function fn () {
        var num = 10;
        return function fun () {
            console.log(num);
        }
    }
    fn()(); // 10
    // 外部函数调用另外函数内部的变量

6.节流和防抖

节流与防抖的实现直接涉及到了闭包知识


节流

// 时间戳版本
    function throttle(fn,delay) {
        // 记录上一次函数触发的时间
        var lastTime = Date.now();
        return function() {
            // 记录当前函数触发的时间
            var nowTime = Date.now();
            if(nowTime - lastTime >= delay) {
                // 修正this指向问题
                fn.call(this);
                // 同步时间
                lastTime = Date.now();
            }
        }
    }

节流使用的场景: 1.拖拽功能实现。 2.搜索联想

防抖

    function deboounce(fn,delay) {
        // 记录上一次的延时器
        var timer = null;
        return function() {
            // 如果此时存在定时器,则取消之前的定时器重新计时
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            // 设置定时器,使事件间隔指定事件后执行
            timer = setTimeout(function() {
                fn.apply(this);
            },delay)
        }
    }

防抖使用的场景:重复ajax请求。

7. 深浅拷贝


    function DeepClone(obj){
        if(!obj || typeof obj !== 'object') return;
        let newObj = Array.isArrary(Obj) ? [] : {};
        for(let key in obj){
            if(Object.hasOwnProperty(key)){
                newObj[key] = 
                    typeof obj[key] === 'Object' ? DeepClone(obj[key]) : obj[key];
            }
        } 
        return newObj;
    }

8. 描述一下Promise

promise是一个对象,它代表了一个异步操作的最终完成或失败。由于它的then方法和catch,finally方法会返回一个新的Promise所以允许我们链式调用,解决了传统的回调地狱问题。含有三个状态resolve,reject,pendding

9. EvenLoop 的执行过程

  • 一开始整个脚本作为宏任务开始执行
  • 执行过程中同步代码直接执行,宏任务进宏任务队列,微任务进微任务队列
  • 当前宏任务执行完出队,检查微任务列表,列表依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查web worker任务是否存在,有则执行
  • 执行完本轮宏任务出队,回到2,依次执行,直到宏微队列全部清空

微任务包括:

MutationObserver、Promise.then()或者catch()、Promise。

宏任务包括:

script、setTimeout、setInterval、setImmediate、I/O、UI rendering。

10. 如何使0.1+0.2===0.3

i. 浮点数相加结果正确的方法
  const mathFloat = (float:number, digit:number):number => {
    // .pow(x,y)返回x的y次幂
    const math = Math.pow(10, digit);
    // parseInt(string, radix)函数解析字符串并返回整数。第一个参数为要解析的字符串,第二个为要转换的进制基数,默认是十进制。
    // 使用.toString()是参数在TS的情况下会报错,在JS的情况下参数会进行隐式转换parseInt(100)不会报错
    return parseInt((float*math).toString())/math 
  }
  return (
    <div className="test">
      {mathFloat(0.1 + 0.2, 3)} // 输出0.3
    </div>
  )
ii. 判断值是否正确
    // Number.EPSILON 表示 1 和比最接近 1 且大于 1 的最小 Number 之间的差别
    const NumberEpsilon = (num,res) => {
        return Math.abs(num - res) < Number.EPSILON
    }
    NumberEpsilon(0.1 + 0.2, 0.3)

11. 数组有哪些原生方法

  • 数组转字符串方法: toString()、toLocalString()、join(),其中join()可以指定转换字符串时的分隔符号
  • 数组尾部操作方法: pop()、push(), 其中push()可以传入多个参数
  • 数组首部操作方法: shift()<从第一项移除>、unshift()<从第一项添加>
  • 数组的排序方法: reverse()、sort(),其中sort()方法可以传入一个函数
    // 数组的升序和降序
    const points = [40,100,1,5,25,10]
    points.sort((a,b)=>(a-b)) // 1,5,10,25,40,100
    points.sort((a,b)=>(b-a)) // 100,40,25,10,5,1
  • 数组的连接方法: concat(),返回拼接好的数组,不影响原数组
  • 数组的截取方法: slice(),用于截取数组中的一部分返回,不影响原数组
  • 数组的插入方法: splice()
  • 数组查找特定项的方法: includes()、filter()、some()、every()、map()、forEach()、indexOf()、lastIndexOf()、find()、findIndex()
  • 数组并归:reduce()、reduceRight()

12. Object.is()、"=="、"==="的区别

  • "=="判断两边值是否相等,不判断数据类型,如果类型不一样时会进行强制转换后再比较。比如:0 == "0" // true
  • "==="严格判断,判断两边值是否相当,且判断数据类型。比如:"0"=== 0 // false
  • Object.is()判断两个值是否相等,一般情况下和"==="严格相等相同,处理了特殊情况比如:0和-0不相等,两个NaN是相等的

13. let、const、var的区别

  • var不存在块级作用域,存在变量提升,变量覆盖的问题,声明的变量为全局变量,定义的变量可以不赋初始值
  • let存在块级作用域,变量不会提升,不允许重复声明变量,定义的变量可以不赋初始值
  • const常量,存在块级作用域,变量不会提升,不允许重复声明变量,变量必须有初始值且不可修改

计算机网络

1. HTTP 和 HTTPS 区别

  1. https协议需要CA证书,费用较高,而http协议不用
  2. http协议是超文本传输协议,信息是明文传输的,https则是具有安全性的SSL加密传输协议
  3. http协议的默认端口是80,ttps协议的默认端口为443
  4. http协议的链接是无状态的,https协议使用SSL/TLS协议构建的可加密传输,身份认证的网络协议比http更加安全

2. HTTP1.0 和 HTTP 1.1 区别

  1. 链接方面:http1.0默认非持久链接,http.1通过使用持久链接来使多个http请求复用同一个TCP链接,以此来避免使用非持久链接时每次需建立链接的时延

  2. 资源请求方面:http1.0存在一些浪费带宽的现象,比如客户端只需要请求某个对象的一部分,而服务器却将整个对象传送过来了,并且不支持断点续传。http1.1则在请求头中引入range头域,它允许只请求资源的某个部分,这样就方便了开发者自由的选择以便于充分利用带宽和连接

  3. 缓存方面:在强缓存中http1.0通过设置Expires: Wed, 22 Nov 2019 08:41:00 GMT(具体时间)进行缓存,http1.1通过设置Cache-Control:max-age=3600(具体过期时间点)进行缓存

  4. http1.1新增了host字段用来指定服务器的域名

  5. http1.1比http1.0新增了请求方法:PUT,HEAD,OPTIONS等

3. 状态码

  • 1xx:信息类,表示服务器已接收到请求,需要继续处理
  • 2xx:成功类,表示服务器已成功处理请求,返回响应内容
  • 3xx:重定向类,表示请求的资源已被移动或需要进一步操作才能完成
  • 4xx:客户端错误类,表示客户端请求有语法错误或者无法被服务器满足
  • 5xx:服务器错误类,表示服务器在处理请求时发生的内部错误

常见的状态码有以下几种:

  • 200 OK:表示请求成功,返回响应内容
  • 301 Moved Permanently: 表示请求的资源已被永久重定向到另一个URI,客户端应使用新的URI访问资源
  • 302 Found: 表示请求的资源已被临时重定向到另一个URI,客户端应使用原有URI做访问资源
  • 304 Not Modified: 表示请求的资源未被修改,客户端可以使用缓存中的资源
  • 400 Bad Request: 表示客户端的请求有语法错误或无法被服务器理解
  • 401 Unauthorized: 表示请求需要用户身份认证
  • 403 Forbidden: 表示服务器拒绝执行请求,通常是因为客户端没有权限访问资源
  • 404 Not Found: 表示请求的资源不存在,或者服务器不想让客户端知道该资源的存在
  • 500 Internal Server Error: 表示服务器在处理请求时发生的内部错误,通常是因为程序异常或配置错误
  • 503 Service Unavailable: 表示服务器暂时无法处理请求,通常是因为过载或维护

4. 什么是XSS攻击?

XSS(跨站脚本)攻击是指浏览器中执行恶意脚本(不论是跨域还是同域),从而拿到用户信息并进行操作

  • 窃取cookie
  • 监听用户行为,比如输入账号密码后直接发送到黑客服务器
  • 修改DOM伪造表单登录
  • 在页面中生成浮窗广告

XSS攻击的实现有三种方式:存储型、反射型、文档型。

存储型

顾名思义就是将恶意脚本存储了起来,存储型的XSS将脚本存储在了服务端的数据库,然后在客户端执行这些脚本时,从而达到攻击的效果

反射型

反射型XSS是指恶意脚本做为网络请求的一部分

    http://sanyuan.com?q=<script>alert("你完蛋了")</script>

比如输入此段代码,服务器端会拿到q参数,然后将内容返回给浏览器端,浏览器将这些内容作为HTML的一部分解析,发现是一个脚本,直接执行,这样就被攻击了。

文档型

文档型XSS攻击并不会经过服务器端,而是作为中间人的角色,在数据传输过程中劫持网络包,并通过修改里面的HTML文档达到一种攻击效果

防范措施:一个信念,两个利用

  • 一个信念
    • 不要相信用户的输入,前后端都必须对用户的输入进行转码或者过滤
  • 两个利用
    • 利用CSP: 即浏览器中的内容安全策略
        1. 限制其他域下的资源加载
        1. 禁止向其他域提交数据
        1. 提供上报机制,能够帮助及时发现XSS攻击
    • 利用HttpOnly: 设置CookieHttpOnly属性

浏览器工作原理

1. 浏览器缓存

当我们访问一个网站时会加载各种资源(html文档,js,css,图片等)。浏览器会将一些不经常改变的资源保存在本地,这样下次访问相同的网站时,就直接的从本地加载资源,并不通过请求服务器,这就是浏览器缓存。

  • 强缓存

    • expires 设置具体时间来规定缓存的有效期Expires: Wed, 22 Nov 2019 08:41:00 GMT
    • cache-control 设置 max-age 最大时间的值来进行缓存Cache-Control:max-age=3600
  • 协商缓存

    • Last-Modified 在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间,如果请求头中的这个值小于最后修改时间,说明是时候更新了,返回新的资源,跟常规的HTTP请求响应的流程一样。否则返回304,告诉浏览器直接用缓存。
    • Etag 会在下次请求时,将这个值作为if-None-Match这个字段的内容,并放到请求头中,然后发给服务器,如果两者不一样,则说明要更新了。否则返回304,告诉浏览器直接用缓存。

tips

1. 精准度上Etag更强
2. 性能上Last-Modified更好
3. 两者都支持的情况下,Etag的优先级更高
4. 一般较好的缓存方案是JS、CSS、图片使用强缓存,html文件使用协商缓存

2. 当浏览器中输入一个URL发生了什么

  1. 浏览器的输入和解析:浏览器会根据用户输入的网址,判断是否是合法的URL,如果是,就进行URL解析,获取协议,域名,端口,路径等信息;如果不是,就将输入作为搜索关键词,使用默认的搜索引擎进行搜索。
  2. 缓存判断:浏览器会检查请求的资源是否存在缓存里,如果命中则直接使用,否则则向服务器发送新的请求
  3. DNS解析:浏览器根据域名获取对应的IP地址,这个过程就叫DNS解析。如果一个域名已经解析过,那会把解析的结果直接缓存下来,下次处理直接走缓存,不需要经过DNS解析。
  4. TCP链接建立:浏览器根据ip和端口号(默认80或443)向服务器发起TCP连接请求,这个过程涉及到三次握手,即客户端发送SYN包,服务器回复SYN+ACK包,客户端再发送ACK包。
  5. HTTP发送请求:浏览器在TCP连接建立后,向服务器发送HTTP请求报文,这个报文包含请求行,请求头和请求体等信息。
  6. HTTP响应接收:服务器在收到HTTP请求后,根据请求内容进行处理,并返回HTTP响应报文,这个报文包含了状态行,响应头和响应体等信息。
  7. TCP连接释放:浏览器在接收完HTTP响应后,根据响应头中的Connection字段判断是否需要关闭TCP连接,如果是,则发起四次挥手,即客户端发送FIN包,服务器回复ACK包,服务器发送FIN包,客户端回复ACK包。
  8. HTTP缓存处理:浏览器根据响应头中的缓存字段(如last-modified,Etag,Cache-Control,Expires)判断响应内容是否可以缓存,如果可以,则将其存储在本地缓存中,以便下次访问时直接使用。
  9. HTML文档解析:浏览器根据响应体中的HTML文档进行解析,构建DOM树和CSSOM树,并合并为渲染树。在解析过程中,如果遇到外部资源(如图片,样式表,脚本等),则会发起额外的HTTP请求,并根据资源类型进行相应的处理。
  10. 页面渲染展示:浏览器根据渲染树计算每个节点的布局和样式,并绘制到屏幕上。在渲染过程中如果遇到脚本执行或者用户交互等事件,则可能会触发重绘或者回流。

Vue

1.Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?

  • Vue 使用Object.defineProperty实现双向数据绑定

  • 在初始化实例时对属性进行setter/getter的转化

  • 属性必须在data对象上存在才能让Vue将它转为响应式(这也就造成了Vue无法检测到对象属性的添加或删除)

所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

// 实现源码
  export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 已经存在,直接修改属性值  
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 本身就不是响应式数据, 直接赋值
  if (!ob) {
    target[key] = val
    return val
  }
  // 对属性进行响应式处理
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

vm.$set 的实现原理:

1.如果目标是数组,直接使用数组的 splice 方法触发响应式

2.如果目标是对象,判断属性是否存在、对象是否响应式

3.最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理

defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法

2. vue2和vue3实现原理 (区别)

1. vue2 vue3 数据绑定原理不同
  • vue2 的双向数据绑定是使用ES5中API Object.definePropert() 对数据进行 劫持 结合发布订阅模式的方式来实现的
  • vue3 则是通过 Proxy 来实现数据挟持,创建一个对象的代理,实现基本操作的拦截和自定义,本质是通过拦截对象的内部方法的执行实现代理。vue3 中提供了 reactive() 和 ref() 两个方法将目标数据变为响应式数据。

Object.definePropert()存在的问题

1.  深度监听需要一次性递归
2. 无法监听新增属性和删除属性 ($set()/$delect())
3. 无法监听数组下标的变化

Proxy 的优势

1. 对深度监听无需一次性递归,提高性能
2. 对整个拦截对象直接进行挟持,无需遍历属性一次添加定义 get 和 set 属性
3. 对于原对象生成拦截对象,然后对拦截对象进行相应监听行为确保原对象不变 (可监听数组的变化)
2. vue3 支持碎片,vue2 不支持

vue3 中组件的 template 下可以包含多个根节点,vue2 中只能包含一个根节点否则报错

// vue2中在template里存在多个根节点会报错
<template>
  <header></header>
  <main></main>
  <footer></footer>
</template>

// 只能存在一个根节点,需要用一个<div>来包裹着
<template>
  <div>
    <header></header>
    <main></main>
    <footer></footer>
  </div>
</template>

// vue3
<template>
  <header></header>
  <main></main>
  <footer></footer>
</template>
3. vue2 是选项式API,vue3 是组合式API

vue2 选项式API 在代码里分割了不同的属性:data, methods, computed等,同一块业务逻辑会把数据和方法拆分到不同的代码块中

vue3 组合式API 能让相同的业务的数据方法写在同一块代码区域,使代码更加简便整洁

4. 生命周期不同
  • vue3 删除了beforeCreate/created 在vue2 生命周期钩子名称 + on 前缀,将 beforeDestroy/destroyed 改为 onBeforeUnmount/onUnmounted
vue2vue3
beforeCreate-
created-
boforeMountonBoforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
  • vue3 在使用生命周期钩子时需按需引入,vue2则可直接调用

// vue3
<script setup>     
import { onMounted } from 'vue';   // 使用前需引入生命周期钩子

onMounted(() => {
  // ...
});

// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
onMounted(() => {
  // ...
});
</script>

// vue2
<script>     
export default {           mounted() {   // 直接调用生命周期钩子            
    // ...         
  },           }
</script> 

5. vue异步组件(Suspense)

vue3 提供 Suspense 组件,允许程序在等在异步组件加载完成前渲染兜底的内容,如 loading,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

<tempalte>
  <suspense>
    <template #default>
      <List />
    </template>
    <template #fallback>
      <div>
        Loading...      </div>
    </template>
  </suspense>
</template>
6. vue3 新增 Teleport 传送门组件

Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
  <div class="dialog" v-if="dialogVisible">
    我是弹窗,我直接移动到了body标签下  </div>
</teleport>

将 Teleport 组件标签内部的 dom 插入到 body 中 to 允许接收值: 期望接收一个 CSS 选择器字符串或者一个真实的 DOM 节点。

7. diff 算法不同

vue2 diff算法是就虚拟节点进行同级节点对比再比较子节点,并返回一个patch对象,用来储存节点中的不同,最后用patch记录的消息去更新dom,diff算法会比较每一个vnode,而对于不参与更新的元素是消耗内存的

vue3 diff 算法是在初始化的时候会给每个虚拟节点添加一个patchFlags优化标识,只会比较patchFlags发生变化的虚拟节点,进行视图更新,对于没有变化的元素做静态标记,在渲染的时候直接复用

3. 为什么Proxy一定要配合Reflect使用

Proxy 中的 receiver 会改变this指向,使用 Reflect 可避免这个问题

4. ref 和 reactive 的区别

ref是一个函数,它可以将一个普通数据类型(如字符串、数字等)转换成一个响应式对象,从而让这个数据在vue响应式模式下被追钟,ref返回一个对象,对象有一个.value属性,用来获取和设置这个响应式对象的值

import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const count = ref(0);
    function addCount() {
      count.value += 1;
      console.log(count.value)
    }
    return {
      count,
      addCount,
    };
  },
});

reactive它可以将一个普通的JS对象转换为响应式对象。它会递归地将这个对象的所有属性都转换为响应式对象,从而让整个对象在vue响应式中被追踪。reactive返回一个Proxy对象,用来代替原始对象的访问和修改

import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const state = reactive({
        count: 0,
        message: 'hello'
    });
   console.log(state.count)// 0
   console.log(state.message)// hello
   
   state.count = 1 ; 
   state.message = 'world' ;
   console.log(state.count)// 1
   console.log(state.message)// world
   
    return {
      state,
    };
  },
});

5. MVVM

MVVM 分为 Model,View,ViewModel:

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

一个组件被多次调用会创建多个实例,如果data是一个引用类型,共用一个data,会对实例中的数据产生污染,所以组件中的data必须是一个函数。

7. SPA单页面首屏加载速度慢如何解决

  1. 减小入口文件的体积,在vue-router的时间使用动态加载路由的形式,以函数的形式加载路由,这样可以把各自的路由文件分别打包,在解析给定的路由时,才会加载路由组件

routes:[ 
    path: 'Blogs',
    name: 'ShowBlogs',
    component: () => import('./components/ShowBlogs.vue')
]
  1. 静态资源本地缓存

    • 采用HTTP的缓存方式:cache-control,Etag,Last-modify
    • 合理利用LocalStrage
  2. UI框架按需引入

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
  1. 组件重复打包
 new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3 // 使用CommonsChunkPlugin,配置minchuncks:3,会把使用3次及以上的包抽离出来,放进公共依赖,避免重复加载组件
    }),

使用CommonsChunkPlugin,配置minchuncks:3,会把使用3次及以上的包抽离出来,放进公共依赖,避免重复加载组件

  1. 使用GZip压缩 - 使用CompressionWebpackPlugin配置
 webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(' + config.build.productionGzipExtensions.join('|') + ')$'),
      threshold: 10240,
      minRatio: 0.8
    })
  );
  
  1. 图片资源的压缩
    • 适当压缩图片
    • icon尽量使用在线字体图标或者雪碧图

8. NextTick是什么

在下一次DOM更新循环结束后执行的延迟回调

{{num}}
for(let i=0; i<100000; i++){
    num = i
}

举个例子:如果一个num要循环上万次在页面上打印,没有nextTick,每一次都会触发试图的更新,就更新了上万次的试图,如果有了nextTick,只需要更新一次。

nextTick主要使用了宏微任务,根据不同环境分别执行:

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不执行则采用setTimeout