Vue篇
data为什么是个函数?
在Vue中,data必须是一个函数,这是因为当data是函数时,每个组件实例化时都会调用该函数,返回一个新的数据对象,从而保证每个组件实例拥有独立的数据,避免数据冲突和不正确的状态更新。
具体来说,如果data是一个对象字面量,多个组件实例会共享同一个数据对象,导致数据冲突和不正确的状态更新。而当data是一个函数时,每次创建组件实例时都会调用该函数,返回一个新的数据对象,每个组件实例都有自己的数据对象,互不干扰。这样,改变其中一个组件的状态不会影响到其他组件。
优点:
- 避免数据冲突:通过将data定义为函数,每个组件实例都会获得一个独立的数据对象,避免了数据冲突和不正确的状态更新。
- 更好的封装和组件化:将data定义为函数可以更好地封装组件的状态,使得组件的逻辑更加清晰和模块化,也使得组件的测试更加容易。
- 提高性能:由于data是函数而不是对象字面量,Vue可以更好地跟踪依赖关系和变化,从而提高响应性和性能。
生命周期:
Vue2的生命周期:
-
beforeCreate
:实例初始化之后,数据观测(data observer)和事件/watcher 设置之前被调。 -
created
:实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event
事件回调。然而,挂载阶段还没开始,$el
属性目前不可见。 -
beforeMount
:模板编译/挂载之前被调用。在这之前,$el
属性还不存在。 -
mounted
:el 被新创建的vm.$el
替换,并挂载到实例上去之后调用。一旦挂载完成,实例就处于可用状态。 -
beforeUpdate
:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 -
updated
:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。 -
activated
:在使用vue-router
时,路由变化(切换到当前组件)时被调用。 -
deactivated
:在使用vue-router
时,路由变化(离开当前组件)时被调用。 -
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用。 -
destroyed
: Vue实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁。
Vue3的生命周期:
-
setup - Vue2中的beforecreate和created被setup方法本身所替代
-
onBeforeMount
- 组件挂载之前调用。 -
onMounted
- 组件挂载之后调用。 -
onBeforeUpdate
- 组件更新之前调用。 -
onUpdated
- 组件更新之后调用。 -
onBeforeUnmount
- 组件卸载之前调用。 -
onUnmounted
- 组件卸载之后调用。 -
onActivated
- 组件被keep-alive激活时调用。 -
onDeactivated
- 组件被keep-alive停用时调用。 -
onErrorCaptured
- 捕获子组件的错误时调用。
父子组件生命周期:
创建时:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
**销毁时:**父beforeUpdated -> 子beforeUpdated -> 子updated -> 父updated
父子组件传参和事件:
子组件
html
<template>
<div class="hello">
<div>{{ msg }} 子组件</div>
<button @click="add">加一</button>
<button @click="add2">加二</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
child: '儿子'
}
},
methods: {
add() {
this.$emit('child')// 触发父组件的自定义事件
},
add2() {
this.$emit('child', 2)// 触发父组件的自定义事件
},
}
}
</script>
<style scoped></style>
父组件
html
<template>
<div id="app">
<!-- <img alt="Vue logo" src="./assets/logo.png"> -->
<h1>父组件</h1>
<HelloWorld :msg="AppMsg" @child="parentMethod" ref="childComponentRef" />
{{ count }}
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
components: {
HelloWorld
},
data() {
return {
AppMsg: "Welcome to Your Vue.js App",
count: 1
}
},
methods: {
parentMethod(e) {
if (e) {
this.count += e
} else {
this.count++
}
},
callChildMethod() {
this.$refs.childComponentRef.add(); // 调用子组件的方法
this.AppMsg = this.$refs.childComponentRef.child; // 访问子组件的属性
},
}
}
</script>
<style></style>
使用provide/inject组件通讯
可以方便地在祖先组件中提供数据给任意后代组件,无需逐层传递props。
提供的组件
javascript
export default {
provide() {
return {
provideData: this.oneData
};
},
data() {
return {
oneData: '来自提供的组件的数据'
};
}
};
子组件使用
javascript
<script>
export default {
inject: ['provideData'],
mounted() {
console.log('从提供的组件中获取的数据:', this.provideData);
}
};
</script>
组件的封装:
父组件(使用被封装的组件)
html
<template>
<div id="map-container">
<!-- 使用组件 -->
<mapItem :mapData="mapData" :isFullScreen="isFullScreen" ref="mapIndex" @pointermove="clickPoint">
<div
class="map-item"
v-for="(item, index) in mapData"
:key="index"
:class="{ active: index + 1 === parseInt(pointName) }"
>
插槽内容
</div>
</mapItem>
</div>
</template>
<script>
import mapItem from "@/components/map/index.vue";//引用组件
export default {
name: "map",
data() {
return {
pointName: "",//传递给组件的数据
isFullScreen: true,
mapData: [
//传递给组件的数据
],
};
},
components: {
mapItem,//声明组件
},
mounted() {},
methods: {
clickPoint(e) {
this.pointName = e;
},
},
};
</script>
<style scoped>
#map-container {
height: 100%;
}
#map-container {
display: flex;
}
.margin-top {
margin-top: 10px;
margin-left: 5px;
}
::v-deep .el-descriptions-item {
line-height: 0.85 !important;
font-size: 11px;
}
.map-item {
border: 1px solid #a3cffe;
border-bottom: 0;
}
.map-item:last-child {
border-bottom: 1px solid #a3cffe;
}
.active {
background-color: #a3cffe;
}
</style>
组件
html
<template>
<div class="map-container">
<div class="map-content">
</div>
<div class="map-right" v-if="$slots.default">
<!-- 默认插槽 -->
<slot />
</div>
</div>
</template>
<script>
export default {
name: "Map",
props: {//接受父组件传来的数据
mapData: Array,
isFullScreen:Boolean,
},
data() {
return {
pointName: "",
};
},
mounted() {},
methods: {
changePoint(e) {
this.pointName = e;
this.$emit('pointermove',this.pointName)
},
},
};
</script>
<style scoped>
.map-container {
height: 100%;
width:100%;
}
.map-container {
display: flex;
}
.map-container .map-content {
flex: 8;
}
.map-container .map-right {
flex: 2;
}
</style>
::v-deep和:deep()
在Vue中,::v-deep 和 :deep() 都用于修改CSS选择器的作用域。 区别在于:
- :deep() 是一个伪类选择器,可以用于将CSS规则应用于当前组件及其所有子组件中匹配选择器的元素。例如,.foo :deep(.bar)会选择包含class为"bar"的元素的所有嵌套层次结构。
- ::v-deep 是一个特殊的深度作用选择器,它只在scoped样式中起作用,并且可以将CSS规则应用于当前组件及其所有子组件中匹配选择器的元素。例如,.foo::v-deep .bar 会选择包含class为"bar"的元素的所有嵌套层次结构,但仅对 .foo组件的样式生效。
$.nextTick
为什么使用nextTick
因为 vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更;这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数,如果不采用这种方法,假设数据改变100次就要去更新100次DOM,而频繁的DOM更新是很耗性能的。
nextTick的作用和使用
- nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
- 使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中;
javascript
new Vue({
data() {
return {
message: 'Hello, Jock!!'
}
},
mounted() {
this.message = 'Hi,Tom'
this.$nextTick(() => {
// 在DOM更新之后执行的操作
console.log(this.$el.textContent) // 输出:"Hi,Tom"
})
}
})
Vue.set
一般定义在data里的数据,由于双向绑定,操作后会发生改变,但是对于复杂数据类型中的值的改变Vue不能检测,所以我们需要用到Vue.set来实现。
为什么要使用Vue.set
因为 value 数据发生变化的时候,Vue 没有立刻去更新 DOM ,而是将修改数据的操作放在了一个异步操作队列中,如果一直修改相同数据,异步操作队列还会进行去重,等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行 DOM 的更新.
Vue.set的使用:
Vue.set( target, key, value )
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据
value :重新赋的值
Vuex
- 提供了一个中心化的状态管理机制,便于多个组件共享和管理状态。
- 支持异步操作,适合大型项目,可以有效降低组件间的耦合度
v-show和v-if
- v-show是通过控制dom元素的display属性来控制元素的显示与隐藏;而v-if是直接在dom树中添加或者删除元素来控制元素的显示与隐藏。
- 切换条件时,v-if控制的元素会进行销毁和重建,而v-show控制的元素只是进行css切换。
- 如果进行不断的条件切换时,因为v-if会对元素进行销毁和重建,所以消耗的性能更大;v-show只是对css进行变更,所以消耗的性能更小。
Vue2和Vue3的区别和优点
Vue2和Vue3的区别
Vue 2和Vue 3之间的主要区别体现在多个方面,包括底层API、语法、根节点、兼容性、生态支持以及开发工具的支持。以下是详细的对比:
底层API和响应式原理
- Vue 2 :使用
Object.defineProperty
来实现响应式数据,这种方式可以劫持对象的属性,但对于数组和对象的变化处理不够灵活,新增属性需要手动处理才能实现响应式。 - Vue 3 :采用
Proxy
来实现响应式数据,可以劫持整个对象,递归返回属性的值的代理,从而实现对整个对象的深度监听,包括属性的新增和删除。
根节点
- Vue 2:必须有一个根节点。
- Vue 3 :可以没有根节点,多个根节点会被默认包裹在一个
Fragment
虚拟标签中,这有助于减少内存使用。
兼容性
- Vue 2:不支持IE8及以下版本。
- Vue 3:不支持IE11及以下版本。
语法和API
- Vue 2:采用选项式API,将数据和函数集中在一个对象中处理,这种方式在复杂项目中不利于代码的阅读和维护。
- Vue 3:采用组合式API,将同一个功能的代码集中在一起处理,使得代码更加有序,有利于代码的书写和维护。
开发工具
- Vue 2:主要使用Vetur插件进行代码高亮和语法提示,Vue 2 Snippets提供语法片段支持。
- Vue 3:推荐使用Volar插件进行代码高亮和语法提示,Vue 3 Snippets提供更好的Vue 3支持。
生态支持
- Vue 2:由JavaScript编写,对TypeScript的支持相对较弱。
- Vue 3:由TypeScript重写,提供更好的TypeScript支持,有利于使用TypeScript进行开发。
生命周期
Vue3.0中 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系:
- beforeCreate===>setup()
- created=======>setup()
- beforeMount ===>onBeforeMount
- mounted=======>onMounted
- beforeUpdate===>onBeforeUpdate
- updated =======>onUpdated
- beforeUnmount ==>onBeforeUnmount
- unmounted =====>onUnmounted
插槽
插槽就是子组件中的提供给父组件使用的一个占位符,用slot标签表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的slot标签。简单理解就是子组件中留下个"坑",父组件可以使用指定内容来补"坑"。vue插槽之插槽的用法及作用域插槽详解-CSDN博客
官方文档:https://v2.cn.vuejs.org/v2/guide/components-slots.html
数据双向绑定的原理
Vue的双向绑定原理是通过数据劫持合发布者-订阅者模式来实现的,关键步骤如下:
- 数据劫持:Vue组件初始化时,对 data 中的 item 进行递归遍历,会通过Object.defineProperty()方法来劫持对象的属性,这个方法可以对一个对象的属性进行定义或修改,包括定义getter和setter。这样,当这些被劫持的属性被读取或赋值时,就会触发相应的getter或setter函数。
- 依赖收集:在编译器编译模板的过程中,如果模板中出现了某个数据对象的属性,那么Vue会为这个属性添加一个订阅者,这个过程就是依赖收集。这样,当数据变化时,就可以通知所有依赖于这个数据的订阅者进行更新。
- 派发更新:当数据发生变化时,会触发setter函数,进而通知所有的订阅者。订阅者收到通知后,就会执行对应的更新函数,从而更新视图。
- 视图更新:最后,Vue会根据更新函数的指示,对DOM进行操作,从而实现视图的更新。
页面白屏的原因
- 元素标签或组件等重要格式出错,引用外部资源格式出错。
- 在渲染页面的时候需要加载很大的JS文件( app.js 和vendor.js ),在JS解析加载完成之前无法展示页面,从而导致了白屏(当网速不佳的时候也会产生一定程度的白屏)。
- 浏览器兼容问题。
- URL 网址无效或者含有中文字符。
- 缓存问题。
- 页面报错。
HTML篇
rem、em、vh、vw、px
em
- em是相对长度单位,它是基于父元素的字体大小。如父元素的字体大小为14px,子元素字体大小为1.2em,显示出来的就是16px。
- 当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px)。
- 如果自身定义了
font-size
按自身来计算,整个页面内1em不是一个固定的值。
rem
rem也是相对单位,但它相对的只是HTML根元素font-size
的值。
vw,vh
- vw 就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度。
- 在pc端中,指的是浏览器的可视区域。
- 在移动端中,指的则是布局视口。
px
绝对单位,页面按精确像素展示。
H5低版本浏览器如何处理
使用html5.js插件兼容(推荐)
html
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
CSS篇
CSS3新特性
-
圆角(border-radius)
htmldiv { border-radius: 10px; }
-
阴影(box-shadow)
htmldiv { box-shadow: 5px 5px 10px #888888; }
-
渐变(gradient)
htmldiv { background: linear-gradient(to right, red, yellow); }
-
变换(transform)
htmldiv:hover { transform: rotate(360deg); }
-
动画(animation)
html@keyframes example { from {background-color: red;} to {background-color: yellow;} } div { animation-name: example; animation-duration: 4s; }
-
多列布局(multi-column layout)
htmldiv { column-count: 3; }
-
过渡效果(transition)
htmldiv:hover { transition: all 0.3s ease-in-out; }
-
用户界面(UI)
htmlinput[type="range"] { -webkit-appearance: none; background: #333; height: 10px; }
-
2D/3D转换(transform)
htmldiv { transform: translate(50px, 100px) rotate(360deg) scale(1.5); }
-
文字效果(text-effects)
htmldiv { text-shadow: 2px 2px 2px #888888; }
-
伪元素(::before, ::after)、媒体查询(@media)、字体图标(@font-face)、网格布局、弹性布局、透明度等。
display: none 和 visibily: hidden 区别?
- **display:none:**隐藏对应的元素,整个元素消失不占空间。
- **visibily:hidden:**隐藏对应的元素,元素还会占用空间。
JS篇
typeof和instanceof的区别是什么?
- 作用: typeof检测数据类型,操作符返回一个字符串,表示未经计算的操作数的类型。instanceof检测对象之间的关联性,运算符用于检测构造函数的
prototype
属性是否出现在某个实例对象的原型链上。 - **返回值:**typeof返回小写字母字符串表示数据属于什么类型,instanceof返回布尔值。
- **操作数:**typeof是简单数据类型、函数或者对象,instanceof的左边必须是引用数据类型右边必须是函数。
- **操作数数量:**typeof是1个,instanceof是2个。
typeof的用法
javascript
typeof "John" // 返回 string
typeof 3.14 // 返回 number
typeof false // 返回 boolean
typeof [1,2,3,4] // 返回 object
typeof {name:'John', age:34} // 返回 object
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof null // 'object'
instanceof 的用法
javascript
// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false
instanceof的底层原理
instanceof查找构造函数的原型对象是否在实例对象的原型链上,如果在返回true,如果不在返回false。 所以,只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false。
javascript
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L) // 当 O 显式原型 严格等于 L隐式原型 时,返回true
return true;
L = L.__proto__;
}
}
懒加载
懒加载也就是延迟加载。当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。
懒加载的原理
- 页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,
- 只有通过javascript设置了图片路径,浏览器才会发送请求。
- 懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,
- 把真正的路径存在元素的"data-url"(这个名字起个自己认识好记的就行)属性里,要用的时候就取出来,再设置;
懒加载的实现步骤
- 首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。
- 页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。
- 在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。
懒加载的优点
页面加载速度快、可以减轻服务器的压力、节约了流量,用户体验好。
promise
对象的遍历
使用for...in
循环:
javascript
let obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 确保属性是对象自身的而不是继承的
console.log(key, obj[key]);
}
}
使用Object.keys()
结合forEach
:
javascript
let obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach(key => {
console.log(key, obj[key]);
});
使用Object.entries()
结合for...of
循环:
javascript
let obj = { a: 1, b: 2, c: 3 };
for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
使用Object.getOwnPropertyNames()
结合forEach
:
深拷贝和浅拷贝
javascript
let obj = { a: 1, b: 2, c: 3 };
Object.getOwnPropertyNames(obj).forEach(key => {
console.log(key, obj[key]);
});
闭包
闭包(closure)是一个函数以及它所引用环境的组合。创建闭包的常见方式是在一个函数内部创建另一个函数,而该内部函数可以访问外部函数的局部变量,即使外部函数已经执行完毕。【JavaScript】一文了解JS的闭包_js闭包-CSDN博客
防抖与节流
-
防抖 :触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。作用:防止事件连续或高频触发,让它只触发一次或者最后一次。
javascriptvar btn = document.querySelector("button"); var tiemr = null; btn.onclick = function () { clearInterval(tiemr); tiemr = setTimeout(() => { console.log("触发了") }, 500) }
-
节流 :高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。作用:降低事件触发的频率,比如1s内最多执行一次。
javascriptlet btn = document.querySelector("button"); let jie = true; btn.onclick = function () { if (jie) { jie = false; console.log('触发了'); setTimeout(() => { jie = true; }, 2000) } }
axios的封装
Axios 是一个基于 promise 网络请求库。封装后我们只需要改变方法,请求路径,和请求返回的数据即可发送网络请求,省去大量重复的代码。
Axios封装代码
javascript
import axios from 'axios';
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // api的base_url
timeout: 5000 // 请求超时时间
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 可以在这里添加请求头等信息
// 例如:config.headers['Authorization'] = 'Bearer your-token';
return config;
},
error => {
// 请求错误处理
console.log(error); // for debug
Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 对响应数据做处理,例如只返回data部分
const res = response.data;
// 如果返回的状态码为200,说明成功,可以直接返回数据
if (res.code === 200) {
return res.data;
} else {
// 其他状态码都当作错误处理
// 可以在这里对不同的错误码进行不同处理
return Promise.reject({
message: res.message || 'Error',
status: res.code
});
}
},
error => {
// 对响应错误做处理
console.log('err' + error); // for debug
return Promise.reject(error);
}
);
export default service;
Axios封装后的使用
javascript
import service from '@/utils/request';
// 获取用户列表
export function getUserList(params) {
return service.get('/user/list', { params: params });
}
// 创建用户
export function createUser(data) {
return service.post('/user/create', data);
}
// 更新用户信息
export function updateUser(id, data) {
return service.put(`/user/update/${id}`, data);
}
数组常用方法
-
push()
- 在数组末尾添加一个或多个元素,并返回新的长度。 -
pop()
- 删除数组的最后一个元素,并返回那个元素。 -
shift()
- 删除数组的第一个元素,并返回那个元素。 -
unshift()
- 在数组的开始添加一个或多个元素,并返回新的长度。 -
slice()
- 选取数组的一部分,返回数组的一个浅拷贝。 -
splice()
- 通过删除现有元素和/或添加新元素来更改一个数组的内容。 -
concat()
- 连接两个或更多数组,并返回一个新数组。 -
join()
- 将数组中的所有元素转换为一个字符串。 -
reverse()
- 颠倒数组中元素的顺序。 -
sort()
- 对数组的元素进行排序。 -
forEach()
- 遍历数组中的每个元素并执行回调函数。 -
map()
- 创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。 -
filter()
- 创建一个新数组, 其包含通过所提供函数符合条件所有元素。 -
reduce()
- 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。 -
reduceRight()
- 对数组中的每个元素执行一个由您提供的reducer函数(降序执行),将其结果汇总为单个返回值。 -
some() - 检测数组元素中是否有元素符合指定条件。
-
fill()- 使用一个固定值来填充数组。
更多内容参考:JavaScript Array 对象 | 菜鸟教程
Git篇
版本冲突
-
检测当前分支
bashgit branch
-
确保你处于需要合并的分支上
bashgit checkout your-branch
-
尝试合并另一个分支。
bashgit merge other-branch
-
如果出现冲突,Git会告诉你哪些文件冲突了
bash# 查看所有冲突的文件 git status
-
打开冲突文件,你会看到类似这样的标记:
bash<<<<<<< your-branch 这是你的版本的内容 ======= 这是other-branch的内容 >>>>>>> other-branch
-
解决冲突后,添加并提交这些文件。
bashgit add . git commit -m "解决冲突"
版本回退
-
查看提交历史,找到你想回退到的那个版本的commit id
bashgit log
-
使用
git reset
命令回退到指定的commit id。bashgit reset --hard commit-id
-
强制推送来更新远程仓库
bashgit push origin your-branch -f
网络综合篇
url请求的过程
浏览器进程与网络进程的通信:浏览器进程通过进程间通信(IPC)将URL请求发送至网络进程。
本地缓存检查:网络进程首先检查本地缓存是否有所请求的资源。如果有缓存资源,则直接返回给浏览器进程;如果没有,则进入网络请求流程。
DNS解析:在进行网络请求前,需要进行DNS解析以获取请求域名的服务器IP地址。如果请求协议是HTTPS,还需要建立TLS连接。
TCP连接建立:利用IP地址和服务器建立TCP连接。
构建请求信息:连接建立后,浏览器端会构建请求行、请求头等信息,并将和该域名相关的Cookie等数据附加到请求头中,然后向服务器发送构建的请求信息。
服务器响应:服务器接收到请求信息后,根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程。
响应头解析:网络进程接收响应行和响应头后,开始解析响应头的内容。如果发现返回的状态码是301或302,说明需要进行重定向到其他URL。
重定向处理:网络进程从响应头的Location字段里读取重定向的地址,然后发起新的HTTP或HTTPS请求,重新开始整个过程。