web前端面试题

目录

css

1、实现水平垂直居中的方式?

2、常用伪元素有哪一些?

::before和::after

::first-line

::first-letter

::selection

::placeholder

3、Flex布局

4、css控制文本显示省略号

js

1、本地存储有哪一些?他们三者有什么区别?

2、JS的数据类型?如何判断js的数据类型?

3、操作数组的方式有哪些?

4、说一下ES6的新特性有哪些?

1.变量声明:const和let

2.模板字符串

3.箭头函数(Arrow Functions)

4. 函数的参数默认值

5.Spread / Rest 操作符

6.对象和数组解构

7.对象超类

8.for...of 和 for...in

9.ES6中的类

5、Let、const、var三者有什么区别?

6、数组去重有哪些办法?

(1)set()  ES6方法 

(2)indexOf()  

(3)includes()  

(4)双重for循环

(5)Map()  

7、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?

(1)JSON.parse(JSON.stringify())

(2)递归函数实现

 8、说一下防抖和节流。怎么实现?

 9、你的登录拦截怎么实现的?---待完善

10、闭包是什么?如何实现?用闭包的原理做过哪些?

11、bind  call  apply 的区别 ---待完善

12、什么是Js原型?原型链是什么?

13、你是如何实现资源的懒加载或者预加载的

懒加载的原理

方式1:通过 元素距离顶部的高度 - 滚动条滚动的高度 < =窗口可视化区域高度 进行判断;

方式2:getBoundingClientRect()获取元素位置, 这个方法没有参数用于获得页面中某个元素的左,上, 右和下分别相对浏览器视窗的位置。是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)该函数返回一个0bject对象, 该对象有6个属性: top, lef, right, bottom , width, height;

预加载?

14、函数柯里化

15、数组 字符串 对象 之间的相互转化

vue

1、vue的双向绑定原理是什么?里面的关键点在哪里?

2、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?

3、组件通讯方式有哪一些?

(1)props 

(2)$emit(v-on)

(3) .sync

(4)ref

(5)$children / $parent

(6)Vuex

(7)slot

4、Vuex有几个属性及作用?

1、state

2、getters

3、mutations

4、actions

5、modules

5、Vue的监听属性和计算属性有什么区别?

6、Vue的导航守卫有哪一些?

7、Vue2.0和vue3.0有什么区别?---待完善

一:项目架构

二:数据响应式系统

三:虚拟DOM

四:生命周期

五:组件实例的创建方式

六:路由

七:请求

八:模板指令

8、Vue常用的指令有哪些?

9、v-If和v-show有什么区别?

10、v-for为什么要加一个key?---待完善

11、父子组件生命周期执行顺序是怎么样的?

12、keep-alive是什么?有哪几个生命周期阶段?---待完善

13、路由原理

优化&适配

1、移动端如何适配不同屏幕尺寸?

适配1——viewport

适配2——媒体查询

适配3——相对单位之 rem 

适配4——相对单位之 vw

适配5——微信小程序的rpx

2、浏览器渲染原理

渲染过程

什么情况阻塞渲染

重绘和回流

减少重绘和回流

3、谈谈你在之前的项目中是如何进行Web性能优化的?请举例说明你采取了哪些措施。

1. 性能采集

2.性能优化

(1)加载性能优化

(2)渲染性能优化

3.性能优化碎片化内容

(1)图片优化

(2)图片加载优化

(3)DNS预解析

(4)防抖

(5)节流

(6)预加载

(7)预渲染

(8)懒执行

(9)懒加载

(10)CDN

4、Review代码会考虑哪些因素,说一说对好代码的理解

代码结构

代码逻辑

代码的可读性和可维护性

代码的可靠性

5、什么是跨域?如何解决跨域问题

6、浏览器缓存机制

缓存位置

    1. Service Worker

    2. Memory Cache

    3. Disk Cache

    4. Push Cache

    5. 网络请求

缓存策略

    1.强缓存

    2.协商缓存

7、安全防范

一:XSS攻击

二:CSRF攻击

三:点击劫持

8、监控

方式一:页面埋点

方式二:性能监控

方式三:异常监控

1:代码报错

2:接口异常上报

9、TCP/UDP

TCP 和 UDP区别:

UDP特点

TCP头部

TCP三次握手

TCP断开链接四次握手

10、HTTP


css

1、实现水平垂直居中的方式?

1: flex    display: flex  ; justify-content: center;  align-items: center;

2: display + margin.   父元素display: flex;   子元素margin: auto;

3: table-cell  父元素display: table-cell; vertical-align: middle;   子元素margin: auto;

4: position + translate    父元素position: relative;
        子元素position: absolute;  top: 50%;  left: 50%;  transform: translate(-50%, -50%);

5: position + margin  关键语句:父元素position: relative;
        子元素position: absolute;  top: 0;  bottom: 0;  left: 0;  right: 0;  margin: auto;

6: position+calc( )    父元素position: relative;
        子元素position: absolute;   top: calc(50% - 50px);   left: calc(50% - 50px);

7: position+负margin

        父元素position: relative;
        子元素position: absolute;   top:50%; left:50%;   margin: -50px;

8: Grid(网格布局)  兼容性不如Flex布局

        父元素display:grid;      子元素align-self: center;  justify-self: center;

9: 强行居中

        父元素 text-align: center;
        子元素line-height:父元素height;

10: display + vertical-align

        父元素 display: table;
        子元素display: table-cell;   vertical-align: middle;   text-align: center;


2、常用伪元素有哪一些?
::before和::after
  • 默认display: inline;
  • 必须设置content属性,否则一切都是无用功;
  • 默认user-select: none,::before和::after的内容无法被用户通过鼠标长按滑动选中的;
  • 会继承原本元素的CSS属性(如原元素的颜色等)
  • .clear::after{ clear: both; content:''; display: block; }
::first-line

        只能用于块级元素。用于设置附属元素的第一个行内容的样式。可用的CSS属性为font,color,background,word-spacing,letter-spacing,text-decoration,vertical-align,text-transform,line-height,clear。

::first-letter

        只能用于块级元素。用于设置附属元素的第一个字母的样式。可用的CSS属性为font,color,background,marin,padding,border,text-decoration,vertical-align,text-transform,line-height,float,clear。

::selection

        匹配鼠标长按拖动选中的内容。可用的CSS属性为background,color

        span::selection{ color: red; }

::placeholder

        用于设置input元素的placeholder内容的样式。

        input::placeholder { color: red; font-size: 1.2em;}

3、Flex布局

详情参考阮一峰网址:Flex 布局教程:语法篇 - 阮一峰的网络日志

  • flex-direction:决定主轴的方向(即项目的排列方向)。
  • flex-wrap:换行。
  • flex-flow:flex-directionflex-wrap的简写形式,默认值为row nowrap。
  • justify-content:在主轴上的对齐方式。
  • align-items:交叉轴上如何对齐。
  • align-content:多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.box {
    flex-direction: row(水平/起点左侧) | row-reverse(水平/起点右侧) | column(垂直/上) | column-reverse(垂直/下);
    flex-wrap: nowrap(不换行) | wrap(换行,第一行在上) | wrap-reverse(换行,第一行在下);
    flex-flow: <flex-direction> || <flex-wrap>;
    justify-content: flex-start(左对齐) | flex-end(右对齐) | center(居中) | space-between(两端对齐,项目之间的间隔都相等) | space-around(每个项目两侧的间隔相等);
    align-items: flex-start(交叉轴的起点对齐) | flex-end(交叉轴的终点对齐) | center(交叉轴的中点对齐) | baseline(项目的第一行文字的基线对齐) | stretch(默认值,如果项目未设置高度或设为auto,将占满整个容器的高度);
    align-content: flex-start(与交叉轴的起点对齐) | flex-end(终点对齐) | center(中点对齐) | space-between(两端对齐,轴线之间的间隔平均分布) | space-around(每根轴线两侧的间隔都相等) | stretch(默认值,轴线占满整个交叉轴);
}
4、css控制文本显示省略号

1. 单行文本超出部分显示省略号
        ①溢出隐藏 —— overflow: hidden;
        ②让文本不会换行, 在同一行显示——white-space: nowrap;
        ③用省略号来代表未显示完的文本 ——text-overflow: ellipsis;

2. 多行超出部分显示省略号
        ①溢出隐藏 —— overflow: hidden;

        ②用省略号来代表未显示完的文本 ——text-overflow: ellipsis;

        ③必须设置盒子属性为-webkit-box——display: -webkit-box;

        ④设置超出几行后,超出部分显示省略号,比如-webkit-line-clamp:2;则表示超出2行的部分显示省略号;

        ⑤单词破坏:主要用于破坏英文单词的整体性,即在英文单词还没有在一行完全展示时就换行,简单的理解就是一个单词可能会被分成两行展示——word-break: break-all;

        ⑥盒子实现多行显示的必要条件,文字垂直展示——-webkit-box-orient: vertical;

js

1、本地存储有哪一些?他们三者有什么区别?

存储三种方式:cookie、sessionStorage、localStorage
1.cookie在浏览器和服务器间来回传递。

   sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。

2.存储大小限制也不同
    cookie数据不能超过4k,同时因为每次http请求都会携带cookie,只适合保存很小的数据。
    sessionStorage和localStorage 存储大小可以达到5M或更大。

3.数据有效期不同

    cookie可以设置过期时间,在设置的过期时间之前一直有效,即使窗口或浏览器关闭。

    sessionStorage:仅在当前浏览器窗口关闭前有效,不能持久保持;
    localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;

2、JS的数据类型?如何判断js的数据类型?

基本数据类型:number,undefined,boolean,string,null
复杂数据类型:object
ES6中又新增了一个数据类型:Symbol (表示一个独一无二的值),也是一个基本数据类型

判断类型:

1: typeOf()

//对于原始类型除了null,都可以显示正确的类型
typeof 1; //number
typeof '1'; //string
typeof true; //boolean
typeof undefined; //undefined
typeof null; //object

typeof []; //object
typeof {}; //object

let s = Symbol();
typeof s // "symbol"

可以看出上面代码中判断[]和{}都是object,所以typeOf()只是适合判断基本的数据类型
注:为什么null打印出来也是object,因为null被认为是对象的占位符

2: instanceof  对于引用类型的类型检测支持很好,但是无法对基本类型数据进行类型检测

console.log({} instanceof Object); // true
console.log([] instanceof Array); // true
console.log(new Date() instanceof Date); // true
console.log(function () { } instanceof Function); // true

console.log('123' instanceof String); // false
var str = new String('123');
str instanceof String; //true

3: Object.prototype.toString.call()   对于基本类型和引用类型都可以判断(除了自定义的类)

//1、基本数据类型
var num1 = 1;
var num2 = new Number(1);
console.log(Object.prototype.toString.call(num1)); // [object Number]
console.log(Object.prototype.toString.call(num2)); // [object Number]

//2、数组
console.log(Object.prototype.toString.call([])) // [object Array]

//3、函数
console.log(Object.prototype.toString.call(function(){}))// [object Function]

//4、自定义对象
function P() { }
console.log(Object.prototype.toString.call(new P())) // [object Object]
3、操作数组的方式有哪些?
var arr = [1, 2, 3, 4, 5, 6];  
var arr1=['a', 'b', 'c', 'd'];
var arr2 = [4, 5, 2, 7, 2, 9, 4, 2];
var arr3 = [[1, 2], [3, 4], 5];
var groups = [
     { id: 1, age: 18 },
     { id: 2, age: 18 },
     { id: 3, age: 18 }
]


Math.min(...arr);  //1
Math.max(...arr);  //6
console.log(arr.length); //6

//includes(searchElement,fromIndex)
//第二个参数(fromIndex)可选:从该索引处开始查找searchElement。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0。
arr.includes(8); //false
arr.includes(3, -5); //true

//将一个数组转为字符串,并将新的数组作为函数返回值返回,不会改变原数组
arr.join()    //'1,2,3,4,5,6' 默认‘,’
arr.join('_')    //'1_2_3_4_5_6'//指定分隔符

//slice(begin,end)  
//截取片段:左闭右开,不传参不截取,直接拷贝一份,,不会改变原数组
arr.slice(0,2)     //[1, 2]
arr.slice()     // [1, 2, 3, 4, 5, 6]


//indexOf()
//在数组当中第一次出现的位置,若没有找到该字符,则返回 -1
arr.indexOf(3)     //2


//lastIndexOf()
//最后一次出现在的位置,若没有找到该字符,则返回 -1
arr.lastIndexOf(4)    //3


arr.concat(arr1)     // [1, 2, 3, 4, 5, 6, 'a', 'b', 'c', 'd']
[...arr,...arr1]     // [1, 2, 3, 4, 5, 6, 'a', 'b', 'c', 'd']


//向数组末尾进行追加 [会更改原数组]
arr.push(101)   //7  //arr =  [1, 2, 3, 4, 5, 6, 101]

//向数组末尾进行删除,并且返回这个元素 [会更改原数组]
arr.pop()  //101  //arr = [1, 2, 3, 4, 5, 6]

//删除数组的第一个元素,并返回这个元素。 [会更改原数组]
arr.shift()  //1  // arr = [2, 3, 4, 5, 6]

//向数组最前面进行追加,返回数组长度 [会更改原数组]
arr.unshift(1,23,34,56)  //9  //arr =  [1, 23, 34, 56, 2, 3, 4, 5, 6]


//会更改原数组
arr2.sort((a,b) => a - b)    //[2, 2, 2, 4, 4, 5, 7, 9]  正序
arr2.sort((a,b) => b - a)    //[9, 7, 5, 4, 4, 2, 2, 2]  倒序


//forEach
//没有返回值,不会返回数组
var result = arr1.forEach(function (item, index) {
    console.log(item);  // 数组元素  a b c d
    console.log(index); // 索引号
}) //a 0 b 1 c 2 d 3
console.log(result) //undefined


//翻转数组,会更改原数组
arr.reverse() // [6, 5, 4, 3, 2, 1]


//splice()
// 删除:第一个参数表示要删除的索引,第二个参数删多少个,返回被删除的元素数组
arr.splice(3,2)    // [4, 5]   此时arr = [1, 2, 3, 6]
//替换:第一个参数表示开始替换的索引位置,第二个参数表示替换多少个,第三个参数为要替换的值。返回被替换的元素数组
arr.splice(1,2,'d')     //[2, 3]   此时arr = [1, 'd', 6]
//插入: 6 -> c, 插入是在开始的元素前面添加的
arr.splice(2,0,'c')    //[]    此时arr =  [1, 'd', 'c', 6]


//every()
//根据一个条件,判断当前数组是不是都满足这个条件
// 判断当前数组中的每一项的age是不是都是18
var flag = groups.every(function (group) {
     return group.age === 18
})  //flag = true


//some()
//只要有一个即可,判断当前数组是不是有一个满足这个条件
var flag = groups.some(function (group) {
     return group.id === 1
})  //flag = true


//map()
//对数组中的每个元素进行处理,返回新的数组
var ids = groups.map(group => {
     return {
          'xx': group.id + 100
     }
})    //ids =  [ { xx: 101 }, { xx: 102 }, { xx: 103 } ]

arr.map(item =>{
    return item *item
})    // [1, 4, 9, 16, 25, 36]


//filter() 过滤
groups.filter(groups => groups.id == 2 );     //[{id: 2, age: 18}]


//find() 找一个条件,只要找到一个符合立马返回
groups.find(groups => groups.id == 2 );    //{id: 2, age: 18}


//findIndex()    找第一个符合条件的索引
groups.findIndex(groups => groups.id ==2 );    //1

//flat()  数组扁平化处理,创建一个新数组,其中所有子数组元素以递归方式连接到特定深度。
arr3.flat()    //[1, 2, 3, 4, 5]


//reduce()
//数组求和
arr.reduce((a,b) =>{
    return a+b
})    //21
//数组扁平化处理,将二维数组转化为一维 将数组元素展开
arr3.reduce((a,b) => a.concat(b), [])    // [1, 2, 3, 4, 5]

4、说一下ES6的新特性有哪些?
1.变量声明:const和let
  • let 关键词声明的变量不具备变量提升(hoisting)特性
  • let 和 const 声明只在最靠近的一个块中(花括号内)有效
  • 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
  • const 在声明时必须被赋值
2.模板字符串

es6之前使用‘\’和‘+’号拼接字符串

$("body").html("This including 
student's\ " + name + ", " + seatNumber + ", " + sex + " and so on."); 

es6之后使用 

  1.  基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  2. ES6反引号(``)直接搞定;
$("body").html(`This including 
student's ${name}, ${seatNumber}, ${sex} and so on.`); 
3.箭头函数(Arrow Functions)

特点:不需要 function 关键字来创建函数
           省略 return 关键字
           继承当前上下文的 this 关键字

// ES5 
var add = function (a, b) { 
  return a + b; 
}; 
// 使用箭头函数 
var add = (a, b) => a + b;

// ES5 
[1,2,3].map((function(x){ 
   return x + 1;
 }).bind(this)); 
// 使用箭头函数 
[1,2,3].map(x => x + 1); 
4. 函数的参数默认值
当未传入参数时,text = 'default';
// ES6之前 
function printText(text) {   
  text = text || 'default';     
  console.log(text); 
} 
// ES6; 
function printText(text = 'default') {
     console.log(text);
} 

printText('hello world'); // hello world
printText();// default 
5.Spread / Rest 操作符

Spread / Rest 操作符指的是 ...,具体是 Spread 还是 Rest 需要看上下文语境。
当被用于迭代器中时,它是一个 Spread 操作符:

function foo(x,y,z) {
   console.log(x,y,z);
 } 
let arr = [1,2,3]; foo(...arr); // 1 2 3 

当被用于函数传参时,是一个 Rest 操作符

function foo(...args) {
   console.log(args);
 } 
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
6.对象和数组解构
// 对象 
const student = {
    name: 'Sam',
    age: 22,
    sex: '男' 
} 
// ES5;
const name = student.name; 
const age  = student.age;
const sex  = student.sex;
console.log(name + ' --- ' + age + ' --- ' + sex);

// ES6
const { name, age, sex } = student; 
console.log(name + ' --- ' + age + ' --- ' + sex); 
7.对象超类
var parent = {
 foo() {
     console.log("Hello from the Parent");
 }
} 
var child = {
 foo() {
  super.foo();
  console.log("Hello from the Child");
 }
}   
Object.setPrototypeOf(child, parent);  //为现有对象设置原型,返回一个新对象
接收两个参数:第一个是现有对象,第二是原型对象
child.foo(); // Hello from the Parent  Hello from the Child
8.for...of 和 for...in
var arr = ['a', 'b', 'c', 'd'];

//只能获得对象的键名,不能直接获取键值
for (let a in arr) {
  console.log(a); // 0 1 2 3
}

//允许遍历获得键值
for (let b of arr) {
  console.log(b); // a b c d
9.ES6中的类

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:

class Student {
  constructor() {
    console.log("I'm a student.");
  }
 
  study() {
    console.log('study!');
  }
 
  static read() {
    console.log("Reading Now.");
  }
}
 
console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."

类中的继承和超集:

class Phone {
  constructor() {
    console.log("I'm a phone.");
  }
}
 
class MI extends Phone {
  constructor() {
    super();
    console.log("I'm a phone designed by xiaomi");
  }
}
 
let mi8 = new MI();

extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。

注意:

  • 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
  • 在类中定义函数不需要使用 function 关键词
5、Let、const、var三者有什么区别?

(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量提升: var存在变量提升,let和const不存在变量提升,即变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向:let创建的变量是可以更改指针指向(可以重新赋值)。而const声明的变量是不允许改变指针的指向。

6、数组去重有哪些办法?
(1)set()  ES6方法 

Set本身是一个构造函数,用来生成Set数据结构。

它类似于数组,但里面的成员是唯一的,不重复的。

const set = new Set(['s', 'd','y', 'd',1, 2, 2]);
[...set] //['s', 'd', 'y', 1, 2]
(2)indexOf()  

indexOf():查找一个字符串中,第一次出现指定字符串的位置。

indexOf方法返回一个整数值,指出 string对象内子字符串的开始位置。如果没有找到该字符串则返回-1。

var arr = ['s', 'd','y', 'd',1, 2, 2]
var array = [];
for(var i=0;i<arr.length;i++){
        if(array.indexOf(arr[i]) === -1){
            array.push(arr[i]);
        }
}
console.log(array)  //['s', 'd', 'y', 1, 2]
(3)includes()  
var arr = ['s', 'd','y', 'd',1, 2, 2]
var array = [];
for(var i=0;i<arr.length;i++){
        if(!array.includes(arr[i])){
            array.push(arr[i]);
        }
}
console.log(array)  //['s', 'd', 'y', 1, 2]
(4)双重for循环
var arr = ['s', 'd','y', 'd',1, 2, 2]
var array = [];
for(var i=0;i<arr.length;i++){
    var flag = false;
    for(var j=0;j<array.length;j++){
        if(arr[i] == array[j]){
            flag = true;
            break;
        }
    }
    if(!flag){
        array.push(arr[i])
    }
}
console.log(array)  // ['s', 'd', 'y', 1, 2]
(5)Map()  

        Map中的一些常用方法

        1、has():检测map对象中键是否存在,存在返回true,不存在返回false。

        2、set():为map对象添加键值对,方法里里面跟两个参数(键,值),如果只传入一个参数,则值为undefine。

        3、get():通过键来获取对应的值。

var map = new Map();
var arr = ['s', 'd','y', 'd',1, 2, 2]
var array = [];
for(var i=0;i<arr.length;i++){
   if(!map.has(arr[i])){
       map.set(arr[i],true);
       array.push(arr[i]);
   }
}
console.log(array) //['s', 'd', 'y', 1, 2]
7、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?

浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块

方法一:Object.assign
let a={
    name: '张三'
}
let b = Object.assign({},a);
a.name = '李四';
console.log(b); //{name: '张三'}

方法二:展开运算符 ...
let a={
    name: '张三'
}
let b = {...a};
a.name = '李四';
console.log(b); //{name: '张三'}

深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

(1)JSON.parse(JSON.stringify())
var arr = [1,2,3];
var b = JSON.parse(JSON.stringify(arr));
b === arr //false

原理:就是用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,

缺点:当对象里面有函数的话,深拷贝后,函数会消失

(2)递归函数实现

原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

    var obj = {   //原数据,包含字符串、对象、函数、数组等不同的类型
        name: "test",
        main: {
            a: 1,
            b: 2
        },
        fn: function () {
 
        },
        friends: [1, 2, 3, [22, 33]]
    }
 
    function copy(obj) {
        let newobj = null;   //声明一个变量用来储存拷贝之后的内容
        //判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可,
        //由于null不可以循环但类型又是object,所以这个需要对null进行判断
        if (typeof (obj) == 'object' && obj !== null) {
 
            //声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存
            newobj = obj instanceof Array ? [] : {};
            //循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数
            for (var i in obj) {
                newobj[i] = copy(obj[i])
            }
        } else {
            newobj = obj
        }
        console.log('77', newobj)
        return newobj;    //函数必须有返回值,否则结构为undefined
    }
    var obj2 = copy(obj)
    //obj2.name = '修改成功'
    //obj2.main.a = 100
    obj == obj //false

使用深拷贝的注意事项?

如果对象比较大,层级也比较多,深拷贝会带来性能上的问题。所以在遇到需要使用深拷贝,考虑有没有其他的方案,实际应用中主要还是以浅拷贝为主

 8、说一下防抖和节流。怎么实现?

防抖 = 回城。 节流 = 技能(CD冷却)  ------  游戏爱好者自行理解

防抖和节流是用于控制函数执行频率的方法,可以通过setTimeout和时间戳等方式实现。

防抖:在一定时间内只执行最后一次操作。可以用于处理一些频繁触发的事件,如窗口大小改变、输入框输入等。

function debounce(fn, delay) {
  let timer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

节流:在一定时间内只执行一次操作。用于处理一些频繁触发的事件,如页面滚动、鼠标移动等。

function throttle(fn, delay) {
  let timer;
  return function() {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(function() {
        fn.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}
 9、你的登录拦截怎么实现的?---待完善

1:登录时把信息存储在session中

2:定义拦截器,判断是否登录

3:没有登录时重定向至登录页(同步的方式中重定向是成功的,异步,重定向是无效的)

10、闭包是什么?如何实现?用闭包的原理做过哪些?

概念:当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了,但是被内部函数引用的外部函数的变量依然会保存在内存中,我们把引用了其他函数作用域变量的函数和这些被引用变量的集合,称为闭包(Closure)

通俗来说:函数A内部有一个函数B,函数B可以访问函数A中的变量,那么函数B就叫做闭包

function a(){
    function b(){
        var bb = 888
        console.log(aa);  //输出:666
    }
    var aa = 666
    return b
}
var demo = a()
demo() //666

在上面的代码示例中,a函数定义了一个名为aa的变量和一个名为b的函数,b函数引用了aa变量,因此JavaScript引擎会保留a函数的作用域链,b函数可以访问a函数的执行上下文,b函数内用到了外部函数a的变量aa,在a函数调用结束后该函数执行上下文会销毁,但会保留一部分留在内存中供b函数使用,这就形成了闭包。

具体来说,当内部函数引用外部函数的变量时,外部函数的作用域链将被保留在内存中,以便内部函数可以访问这些变量。

这种函数嵌套和变量共享的方式就是闭包的核心概念。当一个函数返回另一个函数时,它实际上返回了一个闭包,其中包含了原函数定义时的词法作用域和相关变量。

用途:

1.封装私有变量

闭包可以用于封装私有变量,以防止其被外部访问和修改。封装私有变量可以一定程度上防止全局变量污染,使用闭包封装私有变量可以将这些变量限制在函数内部或模块内部,从而减少了全局变量的数量,降低了全局变量被误用或意外修改的风险。

在下面这个例子中,调用函数,输出的结果都是1,但是显然我们的代码效果是想让count每次加一的。

function add() {
    let count = 0;
    count++;
    console.log(count);
}
add()   //输出1
add()   //输出1
add()   //输出1

一种显而易见的方法是将count提到函数体外,作为全局变量。这么做当然是可以解决问题,但是在实际开发中,一个项目由多人共同开发,你不清楚别人定义的变量名称是什么,这么做有点冒险,有什么其他的办法可以解决这个问题呢?

function add(){
    let count = 0
    function a(){
        count++
        console.log(count);
    }
    return a
}
var res = add() 
res() //1 
res() //2
res() //3

答案是用闭包。在上面的代码示例中,add函数返回了一个闭包a,其中包含了count变量。由于count只在add函数内部定义,因此外部无法直接访问它。但是,由于a函数引用了count变量,因此count变量的值可以在闭包内部被修改和访问。这种方式可以用于封装一些私有的数据和逻辑。

2. 做缓存

函数一旦被执行完毕,其内存就会被销毁,而闭包的存在,就可以保有内部环境的作用域。

function foo(){
    var myName ='张三'
    let test1 = 1
    const test2 = 2 
    var innerBar={
        getName: function(){
            console.log(test1);
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()   
console.log(bar.getName()); //输出:1 张三
bar.setName('李四')
console.log(bar.getName()); //输出:1 李四

这里var bar = foo() 执行完后本来应该被销毁,但是因为形成了闭包,所以导致foo执行上下文没有被销毁干净,被引用了的变量myName、test1没被销毁,闭包里存放的就是变量myName、test1,这个闭包就像是setName、getName的专属背包,setName、getName依然可以使用foo执行上下文中的test1和myName。

缺点:

由于闭包会引用外部函数的变量,但是这些变量在外部函数执行完毕后没有被释放,那么这些变量会一直存在于内存中,总的内存大小不变,但是可用内存空间变小了。只有在页面关闭后,闭包占用的内存才会被回收,这就造成了所谓的内存泄漏。

解决内存泄漏:

1.及时释放闭包:手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。
2.使用立即执行函数:在创建闭包时,将需要保留的变量传递给一个立即执行函数,并将这些变量作为参数传递给闭包函数,这样可以保留所需的变量,而不会导致其他变量的内存泄漏。

经典面试题

循环中使用闭包解决var定义函数的问题

for(let i=0;i<5; i++){
    setTimeout(function(){
        console.log(i)
    },
    i*1000)
}
//5  
现象:输出5个5。setTimeout是一个异步函数,会把循环先全部执行完,这时i就是5了

解决办法一:使用闭包的方式
使用立即执行函数将i传入函数内部,这时候值就被固定在了参数j上边不能改变,下次执行到timer这个闭包的时候,就可以使用外部变量j,从而达到目的
for(var i=0;i<5; i++){
    ;(function(j){
        setTimeout(function(){
            console.log(j)
        },
        i*1000)  
    })(i)
}
//0 1 2 3 4

解决方法二:使用setTimeout的第三个参数,这个参数会被当作timer函数的参数传入
for(var i=0;i<5; i++){
    setTimeout(function(j){
        console.log(j)
    },
    i*1000,
    i)
}
//0 1 2 3 4

解决方法三:使用let定义i ,最推荐的方式
for(let i=0;i<5; i++){
    setTimeout(function(){
        console.log(i)
    },
    i*1000)
}
//0 1 2 3 4

11、bind  call  apply 的区别 ---待完善

共同点 :

        都可以改变this指向;

        三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window

不同点:

        call 和 apply 会调用函数, 并且改变函数内部this指向.

        call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递,且apply和call是一次性传入参数,而bind可以分为多次传入

        bind是返回绑定this之后的函数

应用场景

        call 经常做继承.

        apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值

        bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向

12、什么是Js原型?原型链是什么?

一、prototype

在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。

例如:

function Person(age) {

    this.age = age       

}

Person.prototype.name = 'kavin'

var person1 = new Person()

var person2 = new Person()

console.log(person1.name) //kavin

console.log(person2.name)  //kavin

上述例子中,函数的prototype指向了一个对象,而这个对象正是调用构造函数时创建的实例的原型,也就是person1和person2的原型。

原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。

让我们用一张图表示构造函数和实例原型之间的关系:

二、__proto__

这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

function Person() {


}

var person = new Person();

console.log(person.__proto__ === Person.prototype); // true

而关系图:

补充说明:

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。

三、constructor

每个原型都有一个constructor属性,指向该关联的构造函数。

function Person() {

}

console.log(Person===Person.prototype.constructor)  //true

所以再更新下关系图:

function Person() {


}


var person = new Person();


console.log(person.__proto__ == Person.prototype) // true

console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型

console.log(Object.getPrototypeOf(person) === Person.prototype) // true

补充说明:

function Person() {

}

var person = new Person();

console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:

person.constructor === Person.prototype.constructor

四、实例与原型

 当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

function Person() {

}
Person.prototype.name = 'Kevin';

var person = new Person();


person.name = 'Daisy';
console.log(person.name) // Daisy


delete person.name;
console.log(person.name) // Kevin

在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。

但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。

但是万一还没有找到呢?原型的原型又是什么呢?

五、原型的原型

 在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:

var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin

其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype ,所以我们再更新下关系图:

六、原型链

 简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。——摘自《javascript高级程序设计》

其实简单来说,就是上述四-五的过程。

继上述五中所说,那 Object.prototype 的原型呢?

console.log(Object.prototype.__proto__ === null) // true

引用阮一峰老师的 《undefined与null的区别》 就是:

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

最后一张关系图也可以更新为:

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

13、你是如何实现资源的懒加载或者预加载的
懒加载的原理


        懒加载也可以叫做延迟加载,当访问一个页面的时候,先把img元素伙食其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样只需要请求一次,俗称占位图);页面中的img元素,如果没有src属性, 浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求。

懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把真正的路径存在元素的"data-url"(这个名字起个自己认识好记的就行)属性里,要用的时候就取出来放在src里面

实现步骤

  1. 首先,不要将图片地址放到src属性中,而是放到其它属性(data- url)中。
  2. 页面加载完成后,判断图片是否在用户的视野内,如果在,则将data-url属性中的值取出存放到src属性
方式1:通过 元素距离顶部的高度 - 滚动条滚动的高度 < =窗口可视化区域高度 进行判断;
   /*元素距离顶部的高度-页面被卷去的高度<=浏览器可视区域的高度
    如果条件满足,就可以替换图片的src属性
    */
   //获取图片
    const imgArr = document.querySelectorAll("img");
    //初始化执行
    lazyLoad();

    //监听用户是否滚动滚动条,但是这高触发事件,所以需要结合节流优化
    window.addEventListener("scroll", throttle(lazyLoad, 2000));

    function lazyLoad() {
        imgArr.forEach(item => {
            //1、获取图片距离顶部的高度
            const imgOffsetTop = item.offsetTop;
            //2、获取浏览器的可视区域的高度
            const wHeight = window.innerHeight;
            //3、获取页面被卷去的高度
            const scrollHeight = document.documentElement.scrollTop;
            //判断图片是否将要出现
            if (imgOffsetTop - scrollHeight <= wHeight) {
                item.src = item.getAttribute("data-src");
            }
        })
    }

    //节流
    function throttle(fun, time = 250) {
        let lastTime = 0;//最后一次执行的时间
        return function (...args) {
            const now = new Date().getTime();//获取当前的时间
            if (now - lastTime >= time) {
                fun();
                lastTime = now;
            }
        }
    }
方式2:getBoundingClientRect()
获取元素位置, 这个方法没有参数
用于获得页面中某个元素的左,上, 右和下分别相对浏览器视窗的位置。
是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)
该函数返回一个0bject对象, 该对象有6个属性: top, lef, right, bottom , width, height;

    const imgArr = document.querySelectorAll("img");
    //初始化执行
    lazyLoad()

    //监听用户是否滚动滚动条,但是这高触发事件,所以需要结合节流优化
   //监听用户是否滚动滚动条,但是这高触发事件,所以需要结合节流优化
    window.addEventListener("scroll", throttle(lazyLoad, 2000));

    function lazyLoad() {
        imgArr.forEach(item => {
            //获取图片和顶部的高度
            const imgTop = item.getBoundingClientRect().top;
            //获取浏览器可视化区域高度
            const wHeight = window.innerHeight;
            //图片和顶部的高度小于浏览器窗口可视化区域的高度
            if (imgTop <= wHeight) {
                //getAttribute获取自定义属性
                // item.src = item.getAttribute("data-src");
                //也可以使用dataset来获取自定义属性的值
                item.src = item.dataset.src;
            }
        })
    }
    //节流
    function throttle(fun, time = 250) {
        let lastTime = 0;//最后一次执行的时间
        return function (...args) {
            const now = new Date().getTime();//获取当前的时间
            if (now - lastTime >= time) {
                fun();
                lastTime = now;
            }
        }
    }
预加载?

将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。

<div>
    <p></p>
    <img src="../img/1.jpg" alt="">
</div>

<script type="text/javascript">
    const imgArr = ["../img/2.jpg", "../img/3.jpg", "../img/4.jpg", "../img/5.jpg", "../img/6.jpg", "../img/7.jpg"]
    const img = document.querySelector("img");
    const p = document.querySelector("p");
    //定义默认的index的值
    let index = 0;
    p.innerHTML = `实现预加载的效果,我是第${index + 1}张图片`;
    img.addEventListener("click", (e) => {
        if (index < imgArr.length) {
            img.src = imgArr[index];
            index++;
            p.innerHTML = `实现预加载的效果,我是第${index + 1}张图片`;
            if (index < imgArr.length) {
                //切换图片后,同时让浏览器提前加载下一张图片
                preLoad(imgArr[index])
            }
        } else {
            confirm("没有图片了")
        }
    }, false)

    //调用预加载函数,页面一开始就加载数组的第一个元素
    preLoad(imgArr[0])

    //定义预加载函数
    function preLoad(src) {
        //当图片失去焦点后
        img.addEventListener("load", () => {
            //创建一个新的img标签
            const imgNew = document.createElement("img");
            //给img标签添加src属性为我们传进来的src
            imgNew.src = src;
        })
    }
</script>

14、函数柯里化

在一个函数中,首先填充几个函数,然后返回一个新的函数的技术,称为函数柯里化。通常在不侵入函数的前提下,为函数预设通用参数,供多次重复调用。

let add = function(x){
    return function(y){
        return x+y;
    }
}
let a = add(1);
a(20) //21;
15、数组 字符串 对象 之间的相互转化
var arr=[1,2,4,3,6,3,4];
var str = 'javascript';
var jsonString = '{"error":1,"data":"用户不存在"}';
var obj = {
    name:'张三',
    age:'18',
    sex:'男'
}
// 数组转字符串
arr.toString();//1,2,4,3,6,3,4
arr.toLocaleString();//1,2,4,3,6,3,4
arr.join('-');//1-2-4-3-6-3-4

// 字符串转数组
str.split('')// ['j', 'a', 'v', 'a', 's', 'c', 'r', 'i', 'p', 't']
Object.values(str);//['j', 'a', 'v', 'a', 's', 'c', 'r', 'i', 'p', 't']
Array.from(str);//['j', 'a', 'v', 'a', 's', 'c', 'r', 'i', 'p', 't']
[...str];//['j', 'a', 'v', 'a', 's', 'c', 'r', 'i', 'p', 't']

// 数组转对象 
Object.assign({},arr);//{0: 1, 1: 2, 2: 4, 3: 3, 4: 6, 5: 3, 6: 4}

//对象转数组
Object.values(obj);// ['张三', '18', '男']
Object.keys(obj); //['name', 'age', 'sex']
Object.entries(obj);//[['name', '张三'],['age', '18'],['sex', '男']]

// 对象转字符串
JSON.stringify(obj);//{"name":"张三","age":"18","sex":"男"}
Object.toString(obj);

//字符串转对象
JSON.parse(jsonString);//{error: 1, data: '用户不存在'}
eval("(" + jsonString + ")");//{error: 1, data: '用户不存在'}
(new Function("return " + jsonString))() ;//{error: 1, data: '用户不存在'}

vue

1、vue的双向绑定原理是什么?里面的关键点在哪里?

双向数据绑定的原理:采用“数据劫持”结合“发布者-订阅者”模式的方式,通过“Object.defineProperty()”方法来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

关键点在于data如何更新view,监听数据变化,使用getter去更新。使用setter去执行view的更新方法。

2、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?

vue2vue3描述
beforeCreatebeforeCreate组件实例创建之初,通常用于开发中执行一些初始化任务
createdcreated组件实例创建完成,可以访问各种数据,获取接口数据等
beforeMountbeforeMount组件挂载之前
mountedmounted组件挂载到实例上之后,dom已创建,可用于获取访问数据和dom元素;访问子组件等
beforeUpdatebeforeUpdate组件数据发生变化更新之前,此时“view”层还未更新,可用于获取更新前的各种状态
updatedupdated组件数据发生变化更新之后,完成“view”层的更新,更新后,所有状态已是最新
beforeDestroybeforeUnmounted组件实例销毁之前,可用于一些定时器或订阅的取消
destoryedunmounted组件实例销毁之后,可清理它与其他实例的连接,解绑它的全部指令及事件监听
activatedactivatedkeep-alive 缓存的组件激活时调用。
deactivaeddeactivaedkeep-alive 缓存的组件失活时调用。
errorCapturederrorCaptured捕获一个来自子孙组件的错误时被调用
renderTracked调试钩子,响应式依赖被收集时调用
renderTriggered调试钩子,响应式依赖被触发时调用
serverPerFetch当组件实例在服务器上被渲染之前要完成的异步函数
3、组件通讯方式有哪一些?
(1)props 

父传子。子组件接收到数据之后,不能直接修改父组件的数据。否则会报错,因为当父组件重新渲染时,数据会被覆盖

//Parent.vue 传送:
<template>
    <child :msg="msg"></child>
</template>


//Child.vue 接收:
export default {
  // 写法一 用数组接收
  props:['msg'],
  // 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等
  props:{
      msg:{
          type:String,
          default:'这是默认数据'
      }
  },
  mounted(){
      console.log(this.msg)
  },
}

(2)$emit(v-on)

子传父。子组件通过派发事件的方式给父组件数据,或者触发父组件更新等操作

// Child.vue 派发
export default {
  data(){
      return { msg: "这是发给父组件的信息" }
  },
  methods: {
      handleClick(){
          this.$emit("sendMsg",this.msg)
      }
  },
}
// Parent.vue 响应
<template>
    <child v-on:sendMsg="getChildMsg"></child>
    // 或 简写
    <child @sendMsg="getChildMsg"></child>
</template>

export default {
    methods:{
        getChildMsg(msg){
            console.log(msg) // 这是父组件接收到的消息
        }
    }
}

(3) .sync

可以帮我们实现父组件向子组件传递的数据的双向绑定,所以子组件接收到数据后可以直接修改,并且会同时修改父组件的数据

//Parent.vue:
<template>
    <child :page.sync="page"></child>
</template>
<script>
export default {
    data(){
        return {
            page:1
        }
    }
}

//Child.vue:
export default {
    props:["page"],
    computed(){
        // 当我们在子组件里修改 currentPage 时,父组件的 page 也会随之改变
        currentPage {
            get(){
                return this.page
            },
            set(newVal){
                this.$emit("update:page", newVal)
            }
        }
    }
}
</script>
(4)ref

在子组件上使用,引用的指向就是子组件实例。父组件可以通过 ref 主动获取子组件的属性或者调用子组件的方法

// Child.vue
export default {
    data(){
        return {
            name:"oldCode"
        }
    },
    methods:{
        someMethod(msg){
            console.log(msg)
        }
    }
}

// Parent.vue
<template>
    <child ref="child"></child>
</template>
<script>
export default {
    mounted(){
        const child = this.$refs.child
        console.log(child.name) 
        child.someMethod("调用了子组件的方法")
    }
}
</script>

(5)$children / $parent

$children:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等
$parent:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法等
 

//Parent.vue:
export default{
    mounted(){
        this.$children[0].someMethod() // 调用第一个子组件的方法
        this.$children[0].name // 获取第一个子组件中的属性
    }
}

//Child.vue:
export default{
    mounted(){
        this.$parent.someMethod() // 调用父组件的方法
        this.$parent.name // 获取父组件中的属性
    }
}

(6)Vuex

Vuex 是状态管理器,集中式存储管理所有组件的状态

import axios from 'axios';
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

var store = new Vuex.Store({
  debug: true,
  state: {
    //这里放全局参数
    leftNavState: 'index',
    firstNavMenu:0,  //一级菜单index
  },
  mutations: {
    //这里是set方法
    setFirstNavMenu(state,newVal){
      state.firstNavMenu = newVal;
    },
    
  },
  getters: {
    //get方法 获取state中的状态并返回,但是不能修改
    getFirstNavMenu(state){
      return state.firstNavMenu;
    },
  },
  actions: {
    //注册actions,类似vue里的mothods
    async getConfig({commit}){
      let res = await axios.get('/configData.json');
      this.state.taskManageUrl = res.data.taskManageUrl;
    }
  },
  modules: {
    //这里是我自己理解的是为了给全局变量分组,所以需要写提前声明其他store文件,然后引入这里
  }
});

export default store;


// 使用方法:
//   存:this.$store.commit('getFirstNavMenu', val);
//   取:this.$store.state.firstNavMenu  或者this.$store.getters.getFirstNavMenu

(7)slot

把子组件的数据通过插槽的方式传给父组件使用,然后再插回来

//Child.vue:
<template>
    <div>
        <slot :user="user"></slot>
    </div>
</template>
export default{
    data(){
        return {
            user:{ name:"oldCode" }
        }
    }
}

//Parent.vue:
<template>
    <div>
        <child v-slot="slotProps">
            {{ slotProps.user.name }}
        </child>
    </div>
</template>

4、Vuex有几个属性及作用?

五种属性:state、getters、mutations、actions、modules

1、state
//数据源(相当于数据库)
state: {
 user_limit: '' || localStorage.getItem('user_limit'),
 search_phone: '' || localStorage.getItem('search_phone'),
 user_info:'' || localStorage.getItem('userInfo'),
},
2、getters
// 按需取出数据源中的数据
getters: {
   userLimit: (state) => state.user_limit,
   search_phone: (state) => state.search_phone,
   user_info_getters: (state) => state.user_info,
}
3、mutations

mutations是更新store中数据的唯一途径,且是同步操作,不是异步的。mutations中 用户自定义的函数有一个特点,会接收一个以state为第一参数的回调函数。也就是说,前面两个state和getters都是状态值本身,mutations才是改变状态的执行者

mutations: {
   // 修改user_limit,并将user_limit存入localStorage
   handleUserLimit: (state, user_limit) => {
     state.user_limit = user_limit
     localStorage.setItem('user_limit', user_limit)
   },
   handlesearchPhone: (state, search_phone) => {
      state.search_phone = search_phone
      localStorage.setItem('search_phone', search_phone)
   },
   userInfoFunc: (state, user_info) => {
       state.user_info = user_info
       localStorage.setItem('userInfo', user_info)
   },
 },
4、actions

和mutations的不同的是,actions支持异步和同步的操作,mutations只支持同步的操作。
当写上this.$store.dispatch('setUserInfo',xx)代码后,先执行actions,再执行mutations。即执行actions包裹的mutations中的userInfoFunc函数。

actions:{
  setUserInfo(context, playload) {
  // setTimeout(function () { // 每3秒执行一次commit()来模拟异步操作
    context.commit("userInfoFunc", playload) // 同步执行mutations中的userInfoFunc函数
 // }, 3000)
  }
}
5、modules

modules 模块化Vuex。即每一个模块都有每个模块自己的state、getters、mutations、actions。大型项目需要这种模块化,相当于每个模块都有自己的数据库,不用同时操作同一个文件,也避免代码冲突。

使用1:   调用commit或者dispatch进行数据存储

// 同步赋值操作: 触发mutations中的handleUserLimit方法,从而改变state中的user_limit的值
this.$store.commit('handleUserLimit',res.data.data.body.isHost);
// 同步赋值操作:触发mutations中的handlesearchPhone方法,从而改变state中的search_phone的值
this.$store.commit('handlesearchPhone',res.data.data.manager.searchPhone);  
// 异步赋值操作
this.$store.dispatch("setUserInfo", JSON.stringify({name:'coderZb',sex:'1'}));  

使用2:取出数据

<template>
  <div class="shop_content"></div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  data() {
    return {}
  },
  computed: {
    ...mapGetters(['user_info_getters','userLimit']),
  },
  created() {
    console.log('用户信息---取法A---', this.user_info_getters)// 需要引入{ mapGetters } 并应用到computed:{}中
    console.log('用户信息---取法B---', this.$store.getters.user_info_getters)
    console.log('用户信息---取法C---', this.$store.state.user_info)
    
    //  不可以用下面的取法。取的是getters里面的属性 不是state里面的属性
    console.log('错误取法', this.$store.getters.user_info)
   
  },
}
</script>

5、Vue的监听属性和计算属性有什么区别?

一:计算属性(computed

        计算属性是为了模板中的表达式简洁,易维护,符合用于简单运算的设计初衷。运算过于复杂,冗长,且不好维护,因此我们对于复杂的运算应该 使用计算属性的方式去书写。

       1.变量不在 data中定义,而是定义在computed中,写法跟写方法一样,有返回值。函数名直接在页面模板中渲染 。

  2.根据传入的变量的变化,进行结果的更新。

<body>
    <div id="app">
        {{ changewords }} 
    </div>
</body>
<script>
    var vm = new Vue({
       el: "#app",
       data:{},
      // 计算属性
       computed:{
         changewords(){
            return this.myname.substring(0,1).toUpperCase() + this.myname.substring(1)
         }
       }
     })
</script>

二、监听属性(watch)

watch是发生改变的时候才会触发

<body>
    <div id="app">
        <p>单价:<input type="text" v-model="price"></p>
        <p>数量:<input type="text" v-model="number"></p>
        <p>计算金额:{{sum}}</p>
    </div>
</body>
<script>
var vm  = new Vue({
    el:"#app",
    data:{
        price:100,
        number:1,
        sum:0
    },
    //监听某一个值或者状态发生变化 变化就会触发watch 
    watch:{
        // 监听的参数的名字要一致
        price(){
            console.log(this.price)
            if(this.price*this.number < 1000 && this.price*this.number > 0){
                this.sum = this.price*this.number + 100
            }else{
                this.sum = this.price*this.number
            }
        },
        number(){
            console.log(this.price)
            if(this.price*this.number < 1000 && this.price*this.number > 0){
                this.sum = this.price*this.number + 100
            }else{
                this.sum = this.price*this.number
            }
        }
    }
})
</script>

区别:

        1. computed 是计算属性,依赖其他属性计算值, 并且 computed 的值有缓存, 只有当计算值变化才会返回内容。

        2. watch 监听到值变化就会执行回调,在回调中可以进行一些逻辑操作。

        所以一般来说需要依赖别的属性来动态获得值的时候可以使用 computed ,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch

        另外 computer 和 watch 还都支持对象的写法, 这种方式知道的人并不多

vm.$watch( 'obj', { 
    // 深度遍历
    deep: true, 
    // 立即触发
    immediate: true,
    // 执行的函数
    handler: function(val, oldVal) {} 
})


var vm = new Vue({ 
    data: { a: 1 }, 
    computed: {
    aPlus: {
        // this.aPlus 时触发
        get: function () {
           return this.a + 1
        },
        // this.aPlus = 1 时触发 
        set: function (v) {
            this.a = v - 1 }
        } 
    }
})
6、Vue的导航守卫有哪一些?

Vue Router提供了三种导航守卫:

    (1)全局前置守卫(Global Before Guards):通过router.beforeEach方法添加,会在每个路由切换前都执行。可以用于全局的身份验证、权限检查等。

    (2)路由独享的守卫(Per-Route Guards):通过在路由配置中使用beforeEnter属性添加,只在特定的路由切换前执行。可以用于特定路由的身份验证、权限检查等。

    (3)组件内的守卫(In-Component Guards):通过在Vue组件中定义beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave等生命周期钩子函数来添加。用于控制组件的路由导航逻辑。

to:目标路由对象;

from:即将要离开的路由对象;

next:它是最重要的一个参数,调用该方法后,才能进入下一个钩子函数。

        next()//直接进to 所指路由
        next(false) //中断当前路由
        next('route') //跳转指定路由
        next('error') //跳转错误路由

7、Vue2.0和vue3.0有什么区别?---待完善
一:项目架构

Vue 2.0 中通常会选择使用 webpack 或者 vue-cli 来进行项目搭建和打包

Vue 3.0 中则更加推崇使用 Vite 这款新型的前端构建工具

二:数据响应式系统

Vue 2.0中的数据响应式系统,主要是通过Object.defineProperty()方法来实现的。

Vue 3.0使用了ES6中的Proxy代理对象来替代Object.defineProperty()方法。

三:虚拟DOM

静态节点提升:将静态节点与动态节点分离,有效提高了渲染性能
快速标记和补丁:Vue 3.0中采用了更加高效的标记和补丁方式,使得页面渲染更加快速和稳定
更小的bundle大小:Vue 3.0使用了tree-shaking和基于ES2015的模块系统,使得框架的bundle大小更加的小。

四:生命周期

见 2、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?

五:组件实例的创建方式
// MyComponent.vue
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'MyComponent',
  data() {
    return {
      count: 0
    };
  },
  methods: {
    increment() {
      this.count++;
    }
  },
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="increment">Increment</button>
    </div>
  `
});


// main.js
import { createApp } from 'vue';
import MyComponent from './MyComponent.vue';
const app = createApp({});
app.component('my-component', MyComponent);
app.mount('#app')
六:路由

最大区别在于 Vue Router 的使用方式和 API 的变化。

Vue 2.0 中,我们需要使用 Vue.use(VueRouter) 来引入 Vue Router,然后创建一个 Router 实例,并在根组件中使用它:

//router.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})



// main.js 
import Vue from 'vue'
import App from './App.vue'
import router from './router' //导入 Router 并使用
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

 Vue 3.0 中,使用 createRouter 工厂函数来创建路由实例,然后将其传递给根组件使用

//router.js
import { createRouter, createWebHistory } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
const routes = [
  { path: '/', component: HelloWorld }
]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router


//main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router); //将路由实例挂载到根组件上
app.mount('#app')

Vue Router 在 Vue 3.0 中提供了更加灵活的 API 和更好的类型推断支持,但是它的使用方式与 Vue 2.0 有所不同。因此,如果你想升级到 Vue 3.0,并且在你的项目中使用了 Vue Router,就必须阅读相关文档,并进行一些相应的修改才能够使用

七:请求

 Vue 2.0 :通常会使用第三方库 axios 或者 vue-resource 来进行 HTTP 请求的封装。这些库通常需要在全局导入后才能使用,然后通过在组件内部使用相关方法来发起请求。虽然这种方式比较灵活,但是需要编写较多的重复代码,并且类型推断和类型校验的支持也较差。
Vue 3.0 :官方推荐的 HTTP 请求库是 axios 的替代品 - vite-plugin-mock,该库内置了一套基于 axios 的请求拦截和响应拦截机制,并且已经在 Vite 中默认启用了。这种方式可以大大减少编写重复代码的工作量,同时也支持更好的类型推断和类型校验。

八:模板指令

1:v-bind 

//vue2
<div v-bind:class="{'active': isActive}"></div>

//vue3. 简洁的语法
<div :class="{'active': isActive}"></div>

2:v-if 和 v-for 指令

Vue 2.0:不能同时使用,因为 v-for 比 v-if 先执行,导致渲染出不必要的组件。

Vue 3.0:可以同时使用,但是需要将 v-if 指令放在 v-for 指令的父元素上

<div v-if="show">
    <div v-for="item in items" :key="item.id">{{ item }}</div>
</div>

3:v-model

 Vue 2.0 : 语法糖

 Vue 3.0 :实现了双向绑定的核心逻辑,可以更加灵活地绑定表单元素和数据

<input v-model="count" type="number">
<!-- 等价于 -->
<input :value="count" @input="count = $event.target.value" type="number">
8、Vue常用的指令有哪些?

(1)v-once

只会执行一次渲染,当数据发生改变时,不会再变化

(2)v-show

接受一个表达式或一个布尔值。相当于给元素添加一个display属性

(3)v-if、v-else、v-else-if

 v-if和v-show有同样的效果,不同在于v-if是重新渲染,而v-show使用display属性来控  制显示隐藏。频繁切换的话使用v-show减少渲染带来的开销。

(4)v-for

 可用来遍历数组、对象、字符串。

(5)v-text和v-html

v-text是渲染字符串,会覆盖原先的字符串

v-html是渲染为html。{{}}双大括号和v-text都是输出为文本。

(6)v-bind

是用可以将标签内的属性值解析成js代码,在标签的属性中使用v-bind,双引号里的内容会被当作js解析(只能解析变量或三元表达式)

(7)v-on

用于事件绑定

语法: v-on:<事件类型>="<函数名>"
简写:@<事件类型>="<函数名>"

(8)v-model

数据双向绑定指令,限制在 <input>、<select>、<textarea>、components中使用
语法: v-model="<变量名>"

9、v-If和v-show有什么区别?

1:实现方式不同

v-if是一种条件渲染指令。当表达式的值为真时,元素会被插入到DOM中,否则会从DOM中删除。

v-show是一种简单的显示/隐藏指令。当表达式的值为真时,元素会被显示,否则会被隐藏。

2:性能不同

v-if频繁切换的元素具有较高的开销,需要重新渲染元素及其所有子元素。

v-show对于频繁切换的元素具有更好的性能,因为元素的渲染不需要重复进行。

3:适用场景不同

v-if适用于只有在满足特定条件时才需要渲染的元素,可以节省不必要的DOM元素和子元素的渲染开销。

v-show适用于需要在不同的状态之间切换的元素,可以避免在切换时重新渲染元素及其所有子元素。

4:语法不同

<p v-if="show">显示的内容</p>

<p v-show="show">显示的内容</p>

10、v-for为什么要加一个key?---待完善

1:vue中列表循环需加:key=“唯一标识” 唯一标识尽量是item里面id等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM。

2:key主要用来做dom diff算法用的,diff算法是同级比较,比较当前标签上的key还有它当前的标签名,如果key和标签名都一样时只是做了一个移动的操作,不会重新创建元素和删除元素。

3:没有key的时候默认使用的是“就地复用”策略。如果数据项的顺序被改变,Vue不是移动Dom元素来匹配数据项的改变,而是简单复用原来位置的每个元素。如果删除第一个元素,在进行比较时发现标签一样值不一样时,就会复用之前的位置,将新值直接放到该位置,以此类推,最后多出一个就会把最后一个删除掉。

4:尽量不要使用索引值index作key值,一定要用唯一标识的值,如id等。因为若用数组索引index为key,当向数组中指定位置插入一个新元素后,因为这时候会重新更新index索引,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加key一样,因此index虽然能够解决key不冲突的问题,但是并不能解决复用的情况。如果是静态数据,用索引号index做key值是没有问题的。

5:标签名一样,key一样这时候就会就地复用,如果标签名不一样,key一样不会复用。

11、父子组件生命周期执行顺序是怎么样的?

1、加载渲染阶段
在加载渲染阶段,一定得等子组件挂载完毕后,父组件才能挂载完毕,所以父组件的 mounted 在最后。
beforeCreate(父组件) → created(父组件) → beforeMount(父组件) → beforeCreate(子组件) → created(子组件) → beforeMount(子组件) → Mounted(子组件) → Mounted(父组件)

2、更新阶段
当父子组件有数据传递时,才有这个更新阶段执行顺序的比较。
beforeUpdate(父组件) → beforeUpdate(子组件) → updated(子组件) → updated(父组件)

3、销毁阶段
beforeDestroy(父组件) → beforeDestroy(子组件) → destroyed(子组件) → destroyed(父组件)

⚠️注意:如果子组件异步组件的话,它们的执行顺序会发生改变,会先执行完父组件的生命周期然后再执行子组件的生命周期。

12、keep-alive是什么?有哪几个生命周期阶段?---待完善

keep-alive是 Vue 提供的一个内置组件,用来对组件进行缓存,在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。

当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。

13、路由原理

端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面, 并且无须刷新页面

实现方式一:Hash模式

        www.test.com/#/ 就是 Hash URL , 当 #后面的哈希值发生变化时, 通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面, 并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com。兼容性好

实现方式二:History模式

        History 模式是 HTML5 新推出的功能, 主要使用 history.pushState 和 history.replaceState 改变 URL。

通过 History 模式改变 URL 同样不会引起页面的刷新, 只会更新浏览器的历史记录。

// 新增历史记录 
history.pushState(stateObject, title, URL)

// 替换当前历史记录 
history.replaceState(stateObject, title, URL)

当用户做出浏览器动作时, 比如点击后退按钮时会触发 popState 事件
window.addEventListener( 'popstate', e => {
    // e.state 就是 pushState(stateObject) 中的 stateObject 
    console.log(e.state)
})

两种模式对比

        1: Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL
        2: History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改

哈希值,也就是字符串
        3: Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新

页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候

14、vue.set

使用vue开发时可能会遇到这种情况:vue实例已经创建好了,再次给数据赋值时,并不能在视图中改变。
set主要用于操作没有定义在data里的数据, 因为没有双向绑定,所以视图不更新。

<div id="app">
	<p v-for="item in items">{{item}}</p>
	<button @click="btn()">添加</button>
</div>
//此时 页面上依旧显示['a', 'b', 'c']

new Vue({
	el:'#app',
	data:{
		items:['a', 'b', 'c']
	},
	methods:{
		btn(){
			this.items[1] = 'd'
			console.log(this.items); //['a', 'd', 'c']
		}
	}
})

想要更改这种情况,需要借助vue.set。此时可以发现item和页面内容都会进行更改

var vm =new Vue({
	el:'#app',
	data:{
		items:['a', 'b', 'c']
	},
	methods:{
		btn2(){
            //第一种写法
			Vue.set(this.items, 1, 'e');
            //第二种写法
            this.$set(this.items, 1, 'e')
			console.log(this.items)
		}
	}
})

优化&适配

1、移动端如何适配不同屏幕尺寸?
适配1——viewport

<meta name="viewport" content="initial-scale=1">

适配2——媒体查询
@media not|only mediatype and (mediafeature and|or|not mediafeature) {
  CSS-Code;
}
适配3——相对单位之 rem 

        rem也是相对单位,相对于根元素(html)的font-size值来计算,假如根元素的字号为20px,则1rem为20px

        插件:postcss-pxtorem

适配4——相对单位之 vw

        假如设计稿宽度750px,那么30px则换算为 30 / 750 * 100 = 4vw

    注意:vw和vh混用可能会导致元素变形

        插件:postcss-px-to-viewport 

适配5——微信小程序的rpx

        微信小程序规定所有设备上逻辑宽度都是750rpx

2、浏览器渲染原理
渲染过程

1. 解析HTML:浏览器首先会将HTML代码解析成DOM树,DOM树是由节点和对象组成的树形结构,表示HTML文档的结构和内容。

2. 解析CSS:浏览器会将CSS代码解析成CSSOM树,CSSOM树是由样式规则和对象组成的树形结构,表示HTML文档的样式信息。

3. 构建渲染树:浏览器会将DOM树和CSSOM树合并成渲染树,渲染树只包含需要显示的节点和样式信息,不包含隐藏的节点和不可见的样式信息。

4. 布局:浏览器会根据渲染树的信息计算每个节点的位置和大小,生成布局。

5. 绘制:浏览器会将布局转换成像素,然后绘制到屏幕上。

6. 重绘和回流:当页面发生变化时,浏览器会进行重绘和回流。重绘是指重新绘制已经显示的元素,而回流是指重新计算元素的位置和大小。回流比重绘的代价更高,因为回流会涉及到整个渲染树的重新计算和布局。

什么情况阻塞渲染

首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染 。如果想渲染的越快,越应该降低一开始需要渲染的文件大小, 并且扁平层级,优化选择器。

然后当浏览器在解析到 script 标签时,会暂停构建 DOM , 完成后才会继续构建DOM 。如果想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。

当 script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行 ,对于这种情况就可以把 script 标签放在任意位置。

对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。

重绘和回流

        重绘是当节点需要更改外观而不会影响布局的, 比如改变 color 就叫称为重绘

        回流是布局或者几何属性需要改变就称为回流。

        回流必定会发生重绘, 重绘不一定会引发回流 。回流所需的成本比重绘高的多, 改变父节 点里的子节点很可能会导致父节点的一系列回流。

        以下几个动作可能会导致性能问题

              改变 window 大小

              改变字体

              添加或删除样式

              文字改变

              定位或者浮动

              盒模型

减少重绘和回流

1. 使用 transform 替代 top  {position: absolute; top: 10px; }

2. 使用 visibility 替换 display: none(前者引起重绘,后者引起回流)

3. 不要把节点的属性值放在一个循环里当成循环里的变量

4. 不要使用 table 布局, 可能很小的一个小改动会造成整个 table 的重新布局

5. 动画实现的速度的选择, 动画速度越快, 回流次数越多

6. CSS 选择符从右往左匹配查找, 避免节点层级过多

7.将频繁重绘或者回流的节点设置为图层, 图层能够阻止该节点的渲染行为影响别的节点。比如will-change、video、iframe 标签

3、谈谈你在之前的项目中是如何进行Web性能优化的?请举例说明你采取了哪些措施。

优秀的特征:

  • 访问交互速度迅速
  • 动画效果顺滑流畅
  • 有用户操作的反馈
  • 简单的操作步骤
  • 整站体验一致性
  • 主体内容在最显眼的位置
  • 无障碍访问,不同的人群均可使用
1. 性能采集

白屏时间、首屏时间FSP、用户可交互时间TTI、页面onload时间等作为核心优化指标

还可以通过获取DNS查询耗时、TCP链接耗时、request请求耗时、解析dom树耗时、白屏时间、domready时间、onload时间等做性能分析

2.性能优化
(1)加载性能优化
  1. 减小资源体积:压缩文本内容 或者 优化JavaScript第三方库引入
  2. 对资源进行缓存:浏览器端、网络代理、服务端缓存,往往能大幅加快响应速度

    • HTTP 缓存:现代浏览器都实现了 HTTP 缓存机制。浏览器在初次获取资源后,会根据 HTTP 响应头部的Cache-Control和ETag字段,来决定该资源的强缓存策略或者协商缓存策略。
    • Local Storage:Local Storage主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小。
    • Cache Storage:Cache Storage它用来存储 Response 对象的,也就是说用来对 HTTP响应做缓存的,通常在PWA技术中使用。
    • IndexedDB:IndexedDB是一种在浏览器中持久存储数据的方法,允许我们不考虑网络可用性,创建具有丰富查询能力的可离线web应用程序。
    •  CDN:内容缓存在CDN网络节点,位于用户接入点,是面向最终用户的内容提供设备,可缓存静态Web内容和流媒体内容,实现内容的边缘传播和存储,以便用户的就近访问。
  3. 调整资源优先级:通过预加载、懒加载等多种方式,调整资源加载的行为,优化网页加载性能

  • 预加载:通过<link rel="preload">来提前声明当前页面所需的资源,以便浏览器能预加载这些资源。通过media属性进行媒体查询,根据响应式的情况选择性地预加载资源。 

  • 懒加载:对图像资源采用“懒加载”策略,即仅加载当前在视口内的图像,对于视口外未加载的图像,在其即将滚动进入视口时才开始加载。
  •  预取:浏览器会在空闲时,使用最低优先级下载预取的资源。预取通过<link rel="prefetch">声明,通常用于点击“下一页”的页面动作之前提前加载用户接下来可能需要的html资源。                                                                                             
(2)渲染性能优化

        浏览器在渲染页面前,首先会将 HTML 文本内容解析为 DOM,将 CSS 解析为 CSSOM。DOM 和 CSSOM 都是树状数据结构,两者相互独立,但又有相似之处。接着,浏览器会将 DOM 和 CSSOM 树合并成渲染树。从 DOM 树的根节点开始遍历,并在 CSSOM 树中查找节点对应的样式规则,合并成渲染树中的节点。在遍历的过程中,不可见的节点将会被忽略。渲染树随后会被用于布局,就是计算渲染树节点在浏览器视口中确切的位置和大小。浏览器进行一次布局的性能开销较大,我们需要小心地避免频繁触发页面重新布局。得到渲染树节点的几何布局信息后,浏览器就可以将节点绘制到屏幕上了,包括绘制文本、颜色、边框和阴影等。

        绘制的过程,首先会根据布局和视觉相关的样式信息生成一系列绘制操作,随后执行栅格化(栅格化是将向量图形格式表示的图像转换成位图以用于显示器或者打印机输出的过程),将待绘制项转换为位图存储在 GPU 中,最终通过图形库将像素绘制在屏幕上。

        页面不是一次性被绘制出来的。实际上,页面被分成了多个图层进行绘制,这些图层会在另一个单独的线程里绘制到屏幕上,这个过程被称作合成。合成线程可以对图层进行剪切、变换等处理,因此可以用于响应用户基本的滚动、缩放等操作,又不会受到主线程阻塞的影响。

  1. 关键渲染路径:由于渲染都是在主进程中执行的,所以合理的利用主进程渲染非常重要。首屏渲染所必须的关键资源,共同组成了关键渲染路径,减少非关键渲染路径的资源消耗可以有效提升渲染速度
           延迟非关键 CSS 加载:Web 应用中往往会有一些首屏渲染时用不到的 CSS,如弹框的样式等。通过<link rel="stylesheet">引用的 CSS 都会在加载时阻塞页面渲染。为了使这些非关键 CSS 不阻塞页面渲染,可以通过拆分资源的方式并延迟非关键资源加载。
           async 和 defer由于JavaScript 可能会修改样式,页面中的<script>会阻塞后续 DOM 的构建,等待JS资源下载完成才能执行继续渲染。为了减少 JS 对渲染的阻塞并且能与页面中所有的 DOM 进行交互,最常见的引入方式是将script标签置于<body>的最底部。script提供了asyncdefer两个属性,浏览器同样会发起异步请求(多个异步script的执行顺序是不确定的)
  2. 非阻塞 JavaScript由于 JavaScript 一般是单线程执行的,长时间执行的任务会阻塞浏览器的主线程,使页面失去响应,出现卡顿和假死的现象。
    1. 页面滚动
    2. requestAnimationFrame 任务在浏览器渲染下一帧之前执行
    3. requestIdleCallback 将任务安排在浏览器空闲时执行
    4. Web Workers
    5. 当我们监听 touchstart、touchmove 等事件时,由于合成线程并不知道我们是否会通过 event.preventDefault() 来阻止默认的滚动行为,从而在每次事件触发时,都会等待事件处理函数执行完毕后再进行页面滚动。这通常会导致较明显的延迟,影响页面滚动的流畅性。通过在addEventListener()时声明{passive: true},来表明事件处理函数不会阻止页面滚动,使得用户的操作更快得到响应。

    6. 我们可以将一些耗性能的逻辑放在 worker 线程中进行处理,这样主线程就能继续响应用户操作和渲染页面了。

  3. 降低渲染树计算复杂性:结构越复杂的页面往往性能越差,动画多的页面出现卡顿的几率也越大。

    1. 减少查找与元素匹配成本:对于每个 DOM 元素,需要查找与元素匹配的样式规则。CSS Modules 是一种较为主流的 CSS-in-JS 解决方案,利用 webpack 等构建工具,可以对类选择器生成自定义格式的唯一类名,能减少浏览器匹配 CSS 选择器的开销
    2. 减少布局次数:由于JavaScript 可能会修改样式,页面中的<script>会阻塞后续 DOM 的构建,等待JS资源下载完成才能执行继续渲染。为了减少 JS 对渲染的阻塞并且能与页面中所有的 DOM 进行交互,最常见的引入方式是将script标签置于<body>的最底部。script提供了asyncdefer两个属性,浏览器同样会发起异步请求(多个异步script的执行顺序是不确定的)。   
    3. 优化绘制与合成:修改绝大多数样式属性都会导致页面重绘。仅有的例外是transformopacity他们可以实现动画效果,我们通过will-change为它们创建独立的图层,避免影响其他图层的绘制。    
3.性能优化碎片化内容
(1)图片优化

        计算图片大小:  对于一张 100 * 100 像素的图片来说,图像上有 10000 个像素点, 如果 每个像素的值是 RGBA 存储的话,也就是说每个像素有 4 个通道,每个通道 1 个字节 ( 8 位 = 1 个字节), 所以该图片大小大概为39KB ( 10000*1*4/1024 )

        优化方向:

                1. 减少像素点
                2. 减少每个像素点能够显示的颜色

(2)图片加载优化

        1. 不用图片,使用css去代替

        2. 小图使用 base64 格式

        3. 将多个图标文件整合到一张图片中 ( 雪碧图 / 精灵图)

        4. 选择正确的图片格式:

                4.1.能够显示 WebP 格式的浏览器尽量使用 WebP 格式 。它具有更好的图像数据压缩算法, 能带来更小的图片体积, 而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好

                4.2. 小图使用 PNG , 其实对于大部分图标这类图片, 完全可以使用 SVG 代替

                4.3. 照片使用 JPEG

(3)DNS预解析

        DNS 解析也是需要时间的, 可以通过预解析的方式来预先获得域名所对应的 IP

<link rel="dns-prefetch" href="//blog.poetries.top">
(4)防抖

        考虑一个场景,有一个按钮点击会触发网络请求,但是我们并不希望每次点击都发起网络请求, 而是当用户点击按钮一段时间后没有再次点击的情况才去发起网络请求,对于这种情况我们就可以使用防抖。

(5)节流

        考虑一个场景, 滚动事件中会发起网络请求,但是我们并不希望用户在滚动过程中一直发起请求, 而是隔一段时间发起一次,对于这种情况我们就可以使用节流。

(6)预加载

        预加载其实是声明式的 fetch , 强制浏览器请求资源, 并且不会阻塞 onload 事件,缺点就是兼容性不好

<link rel="preload" href="http://blog.poetries.top">
(7)预渲染

        可以通过预渲染将下载的文件预先在后台渲染。预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开, 否则就是白白浪费资源去渲染。

<link rel="prerender" href="http://blog.poetries.top">
(8)懒执行

        懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。

(9)懒加载

        原理就是只加载自定义区域 ( 通常是可视区域,但也可以是即将进入可视区域) 内需要加载的东⻄。不仅可以用于图片,也可以使用在别的资源上 。比如进入可视区域才开始播放视频 等等。

(10)CDN

        CDN 的原理是尽可能的在各个地方分布机房缓存数据, 这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。

4、Review代码会考虑哪些因素,说一说对好代码的理解
代码结构
  1. 代码的组织结构:代码应该按照一定的组织结构进行编写,例如按照功能模块进行组织、按照层次结构进行组织等等。在审查代码结构时,应该关注代码的组织结构是否清晰、是否符合设计原则等方面。
  2. 模块化和可重用性:代码应该具有一定的模块化和可重用性,以便于代码的复用和维护。在审查代码结构时,应该关注代码是否具有可重用的模块、是否具有良好的接口设计等方面。
  3. 代码的层次结构:代码应该按照一定的层次结构进行编写,例如分为界面层、业务逻辑层、数据访问层等等。在审查代码结构时,应该关注代码的层次结构是否清晰、是否具有良好的模块划分等方面
代码逻辑
  1. 条件分支的检查 判断条件是否覆盖了所有可能的情况,是否有重复的判断条件是否有不必要的嵌套。
  2. 循环结构的检查 检查循环是否能够正常终止,是否存在死循环,是否有更简洁的循环方式。
  3. 异常处理的检查 是否对所有的错误进行正确的处理,是否提供合适的错误提示,是否能够记录错误日志等。
代码的可读性和可维护性
  1. 命名应该清晰,简洁,准确。变量、函数、类等命名应该具有清晰、简洁、准确的特点。而不是简单的字母或数字,且应该使用一致的命名方式,避免混淆。
  2. 注释应该清晰、准确地描述代码的含义和作用。不存在无用的注释并保持最新状态
  3. 代码段的长度合适。:通用的建议是,每个函数或方法的长度控制在 100 行以内。
  4. 函数和方法的参数和返回值规范。 函数和方法的参数应该尽量少,入参和出参较多的情况下,可以考虑使用DTO来封装。函数和方法的返回值应该尽可能明确,避免使用不必要的返回值或无意义的返回值。
  5. 避免使用魔法数字或魔法字符串。
代码的可靠性
  1. 入参合法性检查。 是否对输入参数进行了合法性检查,避免出现意外的输入错误。
  2. 单元测试检查。 是否进行了足够的单元测试,并且能够覆盖各种边界情况。
5、什么是跨域?如何解决跨域问题

浏览器处于安全考虑,有同源策略。如果协议,域名,端口有一个不同就是跨域,ajax请求就会失败

方法一:JSONP

        利用<script>标签没有跨域限制的漏洞,通过<script>标签指向一个需要访问的地址,并提供一个回调函数来接收数据。

        只限于get请求

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script> <script>
    function jsonp(data) {
        console.log(data)
    } 
</script>

方法二:CORS

        需要浏览器端和后端同时支持,浏览器会自动进行CROS通信,只要后端实现CROS,就可以实现跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。该属性表示哪些域名可以访问资源, 如果设置通配符则表示所有网站都可以访问资源 。

常见Content-Type
HTML文档标记:text/html;
普通ASCII文档标记:text/html;
JPEG图片标记:image/jpeg;
GIF图片标记:image/gif;
js文档标记:application/javascript;
xml文件标记:application/xml;

方法三:document.domain

        该方式只能用于主域名相同的情况下, 比如 a.test.com 和 b.test.com 适用于该方式。
        只需要给页面添加 document.domain = 'test.com' 表示主域名都相同就可以实现跨域

方法四:postMessage

        这种方式通常用于获取嵌入页面中的第三方页面数据 。一个页面发送消息, 另 一个页面判断来源并接收消息

// 发送消息端
window.parent.postMessage( 'message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel() mc.addEventListener( 'message', event => {
var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') {
    console.log( '验证通过') }
})
6、浏览器缓存机制
缓存位置
    1. Service Worker

        当 Service Worker 没有命中缓存的时候, 我们需要去调用 fetch 函数获取数据 。也 就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查 找数据 。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据, 浏览器都 会显示我们是从 Service Worker 中获取的内容。

    2. Memory Cache

        Memory Cache也就是内存中的缓存,读取内存中的数据比磁盘快,高效但缓存持续性很短,会随着进程的释放而释放。页面关闭,缓存释放。

    3. Disk Cache

        Disk Cache 也就是硬盘中的缓存。读取速度慢,但是比Memory Cache存储容量大,存储时间长。相同地址的资源一旦被硬盘缓存下来,就不会再次去 请求数据

    4. Push Cache

        Push Cache 是 HTTP/2 中的内容, 当以上三种缓存都没有命中时, 它才会被使用 。缓存时间也很短暂, 只在会话 ( Session ) 中存在,一 旦会话结束就被释放。缓存的资源只能使用一次。

    5. 网络请求

        所有缓存没有命中的情况下,只能发起请求来获取资源

缓存策略

    缓存策略都是通过设 置 HTTP Header 来实现的

    1.强缓存

        Expires 和 Cache- Control 。强缓存表示在缓存期间不需要请求, state code 为 200

Expires 是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期, 需要再次请求 。并且 Expires 受限于本地时间, 如 果修改了本地时间, 可能会造成缓存失效。
例如:Expires: Wed, 22 Oct 2018 08:41:00 GMT


Cache-Control 出现于 HTTP/1.1 ,优先级高于 Expires 
Cache-Control 可以在请求头或者响应头中设置, 并且可以组合使用多种指令
no-cache: 不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载
no-store: 直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源
public: 可以被所有的用户缓存, 包括终端用户和 CDN 等中间代理服务器 。 
private: 只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。 
max-age: 从当前请求开始, 允许获取的响应被重用的最长时间 (秒) 。
例如: Cache-control: public, max-age=1000  //表示资源可以被所有用户以及代理服务器缓存, 最长时间为1000秒。
    2.协商缓存
1:Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件最后修改日期, If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来, 否则返回 304 状态码。
缺点:如果本地打开缓存文件, 即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源


2:ETag 和 If-None-Match
ETag 类似于文件指纹, If-None-Match会将当前 ETag 发送给服务器,询问该资源ETag 是否变动,有变动的话就将新的资源发送回来 。并且 ETag 优先级比 Last- Modified 高。


3:如果什么缓存策略都没设置,那么浏览器会怎么处理
浏览器会采用一个启发式的算法, 通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间

7、安全防范
一:XSS攻击

        攻击者想尽一切办法将可执行代码注入到网页中。总体分为两类:持久型和非持久型。

    持久型就是攻击的代码被服务端写进了数据库中,会导致大量正常访问的用户受到攻击,危害性大。

    非持久型一般通过修改URL参数的方式加入攻击代码,诱导用户访问链接进行攻击,危害性小一些。

举例攻击代码:
<!-- http://www.domain.com?name=<script>alert(1)</script> -->
<div>{{name}}</div>
//Chrome 这类浏览器会自动防御,其他的浏览器不行

防御方式一:转义字符
对于用户的输入应该是永远不信任的。
1: 转义输入输出的内容,对于引号、尖括号、斜杠等进行转义
function escape(str) {
    str = str.replace(/&/g, '&amp;') 
    str = str.replace(/</g, '&lt;') 
    str = str.replace(/>/g, '&gt;') 
    str = str.replace(/"/g, '&quto;') 
    str = str.replace(/'/g, '&#39;') 
    str = str.replace(/`/g, '&#96;') 
    str = str.replace(/\//g, '&#x2F;')
    return str
}
通过转义可以将攻击代码 <script>alert(1)</script> 变成
// -> &lt;script&gt;alert(1)&lt;&#x2F;script&gt;
escape( '<script>alert(1)</script>')

但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。

2: js-xss 来实现, 在输出中保留了 h1 且过滤了 script
const xss = require( 'xss')
let html = xss( '<h1 id="title">XSS Demo</h1><script>alert("xss");</script>' 
// -> <h1>XSS Demo</h1>&lt;script&gt;alert("xss");&lt;/script&gt; 
console.log(html)



防御方式二:CSP
CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。
只要开发者配置了正确的规则,那么即使网站存在漏洞,攻击者也不能执行它的攻击代码。
CSP 的兼容性也不错
可以通过两种方式来开启CSP:
1: 设置 HTTP Header 中的 Content-Security-Policy
    Content-Security-Policy: default-src ‘self’    //只允许加载本站资源
    Content-Security-Policy: img-src https://*    //只允许加载https协议的图片
    Content-Security-Policy: child-src 'none'    //允许加载任何来源框架
2: 设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">
二:CSRF攻击

        CSRF 中文名为跨站请求伪造 。原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求 。如果用户是在登录状态下的话, 后端就以为是用户在操作,从而进行相应的逻辑。

举例攻击代码
<img src="http://www.domain.com/xxx?comment= 'attack '"/>
假设网站有一个GET 请求提交用户评论的接口,攻击者可以在钓鱼网站中加入一张图片,图片地址就是评论接口

防御:
1: Get请求不对数据进行修改
2: 不让第三方网站访问到用户Cookie 
    SameSite: Cookie设置 SameSite 属性。该属性表示Cookie不随着跨域请求发送,可以很大程度减少CSRF的攻击,但是该属性目前并不是所有浏览器都兼容。
3: 阻止第三方网站请求接口
4: 请求时附带验证信息,比如验证码或者Token
    验证 Referer: 对于需要防范CSRF的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。
    Token: 服务器下发一个随机Token,每次发起请求时将Token携带上,服务器验证Token是否有效
三:点击劫持

        点击劫持是一种视觉欺骗的攻击手段 。攻击者将需要攻击的网站通过iframe 嵌套的方式嵌入自己的网页中, 并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。

防御方式一: X-FRAME-OPTIONS
    X-FRAME-OPTIONS是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个HTTP响应头就是为了防御用 iframe 嵌套的点击劫持攻击。
    远古浏览器不支持此种方式

该响应头有三个值可选:
    DENY: 表示页面不允许通过 iframe 的方式展示
    SAMEORIGIN: 表示页面可以在相同域名下通过 iframe 的方式展示
    ALLOW-FROM: 表示页面可以在指定来源的 iframe 中展示


防御方式二:JS防御
    当通过 iframe 的方式加载页面时, 攻击者的网页直接不显示所有内容了
<head>
    <style id="click-jack">
        html {
            display: none !important;
        } 
    </style>
</head>
<body>
    <script>
       if (self != top) {
            var style = document.getElementById( 'click-jack');
            document.body.removeChild(style);
            document.write("<p>这个窗口不是最顶层窗口!我在一个框架?</p>")
       } else {
            top.location = self.location;
            document.write("<p>这个窗口是最顶层窗口!</p>")
       }
     </script>
</body>
知识扩展:
1:window.parent: 返回父窗口
    在使用A页面上使用了一个弹窗,弹窗引入了B页面,在B页面要执行A页面的close方法,就可以使用
    window.parent.close();
    如果窗口本身是顶层窗口,parent属性返回的是对自身的引用。

2:window.top: 返回当前窗口的最顶层浏览器窗口
    如果窗口本身就是顶层窗口,top属性返回的是对自身的引用。

3:window.self: 是对当前窗口自身的引用。 window.self == self == window

8、监控
方式一:页面埋点

一般起码会监控以下几个数据:

  •   PV / UV
  •   停留时长
  •   流量来源
  •   用户交互

对于这几类统计,一般的实现思路大致可以分为两种。手写埋点和无埋点的方式。

        手写埋点:自主选择需要监控的数据然后在相应的地方写入代码。这种方式的灵活性很大,但是缺点就是工作量较大,每个需要监控的地方都得插入代码。

        无埋点:不需要开发者手写埋点,统计所有的事件并且定时上报,没有上一种方式繁琐,但是因为统计的是所有事件,所以还需要后期过滤出需要的数据。

方式二:性能监控

        对于性能监控来说, 其实我们只需要调用这一行代码就够了。这行代码返回了一个数组, 内部包含了相当多的信息,从数据开始在网络中传输到页面加载完成都提供了相应的数据

performance.getEntriesByType('navigation') 
方式三:异常监控
1:代码报错

        (1) window.onerror 拦截报错。能拦截到大部分的详细报错信息

        (2)crossorigin。跨域的代码运行错误会显示 Script error,需要给 script 标签添加 crossorigin 属性

        (3)arguments.caller.caller。某些浏览器可能不会显示调用栈信息, 这种情况可以通过 arguments.callee.caller 来做栈递归

        (4)catch / try catch。对于异步代码,可以使用 catch 的方式捕获错误 。比如 Promise 可以直接使用catch 函数, async await 可以使用 try catch。

⚠️线上运行的代码都是压缩过的, 需要在打包时生成 sourceMap 文件便于debug

2:接口异常上报

接口异常上报可以让开发人员迅速知道有哪些接口出现了大面积的报错,以便迅速修复问题

9、TCP/UDP

TCP 和 UDP 是传输层的两个协议

TCP 和 UDP区别:

        1: UDP 协议是面向无连接的,也就是说不需要在正式传递数据之前先连接起双方。

        2: UDP 协议只是数据报文的搬运工,不保证有序且不丢失的传递到对端。

        3: UDP 协议也没有任何控制流量的算法,

        4: UDP 相较于 TCP 更加的轻便

        TCP 基本是和 UDP 反着来, 建立连接断开连接都需要先需要进行握手。在 传输数据的过程中,通过各种算法保证数据的可靠性, 相比 UDP 来说不那么的高效

UDP特点

        1: 面向无连接

        不需要进行三次握手建立连接,想发数据就可以开始发送。只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作(只去除 IP 报文头就传递给应用层)。

        2: 不可靠性      

        并且收到什么数据就传递什么数据, 并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。

        网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景 ( 比如电话会议) 就需要使用 UDP 而不是 TCP

        3: 高效

        UDP 的头部开销小,只有八字节,相比 TCP的至少二十字节要少得多,在传输数据报文时是很高效的。

        UDP 头部包含了以下几个数据:

        两个十六位的端口号 ,分别为源端口 ( 可选字段) 和目标端口整个数据报文的长度

        整个数据报文的检验和 ( IPv4 可选 字段),该字段用于发现头部信息和数据中的错误

        4: 传输方式

        UDP 不止支持一对一的传输方式, 同样支持一对多, 多对多, 多对一的方 式,也就是说 UDP 提供了单播,多播,广播的功能。

        5: 适合使用的场景

        很多实时性要求高的地方。比如直播,王者荣耀等实时性要求高的游戏。最新的画面才是最需要的

TCP头部

Sequence number:这个序号保证了 TCb 传输的报文都是有序的,对端可以通过序号顺序的拼接报文

Acknowledgement Number:这个序号表示数据接收端期望接收的下一个字节的编号是多 少, 同时也表示上一个序号的数据已经收到

Window Size: 窗口大小 ,表示还能接收多少字节的数据,用于流量控制

标识符:

        URG=1:该字段表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文 ,此时紧急指针有效 。紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。

        ACK=1:该字段表示确认号字段有效。此外,TCP 还规定在连接建立后传送的所有报文段都必须把 ACK 置为一 。

        PSH=1:该字段表示接收端应该立即将数据 push 给应用层, 而不是等到缓冲区满后再提交。

        RST=1:该字段表示当前 TCP 连接出现严重问题,可能需要重新建立 TCb连接,也可以用于拒绝非法的报文段和拒绝连接请求。

        SYN=1:当 SYN=1 , ACK=0 时,表示当前报文段是一个连接请求报文 。

                       当 SYN=1 , ACK=1 时,表示当前报文段是一个同意建立连接的应答报文              FIN=1:该字段表示此报文段是一个释放连接的请求报文 。

TCP三次握手

        第一次握手:客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态

        第二次握手:服务端收到连接请求报文段后, 如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态

        第三次握手: 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入ESTABLISHED 状态, 服务端收到这个应答后也进入ESTABLISHED状态,此时连接建立成功。

TCP断开链接四次握手

        第一次握手:若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。

        第二次握手:B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送

ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了 。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A

        第三次握手:B 如果此时还有没发完的数据会继续发送, 完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。

        PS:通过延迟确认的技术 ( 通常有时间限制, 否则对方会误认为需要重传), 可以将第二 次和第三次握手合并,延迟 ACK 包的发送。

        第四次握手: A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态 。该状态会持续 2MSL ( MSL:最大段生存期,是指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态 。当 B 收到确认应答后,也便进入 CLOSED 状态。

10、HTTP

HTTP 请求由三部分构成,分别为: 请求行、首部、 实体

请求行大概长这样 GET /images/logo.gif HTTP/1.1 , 基本由请求方法、URL、协议版本组成

请求方法GET /POST区别

        1.Get 请求能缓存, Post 不能

        2.Post 相对 Get 安全一点点, 因为 Get 请求都包含在 URL 里 ( 当然想写到 body 里也是可以的),且会被浏览器保存历史纪录。 Post 不会但是在抓包的情况下都是一样的。

        3.URL 有长度限制,会影响 Get 请求,这个长度限制是浏览器规定的,不是RFC规定

        4.Post 支持更多的编码类型且不对数据类型限制

首部

        首部分为请求首部和响应首部, 并且部分首部两种通用。

        通用首部

通用字段

作用
Cache-Control控制缓存的行为

Connection

浏览器想要优先使用的连接类型

比如 keep- alive

Date

创建报文时间

Pragma

报文指令

Via 

代理服务器相关信息
Transfer - Encoding传输编码方式

Upgrade

要求客户端升级协议

Warning

在内容中可能存在错误

        请求首部

请求首部

作用

Accept

能正确接收的媒体类型

Accept-Charset能正确接收的字符集
Accept-Encoding能正确接收的编码格式列表
Accept-Language能正确接收的语言列表

Expect

期待服务端的指定行为

From

请求方邮箱地址

Host

服务器的域名

If-Match

两端资源标记比较

If-Modified-Since

本地资源未修改返回 304 ( 比较时间)

If-None- Match

本地资源未修改返回 304 ( 比较标记)

User-Agent

客户端信息

Max-Forwards限制可被代理以及网关转发的次数

Proxy- Authorization

向代理服务器发送验证信息
Range请求某个内容的一部分
Referer表示浏览器所访问的前一个页面
TE传输编码方式
       
        响应首部

Accept-Ranges

是否支持某些种类的范围

Age资源在代理缓存中存在的时间
ETag资源标识

Location

客户端重定向到某个 URL

Proxy-Authenticate向代理服务器发送验证信息
Server

服务器名字

www-Authenticate

获取资源需要的验证信息

         实体首部
实体首部作用

Allow

资源的正确请求方式

Content-Encoding

内容的编码格式

Content-Language

内容使用的语言

Content-Length

request body 长度

Content-Location

返回数据的备用地址

Content-MD5

Base64 加密格式的内容 MD5 检验值

Content-Range

内容的位置范围

Content-Type

内容的媒体类型

Expires

内容的过期时间

Last_modified

内容的最后修改时间

常见状态码

2XX 成功

  • 200 OK ,表示从客户端发来的请求在服务器端被正确处理
  • 204 No content ,请求成功,但响应报文不含实体的主体部分
  • 205 Reset Content ,请求成功,但响应报文不含实体的主体部分,与 204 响应不同在于要求请求方重置内容
  • 206 Partial Content , 进行范围请求

3XX 重定向

  • 301 moved permanently ,永久性重定向,表示资源已被分配了新的 URL
  • 302 found , 临时性重定向,表示资源临时被分配了新的 URL
  • 303 see other ,表示资源存在着另一个 URL ,应使用 GET 方法获取资源
  • 304 not modified ,表示服务器允许访问资源,但因发生请求未满足条件的情况
  • 307 temporary redirect , 临时重定向,和 302 含义类似,但是期望客户端保持请求方法不变向新的地址发出请求

4XX 客户端错误

  • 400 bad request ,请求报文存在语法错误
  • 401 unauthorized ,表示发送的请求需要有通过 HTTP 认证的认证信息
  • 403 forbidden ,表示对请求资源的访问被服务器拒绝
  • 404 not found ,表示在服务器上没有找到请求的资源

5XX 服务器错误

  • 500 internal sever error ,表示服务器端在执行请求时发生了错误
  • 501 Not Implemented ,表示服务器不支持当前请求所需要的某个功能
  • 503 service unavailable ,服务器暂时处于超负载或正在停机维护, 无法处理请求。

原生写法:

一: GET请求

// 建立一个req对象
var httpRequest = new XMLHttpRequest();

// 使用方法GET,参数url,默认true,建立连接,true表示是异步
httpRequest.open('GET', 'http://api.wer.plus:8080/api/xxx', true);

// 发送请求
httpRequest.send();

// onreadystatechange表示当以上请求的状态码发生改变后再触发函数
httpRequest.onreadystatechange = function () {
	// httpRequest.readyStat表示请求的状态,4为成功下载请求结果
	// httpRequest.status 并且请求的状态码为200时,认为请求成功
	if (httpRequest.readyState == 4 && httpRequest.status == 200) {
		// 接收
		var json = httpRequest.responseText;
		console.log(json);
		
	}else{
        console.log(err);
    }
};



二: POST请求

var httpRequest = new XMLHttpRequest();

// 使用方法POST,参数url,默认true,建立连接,true表示是异步
httpRequest.open('POST', 'http://api.x:8080/api/xxx', true);

// 发送请求,构造json
var data={"token":"xxx","data":"xxx"}

// 格式化json
var stringData=JSON.stringify(data);

// 定义请求头
httpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

// 发送json数据
httpRequest.send(stringData);


httpRequest.onreadystatechange = function () {
    if (httpRequest.readyState == 4 && httpRequest.status == 200) {
        var json = httpRequest.responseText;
        console.log(json);  
    }
};


  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值