最近有几次面试,总结了一下面试遇到的题目。不说废话,就直接上干货了。
v-if 和 v-show 的区别
首先,v-if
与 v-show
都能控制dom
元素在页面的显示
v-show
隐藏则是为该元素添加css--display:none
,dom
元素依旧还在。v-if
显示隐藏是将dom
元素整个添加或删除v-if
切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show
只是简单的基于css切换v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染- 性能消耗:
v-if
有更高的切换消耗;v-show
有更高的初始渲染消耗
v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
方法
Computed 和 Watch 的区别
对于Computed:
- 它支持缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当Computed中有异步操作时,无法监听数据的变化
- computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
- 如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
对于Watch:
- 它不支持缓存,数据变化时,它就会触发相应的操作
- 支持异步监听
- 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
- 当一个属性发生变化时,就需要执行相应的操作
- 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
- immediate:组件加载立即触发回调函数
- deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。
总结:
-
computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
-
watch 侦听器 : 更多的是观察 的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
运用场景:
- 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
- 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
组件通信
vue3 组件通信
1. 父组件向子组件传值(defineProps)
props
只能是父组件向子组件进行传值,props
使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。props
可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。props
属性名规则:若在props
中使用驼峰形式,模板中需要使用短横线的形式
html
<!-- 父组件 -->
<template>
<div id="father">
<son :msg="msgData" :fn="myFunction"></son>
</div>
</template>
<!-- setup 语法糖 -->
<script setup>
import son from "./son.vue";
import { ref } from 'vue'
const msgData = ref("父组件数据")
const myFunction = () => {
console.log("vue");
}
</script>
html
<!-- 子组件 -->
<template>
<div id="son">
<p>{{props.msg}}</p>
<button @click="props.fn">按钮</button>
</div>
</template>
<!-- setup语法糖 -->
<script setup>
// 不需要引入 直接使用
const props = defineProps ({
msg: String,
fn: Function
})
</script>
2. 子组件向父组件传值 (defineEmits)
通过 defineEmits
来声明 emit 函数,用于触发父级组件监听的事件。
html
<!-- 父组件 -->
<template>
<child @myClick="onMyClick" @myClick2="onMyClick"></child>
</template>
<script setup>
import child from "./son.vue"
const onMyClick = (msg) => {
console.log(msg) // 这是父组件收到的信息
}
</script>
html
<!-- 子组件 -->
<template>
<!-- 写法一 -->
<button @click="emit('myClick','111')">按钮</button>
<button @click="emit('myClick2','2222')">222</button>
<!-- 写法二 -->
<button @click="handleClick">按钮</button>
</template>
<script setup>
// 使用defineEmits来声明组件可以触发的事件
const emit = defineEmits(["myClick","myClick2"])
const handleClick = ()=>{
emit("myClick", "这是发送给父组件的信息")
emit("myClick2", "这是发送给父组件的信息222")
}
</script>
3.子组件向父组件暴露属性和方法 (defineExpose + ref(获取子组件实例的引用))
因为 <script setup>
的作用域是私有的,默认情况下,在父组件中使用 ref
获取子组件实例时,会发现子组件的所有内部属性和方法都是不可访问的。
通过 defineExpose
,你可以控制哪些数据或方法可以从父组件访问。这对于希望保持封装性同时又需要提供某些公共接口的场景非常有用。
在父组件中,你可以通过 ref
来引用子组件,并访问通过 defineExpose
暴露的属性和方法
html
<!-- 子组件 ChildComponent.vue -->
<template>
<div>这是一个子组件</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => {
count.value++;
};
// 使用 defineExpose 暴露特定的属性和方法
defineExpose({
count,
increment
});
</script>
html
<!-- 父组件 ParentComponent.vue -->
<template>
<!-- <ChildComponent /> -->
<!-- vue 可以按照驼峰式识别 -->
<!-- ChildComponent => child-component 可识别 -->
<child-component ref="childRef"></child-component>
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './son.vue';
const childRef = ref(null);
const callChildMethod = () => {
// 确保元素已挂载
if (childRef.value) {
console.log(childRef.value.count); // 访问子组件的count值
childRef.value.increment(); // 调用子组件的increment方法
}
};
</script>
4.父传子 子组件中 attrs 获取非props属性的集合
当父组件向子组件传递属性时,除了通过 props
明确定义的属性外,还可以传递未在 props
中声明的属性。这些非 props
属性可以通过 $attrs
对象访问。$attrs
包含了所有父级作用域中传递下来的属性(如 HTML 特性、事件监听器等),但不包括 class
和 style
,除非设置了 inheritAttrs: false
。
当你需要在组合式 API 中使用非 props
的属性时,可以利用 useAttrs
函数来获取这些属性。
html
<!-- 父组件 -->
<child :msg1="msg1" :msg2="msg2" title="3333"></child>
<script setup>
import child from "./child.vue"
import { ref } from "vue"
const msg1 = ref("1111")
const msg2 = ref("2222")
</script>
html
<!-- Child.vue 接收 -->
<script setup>
import { useAttrs } from "vue"
const props = defineProps({
msg1: String
})
const attrs = useAttrs()
// msg1 被props 了 不会被attrs获取到
console.log(attrs) // { msg2:"2222", title: "3333" }
</script>
5.provide / inject 祖先 ->子孙 跨层通信
provide 允许一个组件(父组件)向其所有的子组件提供数据。
inject 让子组件能够接收由祖先组件提供的数据。
适合于深层嵌套组件需要访问一些共享的状态或资源的情况
html
<!-- 父组件 -->
<template>
<child></child>
</template>
<script setup>
import { provide } from "vue"
import child from "./son.vue"
provide("name", "沐华")
</script>
xml
<!-- 子组件 -->
<script setup>
import { inject } from "vue"
const name = inject("name")
console.log(name) // 沐华
</script>
<template>
{{ name }}
</template>
6.Vuex/Pinia
Vuex/Pinia 也是一种组件通信
当涉及到跨组件通信时,尤其是对于大型应用,使用状态管理库如 Vuex 或 Pinia 是非常有效的。它们提供了一个集中式存储来管理应用的所有组件状态,并确保状态以一种可预测的方式发生变化。
7.mitt
Vue3 中没有了 EventBus 跨组件通信,有了一个替代的方案 mitt.js,它的原理还是 EventBus。mitt
是一个轻量级的事件处理库,非常适合用于组件间的通信,尤其是非父子关系组件之间的通信。
首先,你需要安装 mitt
:
bash
npm install mitt
使用 Mitt 进行组件间通信
创建 Event Bus
你可以在项目的合适位置创建一个 event bus 实例,通常是一个单独的文件,例如 eventBus.js
:
javascript
// eventBus.js
import mitt from 'mitt';
export const eventBus = mitt();
触发事件
在需要发送事件的组件中,你可以通过这个 event bus 来触发事件:
html
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script>
import { eventBus } from './eventBus';
export default {
methods: {
sendMessage() {
eventBus.emit('message', 'Hello from Component A');
}
}
}
</script>
监听事件
在另一个组件中监听该事件:
xml
vue
深色版本
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { eventBus } from './eventBus';
const handleMessage = (message) => {
console.log(message); // 输出: Hello from Component A
};
onMounted(() => {
eventBus.on('message', handleMessage);
});
onUnmounted(() => {
eventBus.off('message', handleMessage); // 清理事件监听器
});
</script>
vue2 组件通信
1.props 父传子
不能直接修改父组件的数据
html
<!-- 父组件 -->
<template>
<Son :msg="msg"></Son>
</template>
<script >
import Son from './son.vue'
export default {
data() {
return {
msg: 'Hello from parent'
}
},
components: {
Son
}
}
</script>
html
<!-- 子组件 -->
<template>
<div>
{{ msg }}
</div>
</template>
<script >
export default {
// 写法一 用数组接收
props:['msg'],
// 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等
props:{
msg:{
type:String,
default:'这是默认数据'
}
},
mounted(){
console.log(this.msg)
},
}
</script>
2.$emit 子传父
子组件通过派发事件的方式给父组件数据,或者触发父组件更新等操作
html
<!-- 子组件 -->
<template>
<button @click="handleClick">发送消息给父组件</button>
</template>
<script>
export default {
data() {
return {
msg: "这是发给父组件的信息"
}
},
methods: {
handleClick() {
// 使用 $emit 方法派发一个名为 'sendMsg' 的事件,并传递数据
this.$emit('sendMsg', this.msg);
}
}
}
</script>
html
<!-- 父组件 -->
<template>
<!-- 监听来自子组件的 'sendMsg' 事件 -->
<child @sendMsg="getChildMsg" />
</template>
<script>
import Child from './son.vue';
export default {
components: {
Child
},
methods: {
getChildMsg(msg) {
console.log(msg); // 输出: 这是发给父组件的信息
}
}
}
</script>
3.$refs 父组件调用子组件的方法
使用 $refs
访问子组件实例
html
<!-- 父组件 -->
<template>
<child ref="child"></child>
</template>
<script>
import child from './son.vue';
export default {
components: {
child,
},
mounted() {
const child = this.$refs.child;
console.log(child.name); // 输出: 沐华
child.someMethod("调用了子组件的方法");
}
}
</script>
html
<!-- 子组件 -->
<template>
<div>{{ name }}</div>
</template>
<script>
export default {
data(){
return {
name:"沐华"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
</script>
4.$listeners 父传子所有监听事件集合
包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on="$linteners"
html
<!-- 父组件 -->
<template>
<child-component @click="handleClick" @mouseover="handleMouseOver"></child-component>
</template>
<script>
import ChildComponent from './son.vue';
export default {
components: { ChildComponent },
methods: {
handleClick() {
console.log('Child was clicked');
},
handleMouseOver() {
console.log('Mouse over child');
}
}
}
</script>
html
<!-- 子组件 -->
<template>
<div>
<button v-on="$listeners">点击我</button>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$listeners); // 打印出所有的监听器
}
}
</script>
5.EventBus
在 Vue 2 中,Event Bus 是一种常见的用于非父子组件之间通信的方法。它通过创建一个空的 Vue 实例来作为事件分发中心(即事件总线),让不同的组件可以在这个总线上监听和派发事件,从而实现组件间的通信。然而,在 Vue 3 中,由于移除了 $on
、$off
和 $once
方法,直接使用 Vue 实例作为 Event Bus 的方式不再适用。在 Vue 3 中,可以使用第三方库如 mitt
来替代这种功能。
插槽 slot
slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot又分三类,默认插槽,具名插槽和作用域插槽。
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot
中,默认插槽为vm.$slot.default
,具名插槽为vm.$slot.xxx
,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot
中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
默认插槽
这是最基本的插槽形式,用于在一个组件内插入内容。
- 子组件 ChildComponent.vue
html
<template>
<div>
<slot>Default content</slot> <!-- 如果没有提供内容,则显示默认内容 -->
</div>
</template>
- 父组件 ParentComponent.vue
html
<template>
<ChildComponent>
来自父组件
</ChildComponent>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
具名插槽
当你需要在组件的不同部分插入内容时,可以使用具名插槽。
- 子组件 ChildComponent.vue
html
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
- 父组件 ParentComponent.vue
html
<template>
<ChildComponent>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</ChildComponent>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
v-slot
指令是 Vue 2.6.0 引入的新特性,它取代了 slot
和 slot-scope
。简写为 #
,如 v-slot:header
可以写作 #header
。
作用域插槽
作用域插槽允许你从子组件向外传递数据。
- 子组件 ChildComponent.vue
html
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: 'John Doe'
})
</script>
- 父组件 ParentComponent.vue
html
<template>
<ChildComponent v-slot="{ user }">
<p>{{ user.name }}</p>
</ChildComponent>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
</script>
vue 的虚拟DOM
Vue 的虚拟 DOM(Virtual DOM)是 Vue.js 框架中用于提高应用性能和开发体验的关键特性之一。虚拟 DOM 是一个轻量级的 JavaScript 对象树,它是对真实 DOM 的抽象表示。Vue 使用虚拟 DOM 来优化更新操作,从而减少直接操作真实 DOM 所带来的性能开销。确保应用即使在处理大量数据或复杂交互时也能保持流畅的用户体验。
虚拟 DOM 的工作原理
- 渲染阶段:当 Vue 应用初次加载时,它会根据组件的模板或渲染函数生成一棵虚拟 DOM 树。
- 对比阶段(Diffing Algorithm) :当组件的状态发生变化并需要重新渲染时,Vue 会再次生成新的虚拟 DOM 树,并将其与旧的虚拟 DOM 树进行比较(即 diff 算法),以确定哪些部分发生了变化。
- 更新阶段:通过对比新旧两棵虚拟 DOM 树的差异,Vue 只更新实际发生改变的部分到真实 DOM 中,而不是重新渲染整个页面。这种方式极大地提高了性能,特别是在处理大规模数据或复杂界面时。
vue-router的几种模式(hash 和 history) 区别
特性/方面 | Hash 模式 | History 模式 |
---|---|---|
URL 结构 | URL 中包含 # 符号,例如 http://example.com/#/path |
使用标准的 URL 路径结构,例如 http://example.com/path |
浏览器兼容性 | 高度兼容,几乎所有现代和旧版浏览器都支持 | 需要 HTML5 History API 支持,大多数现代浏览器支持(IE10+) |
服务器配置需求 | 不需要服务器端配置即可工作 | 需要服务器端配置来处理路由刷新或直接访问时返回正确的 index.html 页面 |
页面刷新行为 | 刷新页面不会丢失当前路由状态 | 直接访问或刷新特定路由时,如果服务器未正确配置,则可能导致 404 错误 |
URL 美观度 | URL 包含 # ,可能不太美观 |
更加干净、直观的 URL,没有 # |
实现原理 | 利用 URL 的 hash 部分变化来触发页面更新,不会向服务器发起请求 | 使用 HTML5 History API(如 pushState, replaceState)改变 URL 并管理历史记录 |
SEO 影响 | 对 SEO 不友好,因为搜索引擎可能会忽略 # 后的内容 |
更有利于 SEO,可以更好地被搜索引擎索引 |
开发复杂度 | 开发简单,不需要额外的服务器配置 | 可能需要额外的服务器配置来确保所有路由指向应用的入口文件 |
防抖和节流
- 防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
- 节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
防抖函数的应用场景:
- 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
- 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce
节流函数的应用场景:
- 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
手写防抖
js
function debounce(fn, wait) {
var timer = null;
return function() {
var context = this,
args = [...arguments];
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) {
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
手写节流
js
// 时间戳版
function throttle(fn, delay) {
var preTime = Date.now();
return function() {
var context = this,
args = [...arguments],
nowTime = Date.now();
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - preTime >= delay) {
preTime = Date.now();
return fn.apply(context, args);
}
};
}
// 定时器版
function throttle (fun, wait){
let timeout = null
return function(){
let context = this
let args = [...arguments]
if(!timeout){
timeout = setTimeout(() => {
fun.apply(context, args)
timeout = null
}, wait)
}
}
}
解决同源策略
同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议 、端口号 、域名必须一致。
同源政策主要限制了三个方面:
- 当前域下的 js 脚本不能够访问其他域下的
cookie
、localStorage
和indexDB
。 - 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
- 当前域下 ajax 无法发送跨域请求。
1. CORS(跨域资源共享)
CORS 是一种W3C标准,允许服务器声明哪些源可以访问其资源。通过在响应头中添加特定的HTTP头部信息,服务器可以明确地告知浏览器允许哪些域、哪些HTTP方法以及哪些头部字段。
设置CORS响应头:
js
Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒
对于预检请求(Preflight Request),客户端会先发送一个 OPTIONS
请求来检查服务器是否允许实际请求。
2. JSONP(仅支持GET请求)
JSONP(JSON with Padding)是一种通过 <script>
标签绕过同源策略的方法。它利用了 <script>
标签没有跨域限制的特点,但只适用于 GET
请求。
html
<script src="http://example.com/data?callback=myCallback"></script>
<script>
function myCallback(data) {
console.log(data);
}
</script>
注意:由于安全性问题,现代Web应用中较少使用JSONP。
3. 代理服务器
如果无法修改目标服务器以支持CORS,可以在同一源下设置一个代理服务器,前端向这个代理服务器发送请求,代理服务器再向目标服务器转发请求,并将结果返回给前端。
- 前端向代理服务器发送请求。
- 代理服务器接收请求后,向目标服务器发起请求。
- 目标服务器响应给代理服务器。
- 代理服务器将响应返回给前端。
4. WebSocket
WebSocket 协议提供了一种全双工通信通道,不遵循同源策略,因此可以用来实现跨域通信。
5. postMessage API
如果你需要在不同窗口或iframe之间进行跨域通信,可以使用 postMessage
API。这允许你安全地跨域发送消息。
js
// 在父页面或一个窗口中发送消息
otherWindow.postMessage('Hello from another domain', 'https://recipient.example.com');
// 在接收方监听消息
window.addEventListener('message', (event) => {
// 检查消息来源是否可信
if (event.origin !== 'https://expected-origin.example.com') return;
console.log(event.data); // 输出接收到的消息
});
6. nginx代理跨域
nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin...等字段。
1)nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
ini
location / {
add_header Access-Control-Allow-Origin *;
}
2)nginx反向代理接口跨域
跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。
实现思路:通过Nginx配置一个代理服务器域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。
nginx具体配置:
nginx
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
7. vite
在开发环境中,Vite 提供了便捷的方式通过配置代理来解决前端应用中的跨域问题。Vite 的开发服务器内置了一个基于 http-proxy
的代理功能,可以用来转发请求并处理 CORS(跨源资源共享)问题。
Vite 的代理功能主要用于开发环境,帮助开发者绕过跨域限制。对于生产环境,通常需要在后端服务器上正确配置 CORS 或者使用其他解决方案(如 Nginx 反向代理)。
js
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
server: {
proxy: { // 代理 媒婆
'/api': { // 拦截以/api开头的请求
// 转发给目标地址
target: 'http://localhost:7001/', // vite 网络ip请求方式 没有跨域问题
changeOrigin: true, // 改变请求的源头,通常需要设置为 true
rewrite: (path) => path.replace(/^/api/, ''), // 重写路径,移除前缀 `/api`
// 可选:如果后端 API 返回的响应头中没有设置 CORS 头,你可以手动添加
configure: (proxy, options) => {
proxy.on('proxyRes', (proxyRes, req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
});
}
}
}
})
localStorage、sessionStorage 和 Cookies区别
特性/存储方式 | localStorage | sessionStorage | Cookies |
---|---|---|---|
生命周期 | 数据持久化保存,除非被手动删除或清除浏览器缓存 | 页面关闭时数据会被清除 | 可设置过期时间,默认是会话结束(关闭浏览器) |
存储容量 | 约5MB(具体取决于浏览器) | 同 localStorage |
每个域名最多4KB(包括所有属性),通常建议不超过 4093 字节以确保兼容性 |
作用域 | 同源策略下有效(协议+域名+端口相同) | 同 localStorage |
同源策略下有效,但可以设置为跨域共享(通过 Domain 属性) |
访问方式 | 使用 window.localStorage 对象 |
使用 window.sessionStorage 对象 |
通过 document.cookie 访问和修改 |
传输到服务器 | 不自动发送到服务器 | 不自动发送到服务器 | 在每次 HTTP 请求中都会携带(如果设置了 path 和 domain 匹配当前请求) |
适用场景 | 需要长期保存的数据,如用户偏好设置 | 临时数据,如单页面会话期间的状态信息 | 跟踪用户会话状态、个性化设置、记住登录状态等 |
get 和 post 请求区别
特性/方面 | GET 请求 | POST 请求 |
---|---|---|
用途 | 主要用于获取信息,应当是幂等的(多次请求结果相同)。例如查询数据。 | 用于提交数据到服务器进行处理,如提交表单或上传文件。 |
参数传递方式 | 参数附加在 URL 后面作为查询字符串(Query String)。URL 的最大长度受限于浏览器和服务器(通常为2048字符左右)。 | 参数通过请求体(Request Body)发送,不会显示在 URL 中。理论上可以发送大量数据。 |
缓存 | 可以被浏览器缓存,并且可能会被保存到浏览器的历史记录中。 | 不会被缓存,默认情况下也不会保存到浏览器历史记录中。 |
书签化 | 由于参数直接包含在 URL 中,因此可以被收藏为书签。 | 由于数据包含在请求体中,无法直接收藏为书签。 |
安全性 | 因为数据直接暴露在 URL 中,不适合传输敏感信息。 | 数据不在 URL 中显示,相对更安全,但仍然需要使用 HTTPS 来加密传输以防中间人攻击。 |
幂等性 | 幂等操作,意味着相同的 GET 请求多次执行应该产生相同的结果。 | 非幂等操作,多次提交可能导致服务器状态变化(例如重复下单)。 |
数据类型支持 | 仅支持 ASCII 字符。 | 支持多种编码类型,包括二进制数据。 |
对浏览器历史的影响 | 会改变浏览器地址栏的 URL,用户可以通过"后退"按钮回到之前的页面状态。 | 不会改变浏览器地址栏的 URL,用户不能通过"后退"按钮撤销 POST 操作。 |
SEO 影响 | 对 SEO 友好,搜索引擎可以抓取并索引这些链接。 | 对 SEO 不友好,因为内容不直接反映在 URL 上。 |
css 盒子模型
box-sizing 属性
-
content-box
(默认值)- 在
content-box
模式下,设置的width
和height
只包含内容区域(不包括内边距 padding、边框 border 或者外边距 margin)。 - 如果你为一个元素设置了宽度和高度,并且还设置了 padding 或 border,那么这些额外的空间会添加到总尺寸上,使整个元素的实际占用空间变大。
- 在
-
border-box
- 在
border-box
模式下,设置的width
和height
包括了内容区域、内边距(padding)和边框(border),但不包括外边距(margin)。 - 这意味着如果你指定了一个元素的宽度和高度,并且增加了 padding 或 border,浏览器会从你设定的宽度和高度中减去这些值,以确保整个元素占据的空间不会超过你设定的宽度和高度。
- 在
HTTP 1.1 和 HTTP 2.0 的区别
- 二进制协议:HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧",可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。
- 多路复用: HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了"队头堵塞"的问题。
- 数据流: HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流。
- 头信息压缩: HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
- 服务器推送: HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。
js 事件循环机制
1.同步和异步的区别
- 同步指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。
- 异步指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。
2.任务队列/事件循环(Event Loop)
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。(异步事件不是宏任务就是微任务)
任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件(宏任务)执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。
Event Loop 执行顺序如下所示:
- 首先执行同步代码,这属于宏任务
- 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
- 执行所有微任务
- 当执行完所有微任务后,如有必要会渲染页面
- 然后开始下一轮 Event Loop,执行宏任务中的异步代码
3. 微任务和宏任务
微任务(Microtasks)和 宏任务(Macrotasks) 是JavaScript事件循环中的两个重要概念。
- 宏任务:包括整体代码script(全局脚本也是宏任务,如console.log)、setTimeout、setInterval、I/O操作、UI渲染等。宏任务会在事件循环的每个周期中执行一个。
- 微任务:包括Promise的回调(then/catch/finally)、对 Dom 变化监听的 MutationObserver、process.nextTick(Node.js环境)等。微任务会在当前宏任务执行完毕后立即执行,且在下一个宏任务之前。
执行顺序:在一个事件循环中,先执行一个宏任务(全局脚本),然后该宏任务期间产生的所有微任务,接着再执行下一个宏任务,依此类推。
对闭包的理解
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途;
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
es6 新特性
- let 和 const
let
: 提供了块级作用域,允许在代码块(如if
语句或循环)中声明变量,这些变量仅在该块内有效。const
: 同样提供块级作用域,但声明的变量值不能重新赋值。对于对象和数组,虽然不能重新赋值整个对象或数组,但是可以修改它们的内容。
- 函数默认参数
- 允许你在函数定义时为参数设置默认值,如果调用函数时没有提供相应的参数,则使用默认值。
- 箭头函数
- 箭头函数提供了一种更简洁的方式来编写函数,并且不会创建自己的
this
绑定,而是继承自父级作用域。
- 箭头函数提供了一种更简洁的方式来编写函数,并且不会创建自己的
- 模板字符串
- 使用反引号定义,支持嵌入表达式 `${expression}`,使得字符串拼接更加直观和方便。
- 扩展运算符
- 可以将数组或对象展开为独立的元素,常用于函数调用、数组构造等场景。
- 解构赋值
- 可以从数组或对象中提取数据并直接赋值给变量,简化了访问复杂数据结构的过程。
- 类 class
- 更清晰的面向对象编程语法,对于那些有其他面向对象编程经验的人来说,
class
语法更加自然和易于接受,减少了学习曲线,促进了团队协作中的代码共享和理解。
- 更清晰的面向对象编程语法,对于那些有其他面向对象编程经验的人来说,
- import 和 export
- 模块化
- Promise
- 提供了一种处理异步操作的方式,避免了回调地狱问题,使代码更易读和维护。
- Symbol
- 一种新的原始类型,每个 Symbol 都是唯一的,通常用于作为对象属性的键,防止属性名冲突。
- Map和Set
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为键或值。Set
对象允许存储任何类型的唯一值,重复的值会被忽略。
数据类型存储方式(堆和栈)
两种类型的区别在于存储位置的不同:
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
- 在数据结构中,栈中数据的存取方式为先进后出。
- 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收
js 获取dom的方式
- getElementById // 按照 id 查询
- getElementsByTagName // 按照标签名查询
- getElementsByClassName // 按照类名查询
- querySelectorAll // 按照 css 选择器查询
语义化标签
- header:定义文档的页眉(头部);
- nav:定义导航链接的部分,它可以被用于任何类型的导航链接组,不仅限于顶部导航栏。
- footer:定义文档或节的页脚(底部);
- article:代表文档、页面、应用或网站中的一个独立的内容块,理论上可以独立分发或重用。
- section:定义文档中的节或分区。它用于将文档分成不同的主题部分,每个部分应该围绕一个特定的主题组织内容,并通常应包括标题。
- aside:定义其所处内容之外的内容(侧边);
<main>
:指定文档的主要内容。每个页面只能有一个<main>
元素,并且不应被嵌套在<article>
、<aside>
、<footer>
、<header>
或<nav>
这些标签内<details>
和<summary>
:这两个标签一起使用来创建用户可以展开查看或折叠隐藏的详细信息区块,其中<summary>
提供了一个可见的标题,点击它可以切换显示或隐藏<details>
内的内容。<figure>
和<figcaption>
:<figure>
用于包含图表、图片、视频等内容,而<figcaption>
则是其标题或说明。
语义化的优点
语义化是指根据内容的结构化(内容语义化),选择合适的标签(代码语义化)。通俗来讲就是用正确的标签做正确的事情。
语义化的优点如下:
●对机器友好,带有语义的文字表现力丰富,更适合搜索引擎的爬虫爬取有效信息,有利于SEO(搜索引擎优化)。除此之外,语义类还支持读屏软件,根据文章可以自动生成目录;
●对开发者友好,使用语义类标签增强了可读性,结构更加清晰,开发者能清晰的看出网页的结构,便于团队的开发与维护。
行内元素、块级元素
- 行内元素有:
a b span img input select strong
; - 块级元素有:
div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p
;
垂直水平居中
html
<div class="wrapper flex-center">
<p>horizontal and vertical</p>
</div>
1. flex
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
2. flex + margin
可以理解为子元素被四周的 margin "挤" 到了中间。
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
display: flex;
}
.wrapper > p {
margin: auto;
}
3. transform + absolute
常用于图片的居中显示
html
<div class="wrapper">
<img src="test.png">
</div>
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
position: relative;
}
.wrapper > img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
4. table-cell
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
display: table-cell;
text-align: center;
vertical-align: middle;
}
5. writing-mode
这个方法可以改变文字的显示方向,比如让文字的显示变为垂直方向。
html
<div class="wrapper">
<div class="wrapper-inner">
<p>horizontal and vertical</p>
</div>
</div>
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
writing-mode: vertical-lr;
text-align: center;
}
.wrapper > .wrapper-inner {
writing-mode: horizontal-tb;
display: inline-block;
text-align: center;
width: 100%;
}
.wrapper > .wrapper-inner > p {
display: inline-block;
margin: auto;
text-align: left;
}
7. grid
css
.wrapper {
width: 300px;
height: 300px;
border: 1px solid #ccc;
display: grid;
}
.wrapper > p {
align-self: center;
justify-self: center;
}
性能优化
图片懒加载
懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。
如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
防抖和节流
防抖函数可以将多次高频率触发的函数执行合并成一次,并在指定的时间间隔后执行一次。通常在处理输入框、滚动等事件时使用,避免频繁触发事件导致页面卡顿等问题。
节流是一种常用的性能优化技术,它可以限制函数的执行频率,避免过多的重复操作,提升页面的响应速度。
CDN
CDN(Content Delivery Network,内容分发网络)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。
- 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
- 部分资源请求分配给了CDN,减少了服务器的负载
css 动画制作
在前端开发中,animation
、transform
和transition
是CSS中用于创建动画效果的关键属性。它们各自有不同的用途和特点,下面分别介绍这三个属性。
Animation
@keyframes
规则定义了一个动画序列,它描述了从一个样式到另一个样式的过渡过程。animation
属性则是用来应用这些关键帧定义的动画到选定的元素上。
-
定义动画 :使用
@keyframes
定义动画。css@keyframes slidein { from { margin-left: 100%; width: 300%; } to { margin-left: 0%; width: 100%; } }
-
应用动画 :通过
animation
属性将动画应用到元素上。cssdiv { animation-name: slidein; animation-duration: 5s; animation-timing-function: ease-in-out; animation-delay: 2s; animation-iteration-count: infinite; animation-direction: alternate; }
Transform
transform
属性允许你对元素应用二维或三维转换,比如旋转、缩放、倾斜或移动等。
-
常用转换 :
translate(x, y)
:移动元素。scale(x, y)
:改变元素大小。rotate(angle)
:旋转元素。skew(x-angle, y-angle)
:倾斜元素。
cssdiv { transform: translate(50px, 100px) scale(1.5) rotate(20deg); }
translate3d
用于在三维空间内移动元素。它允许你指定沿 x、y 和 z 轴的移动距离,从而实现元素在网页中的三维位置变换。
它可以触发GPU加速,这在处理复杂的动画和转换时能够显著提高性能。这是因为大多数浏览器对translate3d()
应用硬件加速,使得动画更加流畅,尤其是在移动设备上。
Transition
transition
属性用于控制元素从一种样式变换到另一种样式的过渡效果。它能够指定哪些属性应该产生过渡效果、持续时间、速度曲线以及延迟时间。
-
基本用法 :
cssdiv { transition-property: width; transition-duration: 1s; transition-timing-function: linear; transition-delay: 0.5s; } /* 或者使用简写 */ div { transition: width 1s linear 0.5s; }
-
当指定的CSS属性值发生变化时(如:hover),就会触发过渡效果。
总结
- Animation适用于需要更复杂、多步骤的动画场景,且可以循环播放、反向播放等。
- Transform提供了一种方式来改变元素的位置、尺寸、角度等,非常适合于动态效果的应用。
- Transition则更适合简单的状态变化(例如hover效果)之间的平滑过渡,使用起来相对简单直接。
if(false)中的false可以拿什么替代 (值为false的)
- 比较运算:1 == 2 ......
- 逻辑运算符:!(true) 、 false && true 、 true || false 等
- 数值:0、-0、NaN、null、undefined、""
function 箭头函数 区别
特性 | 传统函数示例 | 箭头函数示例 | 备注 |
---|---|---|---|
语法 | function(a, b) { return a + b; } | (a, b) => a + b | 箭头函数具有更简洁的语法,尤其适合单行表达式 |
this 关键字 |
取决于调用者上下文 | 继承自父作用域,不会创建自己的this |
箭头函数中的this 是词法作用域的,即基于使用时的外层作用域 |
构造器 | 可以通过new 关键字实例化 |
不支持new 操作符,尝试会抛出错误 |
箭头函数不能作为构造器使用 |
arguments 对象 |
支持arguments 对象来访问传入参数 |
不支持arguments ,但可以使用剩余参数...args |
箭头函数不提供arguments 对象,但ES6的剩余参数提供了类似功能 |
适用场景 | 适用于需要动态this 值的场景 |
更适合用于回调函数和不需要独立this 的场景 |
根据具体需求选择合适的函数类型 |
绑定this |
需要使用.bind(this) 等方法手动绑定this |
无需绑定,因为this 是从封闭的作用域继承来的 |
对于固定this 的情况,箭头函数更为方便 |
Promise 介绍
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
(1)Promise的实例有三个状态:
- Pending(进行中)
- Fulfilled(已完成)
- Rejected(已拒绝)
(2)Promise的实例有两个过程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected :Rejected(已拒绝)
一旦从进行状态变成为其他状态就永远不能更改状态了。
状态的改变是通过 resolve()
和 reject()
函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
注意: 在构造 Promise
的时候,构造函数内部的代码是立即执行的
回调地狱
什么是回调地狱
回调地狱(Callback Hell),也被称为"金字塔之殇",是异步编程中常见的一个问题,尤其是在使用回调函数处理多个嵌套的异步操作时。当一系列异步操作需要依赖前一个操作的结果依次执行时,代码会形成层层嵌套的结构,导致代码难以阅读和维护。
js
asyncFunction1(function(err, data1) {
if (err) {
console.error(err);
return;
}
asyncFunction2(data1, function(err, data2) {
if (err) {
console.error(err);
return;
}
asyncFunction3(data2, function(err, data3) {
if (err) {
console.error(err);
return;
}
// 处理最终结果
});
});
});
解决回调地狱
- Promise
Promises提供了一种更清晰的方式来处理异步操作,允许你链式调用.then()
方法,而不是层层嵌套回调。
javascript
asyncFunction1()
.then(result1 => asyncFunction2(result1))
.then(result2 => asyncFunction3(result2))
.then(result3 => {
// 处理最终结果
})
.catch(error => console.error(error));
- Async/Await
async
/await
是ES2017引入的一种编写异步代码的新方式,它基于Promises之上,但语法更加简洁直观,让异步代码看起来像同步代码一样。
js
async function executeOperations() {
try {
let result1 = await asyncFunction1();
let result2 = await asyncFunction2(result1);
let result3 = await asyncFunction3(result2);
// 处理最终结果
} catch (error) {
console.error(error);
}
}
executeOperations();
- 模块化与函数抽取
即使不使用Promises或async
/await
,也可以通过将逻辑拆分成小的、可重用的函数来减少嵌套层级,提高代码的可读性。
js
function step1(callback) {
asyncFunction1((err, result1) => {
if (err) return callback(err);
step2(result1, callback);
});
}
function step2(prevResult, callback) {
asyncFunction2(prevResult, (err, result2) => {
if (err) return callback(err);
step3(result2, callback);
});
}
// 使用
step1((err, finalResult) => {
if (err) {
console.error(err);
} else {
console.log(finalResult);
}
});
vue2 和 vue3 区别
特性/方面 | Vue 2 | Vue 3 |
---|---|---|
API风格 | Options API(选项式api) | Composition API(组合式api,也支持Options API) |
响应式系统 | 使用Object.defineProperty |
使用Proxy ,具有更好的性能和数据追踪能力 |
Teleport组件 | 不支持 | 支持,可以将DOM元素渲染到文档其他位置 |
全局API名称 | 统一的全局API | 全局API发生变化,更加模块化 |
新功能 | - | watchEffect , 新的生命周期钩子等 |
TypeScript支持 | 支持,但集成不够深入 | 更好的原生支持,类型推断更准确 |
核心库体积 | 较大 | 核心库依赖减少,打包体积更小 |
Tree Shaking支持(性能优化) | 有限支持 | 更好地支持Tree Shaking,按需引入模块 |
pinia 和 vuex 区别
Pinia 和 Vuex 都是 Vue.js 的状态管理库
Vuex:
使用Options API(选项式API),通常需要在 store 中定义 state (状态)、mutations (更改state)、actions (提交的是 mutation,而不是直接变更状态) 和 getters(类似计算属性)。Vuex 强调集中式存储,并且所有的状态更改必须通过提交 mutation 来进行。操作较为固定,例如,mutation 必须是同步的,而 action 可以包含异步操作。
Pinia:
更倾向于使用Composition API(组件式API),可以更自由地组织代码,使得状态管理更加直观。不强制将所有逻辑分割到固定的模块中,而是允许更自然地组织代码。支持直接返回对象或使用defineStore 创建 store,提供了更大的灵活性。Pinia 允许你在 setup 中直接访问 store 的属性和方法,简化了使用。
keep-alive
<keep-alive>
是 Vue.js 中的一个抽象组件,它用于缓存动态组件实例,而不是每次都销毁并重新创建它们。这在需要保持组件状态(如表单输入内容、滚动位置等)或优化性能(减少组件初始化的时间和资源消耗)时非常有用。
主要特点
- 缓存组件 :当组件在
<keep-alive>
标签内切换时,其状态会被保存,不会被销毁。 - 生命周期钩子 :使用
<keep-alive>
包裹的组件会获得两个额外的生命周期钩子:activated
和deactivated
。当组件被激活时调用activated
钩子,当组件停用时调用deactivated
钩子。 - include/exclude 属性 :可以通过这些属性指定哪些组件应该被缓存(
include
)或不应该被缓存(exclude
)。可以是字符串、正则表达式或数组形式。 - max :
max
属性被引入以限制缓存组件的最大数量,帮助控制内存使用。(vue3)
html
<template>
<div id="app">
<!-- 使用 keep-alive 来缓存动态组件 -->
<keep-alive include="componentA" :max="10">
<component v-bind:is="currentComponent"></component>
</keep-alive>
</div>
</template>
<script>
export default {
data() {
return {
currentComponent: 'componentA' // 动态切换的组件名
}
},
components: {
componentA: { /* ... */ },
componentB: { /* ... */ }
}
}
</script>
从浏览器输入网址,到页面渲染完成 中间的过程
1. DNS解析
- 域名系统(DNS)查找:浏览器首先需要将你输入的域名转换为IP地址。它会检查本地DNS缓存,如果没有找到,则会向配置的DNS服务器发送请求来获取相应的IP地址。
2. 建立TCP连接
- 三次握手:一旦获取了目标服务器的IP地址,浏览器就会尝试与该服务器建立TCP连接。这通常通过TCP协议的三次握手过程完成,确保客户端和服务器都能发送和接收数据。
3. 发送HTTP/HTTPS请求
- 浏览器向服务器发送一个HTTP(或HTTPS,如果使用SSL/TLS加密的话)请求,请求包含请求行(如GET /index.html HTTP/1.1)、请求头部信息(如Host、User-Agent等)以及可能的请求体(对于POST请求等)。
4. 服务器处理请求并响应
- 服务器端处理:服务器接收到请求后,根据请求的内容进行处理,可能是查询数据库、执行业务逻辑等,并生成HTML文档作为响应。
- 返回响应:服务器将生成的HTML文档连同状态码(如200 OK表示成功)和其他响应头一起发回给浏览器。
5. 浏览器接收并解析HTML
- 解析HTML:浏览器开始从上到下解析HTML文档,构建DOM树。
- 如果遇到外部资源链接(如CSS、JavaScript文件),则会发起新的HTTP请求加载这些资源。
6. 加载和处理外部资源
- CSS文件 :当浏览器遇到
<link>
标签时,会异步下载CSS文件,并且不会阻塞HTML文档的解析。 - JavaScript文件 :遇到
<script>
标签时,默认情况下会暂停HTML文档的解析,直到脚本被下载并执行完毕(除非设置了async
或defer
属性)。
7. 渲染页面
- 构建渲染树:浏览器结合DOM树和CSSOM(CSS对象模型)来创建渲染树,这个树只包括那些对页面视觉效果有贡献的节点。
- 布局(Layout) :计算每个元素的大小和位置。
- 绘制(Paint) :将渲染树中的节点按照正确的顺序绘制到屏幕上。
DNS 查询过程
-
浏览器缓存检查
- 当用户在浏览器中输入一个网址并按下回车键时,浏览器首先会检查其内部的DNS缓存,查看是否已经存在该域名对应的IP地址记录。如果找到,则直接使用;如果没有找到,则继续下一步。
-
系统
hosts
文件检查- 如果浏览器缓存未命中,操作系统会接管请求,并检查本地的
hosts
文件(例如Windows上的C:\Windows\System32\drivers\etc\hosts
或Linux/MacOS上的/etc/hosts
)。如果在此文件中找到了对应域名的IP地址定义,操作系统将使用这里的IP地址而不再进行进一步的DNS查询。
- 如果浏览器缓存未命中,操作系统会接管请求,并检查本地的
-
本地DNS解析器缓存检查
- 如果
hosts
文件也没有相应的记录,操作系统会向配置的本地DNS解析器(可能是ISP提供的DNS服务器或公共DNS服务如Google DNS 8.8.8.8)发起请求。本地DNS解析器也会有自己的缓存,它会先检查自己的缓存中是否有该域名的记录。
- 如果
-
本地域名服务器(递归查询)
-
如果本地DNS解析器缓存也未命中,它将开始执行递归查询,即代表客户端继续向下一级DNS服务器查询直到得到结果。这个过程包括:
- 本地配置区域资源:本地DNS服务器可能会有某些域名的权威信息,如果是这样,可以直接返回结果。
- 本地DNS服务器缓存:本地DNS服务器还会检查自己缓存中的记录。
-
-
根域名服务器
- 如果本地DNS服务器没有所需的信息,它会向根域名服务器发送查询请求。根域名服务器并不知道具体的IP地址,但它知道顶级域(TLD)服务器的位置(例如
.com
,.org
等)。
- 如果本地DNS服务器没有所需的信息,它会向根域名服务器发送查询请求。根域名服务器并不知道具体的IP地址,但它知道顶级域(TLD)服务器的位置(例如
-
主域名服务器(TLD服务器)
- 根据根域名服务器提供的信息,本地DNS服务器接下来会向适当的TLD服务器发送查询请求。TLD服务器同样不会直接提供最终答案,而是指向负责特定域名的权威DNS服务器。
-
下一级域名服务器(权威DNS服务器)
- 最后,本地DNS服务器根据TLD服务器提供的信息,向真正的权威DNS服务器发送查询请求。这台服务器拥有实际的域名到IP地址的映射,并能给出最终的答案。
递归查询与迭代查询的区别
- 在整个过程中,客户端只与本地DNS服务器交互,这种查询被称为递归查询,因为客户端要求本地DNS服务器必须返回确切的结果,无论需要多少步。
- 而本地DNS服务器与其他DNS服务器之间的交互是迭代查询,意味着每个DNS服务器仅返回最佳的下一步查询位置,而不是直接提供最终答案。