上一篇链接
如果有看不懂的,别硬看,直接chatgpt,让它回答。
- 我的博客需要缩宽页面观看,图片无法均放,很抱歉。
1. 请说明 Ajax Fetch Axios 三者的区别?
1. 用 XMLHttpRequest 实现 Ajax
function ajax1(url, successFn) {
const xhr = new XMLHttpRequest()
xhr.open("GET", url, false)
xhr.onreadystatechange = function () {
// 这里的函数异步执行
if (xhr.readyState == 4) {
if (xhr.status == 200) {
successFn(xhr.responseText)
}
}
}
xhr.send(null)
}
2.使用Fetch发送POST请求提交数据:
fetch('/api/data', {
method: 'POST',
body: JSON.stringify({
name: 'John', age: 30 }),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
3.使用Axios发送PUT请求更新数据:
axios.put('/api/data/123', {
name: 'Jane', age: 35 })
.then(response => console.log(response.data))
.catch(error => console.error(error));
- 使用原生的Ajax需要手动创建XMLHttpRequest对象,并编写回调函数来处理响应。
- Fetch则更加现代化,使用Promise封装了网络请求,并提供了更简洁的API。
- 而Axios则在Fetch的基础上进一步封装,提供了更多的选项和错误处理机制。
lib 和 API的区别? 直接问chatgpt ,它回答全面
2. px % em rem vw vh 有什么区别?
-
px(基本单位,绝对单位):它指定了物体在屏幕上的精确尺寸,例如1个像素就是指视觉上的最小点。这意味着,如果在不同的设备上使用相同数量的像素,那么它们在不同的屏幕上看起来可能会有不同的大小,在高分辨率的屏幕上可能会显示更细致的细节。
-
百分比(%)是相对单位,它是相对于父元素的大小来计算的。例如,如果一个元素的宽度设置为50%,那么它将占据其父元素宽度的50%。这使得百分比非常适合用于响应式设计,因为它可以自动调整大小以适应不同屏幕大小的父容器。
-
em(字体相对长度单位):em是基于当前元素的字体大小来计算其他元素的大小的相对单位。例如,如果一个元素的字体大小为16px,那么1em就等于16px。如果另一个元素的字体大小为20px,那么1em就等于20px。
-
rem(根元素字体大小单位):rem也是相对单位,但是它是相对于根元素的字体大小来计算其他元素的大小的。根元素通常是HTML元素。
-
vw(视窗宽度单位):vw是相对于视口宽度的单位。1vw等于视口宽度的1%,例如,如果视口宽度为1000像素,那么1vw就等于10像素。
-
vh(视口高度单位):vh是相对于视口高度的单位。1vh等于视口高度的1%。
3. 箭头函数的缺点?
- 箭头函数无法通过call()、apply()和bind()方法来改变其this值。因此,如果你需要在函数内部访问不同的this值,就不能使用箭头函数。
- 箭头函数没有自己的arguments对象。在箭头函数中访问arguments将会引用外层函数的arguments对象,这可能导致意想不到的结果。
- 箭头函数不能用作构造函数,因为它们没有自己的this值。
4. 什么时候不可以使用箭头函数?
5. 请描述 TPC 三次握手和四次挥手
6. 防抖和节流
<!DOCTYPE html>
<html>
<head>
<title>防抖函数输入框</title>
<script>
// 定义防抖函数 debounce,用于延迟执行某个函数。
function debounce(fn, delay) {
// 创建一个变量 timerId 并初始化为 null。
let timerId = null;
// 返回一个无名函数,它使用剩余参数语法来获取任意数量的参数,并在内部实现防抖逻辑。
return function debounced(...args) {
// 如果 timerId 不是 null,则调用 clearTimeout 方法取消先前的计时器。
if (timerId !== null) {
clearTimeout(timerId);
}
// 创建一个新的计时器并将其 ID 存储在 timerId 中。该计时器将在 delay 毫秒后执行一个匿名函数。
timerId = setTimeout(() => {
// :在计时器触发时执行原始函数 fn,并将 this 上下文设置为当前作用域。
fn.apply(this, args);
// 重置 timerId 变量以便可以创建新的计时器。
timerId = null;
}, delay);
};
};
// 在页面加载完成后执行以下函数
window.onload = function () {
// 获取 ID 为 myInput 的输入框元素
const input = document.getElementById('myInput');
// 处理输入事件的回调函数
function handleInput() {
console.log(input.value);
}
// 创建防抖函数
const debouncedHandleInput = debounce(handleInput, 300);
// 将防抖函数绑定到输入框的输入事件上
input.addEventListener('input', debouncedHandleInput);
}
</script>
</head>
<body>
<input type="text" id="myInput">
</body>
</html>
7. for in 和 for of 有什么区别?
8. for await …of 有什么作用?
-
for await…of 是用于异步迭代器的语法。异步迭代器是一种特殊类型的迭代器,它支持异步操作并返回 Promise 对象。
-
在传统的同步迭代中,我们可以使用 for…of 循环来遍历一个可迭代对象,例如数组或字符串。但对于异步操作,我们需要等待 Promise 对象解决之后才能继续执行下一步操作,因此需要使用 await 关键字。而 for await…of 语法正是为了简化这一过程而设计的。
function createPromise(val) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(val)
}, 1000)
})
}
(async function () {
const p1 = createPromise(100)
const p2 = createPromise(200)
const p3 = createPromise(300)
const list = [p1, p2, p3]
// 一秒后,100,200,300 一次性展示出来
// Promise.all(list).then(res => console.log(res))
// Promise.all是api形式 , for await ...of是循环形式
// for await ...of 就是 Promise.all一个代替品
for await (let res of list) {
console.log(res);// 一秒后,100,200,300 一次性展示出来
}
})()
<script>
function createPromise(val) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(val)
}, 1000)
})
}
(async function () {
const res1 = await createPromise(100)
console.log(res1);
const res2 = await createPromise(200)
console.log(res2);
const res3 = await createPromise(300)
console.log(res3);
// 依次打印 100 200 300
const arr = [10, 20, 30]
for await (let num of arr) {
const res = await createPromise(num)
console.log(res);
} // 依次打印 10 20 30
})()
</script>
9. offsetHeight 、clientHeight、 ScrollHeight 有什么区别?
offsetHeight, clientHeight, and scrollHeight是用于获取元素高度的三个不同属性。
-
offsetHeight
属性返回一个元素在垂直方向上占用的空间大小,包括该元素的高度、(可见的)水平滚动条的高度、上下边框的高度。它包含了该元素在屏幕上所占的所有空间,即使这些空间是不可见的或被滚动条遮挡着的。 -
clientHeight
属性返回的是一个元素内部的高度,包括该元素的内边距(padding)的高度,但不包括水平滚动条、边框(border)和外边距(margin)。 -
scrollHeight
属性返回的是元素的内容高度,包括那些因为溢出导致显示不出来的部分。如果元素没有溢出,则scrollHeight会等于clientHeight。
需要注意的是,clientHeight和scrollHeight的值都可以受到CSS样式的影响。例如,如果一个元素的overflow属性设置为hidden,则其scrollHeight将小于clientHeight,因为部分内容被隐藏了。
10. HTMLCollection 和 NodeList 区别?
11. vue中 computed 和 watch 的区别?
-
在Vue中,计算属性(computed)和侦听属性(watch)都是用于监控数据变化的。
-
计算属性是基于它们的依赖进行缓存的。只有当一个计算属性的相关依赖发生改变时,才会重新求值。这样可以避免不必要的计算,提高应用程序的性能。计算属性通常用于派生出一些需要动态计算的值。
例如:
data: {
firstName: 'John',
lastName: 'Doe'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
在这个例子中,fullName 是一个计算属性,它依赖于 firstName 和 lastName 这两个属性。每当这两个属性中的任何一个发生变化时,fullName 都将被重新计算。
- 侦听属性是用于监测某个属性的变化并执行相应的操作。当需要在数据变化时执行异步或开销较大的操作时,使用侦听属性比计算属性更合适。
例如:
data: {
message: 'Hello, Vue.js!'
},
watch: {
message: function (newMessage, oldMessage) {
console.log('New message: ', newMessage, ' Old message: ', oldMessage)
}
}
在这个例子中,message 是一个数据属性,watch 监听 message 的变化,并在 message 发生变化时执行回调函数。
综上所述,计算属性和侦听属性都是用于监控数据的变化,但它们有不同的应用场景。计算属性适合派生出需要动态计算的值,而侦听属性适合在数据变化时执行异步或开销较大的操作。
12. vue2组件通讯方式有哪些?分别举例说明?
- 在 Vue2 中,组件之间有以下几种通信方式:
Props
和Events
:父组件通过props
向子组件传递数据,子组件通过events
向父组件发送消息。
适用于父子组件通讯
例如,父组件向子组件传递一个名字:
<template>
<child-component :name="parentName" @send-msg="handleChildMsg"></child-component>
</template>
<script>
export default {
data() {
return {
parentName: 'John'
}
},
methods: {
handleChildMsg(msg) {
console.log('接收子组件的信息', msg)
}
}
}
</script>
- 在子组件中接收
name
属性,并且当按钮被点击时,通过emit
发送消息给父组件:
<template>
<div>
<p>Hello, {
{ name }}!</p>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
export default {
props: ['name'],
methods: {
sendMessage() {
this.$emit('send-msg', 'Hello from child!')
}
}
}
</script>
- 通过 $ emit 和 on:非父子组件之间的通信可以使用事件总线来实现。
利用Vue实例作为事件中心,emit 触发事件,$on 监听事件,并执行回调函数。
适用于两个不相干的组件通讯
让我们创建一个名为EventBus.js的新文件,并在其中导入Vue并创建新的Vue实例作为事件总线:
import Vue from 'vue';
export const EventBus = new Vue();
例如,在一个页面中,有两个组件需要进行通信:
<template>
<div>
<first-component></first-component>
<second-component></second-component>
</div>
</template>
<script>
import EventBus from './event-bus'
export default {
methods: {
handleEvent(msg) {
console.log('received message:', msg)
}
},
mounted() {
EventBus.$on('send-msg', this.handleEvent)
},
beforeDestroy() {
EventBus.$off('send-msg', this.handleEvent);
}
}
</script>
在 first-component 组件中,通过 $emit 发送消息:
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
import EventBus from './event-bus'
export default {
methods: {
sendMessage() {
EventBus.$emit('send-msg', 'Hello from first component!')
}
}
}
</script>
在 second-component 中同样可以通过 $emit 发送消息。
- Vuex:Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。通过 store 对象来实现组件之间的通信。
例如,在一个购物车应用中,多个组件需要共享购物车状态:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
cart: []
},
mutations: {
addToCart(state, product) {
state.cart.push(product)
}
}
})
export default store
在组件中使用 $store 来访问 store 对象:
<template>
<div>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script>
export default {
methods: {
addToCart() {
this.$store.commit('addToCart', {
name: 'Product A', price: 100 })
}
}
}
</script>
通过在其他组件中也使用 $store 来访问 store 对象,就可以实现共享状态。
13. vue3通讯方式,如下。
1. 在Vue 3中,我们可以使用 event-emitter库
来实现自定义事件的发布和订阅,实现组件之间的通信。
- 下面是一个简单的示例:
// eventBus.js
import ee from 'event-emitter'
const event = ee()
export default event
<template>
<div>
<h2>Parent Component</h2>
<button @click="emitCustomEvent">发出自定义事件</button>
<child-component />
</div>
</template>
<script>
import {
reactive } from 'vue'
import {
emitter } from '../eventBus'
import ChildComponent from './ChildComponent.vue'
export default {
components: {
// 注册组件
ChildComponent,
},
setup() {
const state = reactive({
message: 'Hello from Parent',
})
const emitCustomEvent = () => {
emitter.emit('custom-event', {
data: '自定义事件数据' })
}
return {
state,
emitCustomEvent,
}
},
}
</script>
在这个示例中,我们首先导入了一个名为emitter的EventEmitter实例,并将其用于发布自定义事件。接下来,在父组件的setup()
函数中,我们创建了一个响应式对象state,并定义了一个 emitCustomEvent()
方法,在该方法中使用emitter.emit()
方法触发了一个名为custom-event
的自定义事件,并且传递了一个包含数据的对象。
接下来,我们创建了一个子组件,并在子组件中监听了这个自定义事件:
<template>
<div>
<h2>Child Component</h2>
<p>{
{
message }}</p>
</div>
</template>
<script>
import {
reactive, onMounted ,onBeforeUnmount} from 'vue'
import {
emitter } from '../eventBus'
export default {
setup() {
const state = reactive({
message: '',
})
const handleCustomEvent = (data) => {
console.log('接收数据 in child:', data)
state.message = data.data
}
onMounted(() => {
emitter.on('custom-event', handleCustomEvent)
})
onBeforeUnmount(() => {
emitter.off('custom-event', handleCustomEvent)
})
return {
state,
}
},
}
</script>
在子组件的setup()
函数中,我们定义了一个响应式对象state
,并且在子组件挂载时使用emitter.on()
方法监听了custom-event
自定义事件,并在该事件被触发时调用了一个名为handleCustomEvent()
的方法。在这个方法中,我们首先打印了传递过来的数据,并将数据设置到了state.message
属性上。
2. 使用 $attrs
传递数据
在 Vue 3 中,可以通过 $attrs 属性将父组件中未声明为 props 的属性传递给子组件
$attrs是 props 和 emits 的后补
Level1组件如下:
<template>
<p>Level1</p>
<Level2 :a="a" :b="b" :c="c" @getA="getA" @getB="getB" @getC="getC"></Level2>
</template>
<script>
import Level2 from './Level2'
export default {
name: 'Level1',
components: {
Level2 },
data() {
return {
a: 'aaa',
b: 'bbb',
c: 'ccc'
}
},
methods: {
getA() {
return this.a
},
getB() {
return this.b
},
getC() {
return this.c
}
}
}
</script>
Level2组件如下:
<template>
<p>Level2</p>
<Level3 :x="x" :y="y" :z="z" @getX="getX" @getY="getY" @getZ="getZ" v-bind="$attrs"></Level3>
</template>
<script>
import Level3 from './Level3'
export default {
name: 'Level2',
components: {
Level3 },
props: ['a'],
emits: ['getA'],
data() {
return {
x: 'xxx',
y: 'yyy',
z: 'zzz'
}
},
methods: {
getX() {
return this.x
},
getY() {
return this.y
},
getZ() {
return this.z
}
},
created() {
console.log(Object.keys(this.$attrs));
}
}
</script>
这里使用了 inheritAttrs: false 将默认行为禁用掉,因此只有在 props 中定义的属性才会被接收。
如果你想要将 $attrs 中的所有属性都显式地声明为 props,可以使用 v-bind=" $attrs " 将其传递给子组件。
Level3组件如下:
<template>
<p>Level3</p>
</template>
<script>
export default {
name: 'Level3',
props: ['x'],
emits: ['getX'],
inheritAttrs: false,
data() {
return {
}
},
created() {
console.log('level3', Object.keys(this.$attrs));
},
mounted(){
console.log(this.$parent.getX())
}
}
</script>
this.$parent.getX()
可以直接获取Level2中的getX() 方法,也就是 $parent可以获取父组件的方法,控制台输出xxx。
- 可以通过 $parent获取父组件,可以通过 $refs获取子组件
<template>
<div>
<child-component ref="child"></child-component>
<button @click="handleClick">调用子组件方法</button>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
methods: {
handleClick() {
this.$refs.child.sayHello();
}
}
}
</script>
当使用$refs
时,我们需要在子组件上添加ref属性,该属性的值应与我们在父组件中定义的引用名相同。因此,在子组件模板中,我们可以这样添加ref
属性:
<template>
<div ref="child">
<!-- 子组件内容 -->
</div>
</template>
<script>
export default {
methods: {
sayHello() {
console.log('Hello from child component!');
}
}
}
</script>
3. provide和inject是一对用于跨组件传递数据的API。
-
provide定义了一个对象,其中包含要传递给子组件的属性或方法。inject可以在子组件中接收这些属性或方法。
-
使用provide和inject可以避免使用全局事件总线或Vuex等状态管理库来共享数据。它还可以更方便地将数据从父组件传递给多个嵌套子组件。
看以下案例
在父组件中,例如App.vue,你可以像这样使用provide:
provide() {
return {
data: this.data,
method: this.method
}
}
然后,在子组件中,你可以像这样使用inject:
inject: ['data', 'method']
现在,子组件就可以使用父组件提供的数据和方法,而不需要通过props或事件来传递。
4. 再看一个稍微复杂点的,相信更好理解。
- 在 App.vue 文件中,我们使用
provide
来提供一个名为 “theme” 的数据,这个数据是一个字符串类型的变量,它的值为 “light”。同时也包含了一个名为 “changeTheme” 的方法,用来改变主题。
<template>
<div :class="currentTheme">
<h1>My App</h1>
<button @click="changeTheme">改变主题颜色</button>
<child-component></child-component>
</div>
</template>
<script>
import {
ref, provide } from 'vue'
import ChildComponent from './ChildComponent.vue'
export default {
components: {
ChildComponent
},
setup() {
const currentTheme = ref('light')
function changeTheme() {
currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light'
}
provide('theme', currentTheme)
provide('changeTheme', changeTheme)
return {
currentTheme,
changeTheme,
}
},
}
</script>
在 ChildComponent.vue 文件中,我们使用 inject 来注入父组件提供的 "theme"
数据和 "changeTheme"
方法,并将它们分别存储在名为"currentTheme"
和"changeTheme"
的变量中。
<template>
<div :class="currentTheme">
<p>This is the child component</p>
</div>
</template>
<script>
import {
inject } from 'vue'
export default {
setup() {
const currentTheme = inject('theme')
const changeTheme = inject('changeTheme')
return {
currentTheme,
changeTheme,
}
},
}
</script>
这样,通过 provide
和 inject
,我们就可以在父组件和子组件之间传递数据和方法了。在本例中,点击按钮会改变主题,同时在子组件中也能看到主题的变化效果。
14. vuex中 mutaiton 和 actiond 的区别?
- 在 Vuex 中,mutation 和 action 都用于管理应用程序状态的变化。它们之间的主要区别是:
- Mutation 是同步操作,而 Action 是异步操作。Mutation 可以直接修改状态,而 Action 不能修改状态,需要提交 Mutation 来修改状态。
- Mutation 用于更新状态,通常被组件中的方法调用,直接更新应用程序的状态;Action 则可以包含任意异步操作,例如 API 调用、延迟操作等,并在完成后提交 Mutation 更新状态。
- Mutation 必须是纯函数,即不依赖外部状态或产生副作用;而 Action 可以执行异步逻辑并且不必是纯函数。
因此,当我们需要执行异步操作或需要根据其他条件来执行状态变更时,应该使用 Action。而当我们需要直接更新状态时,应该使用 Mutation。
- 通常情况下,将设置Token放在Action中比较合适。这是因为在处理身份验证、登录等操作时,我们需要进行异步处理来与后台服务器进行通信,并在响应返回后更新状态中的token。
例如:
// 定义 credentials 代表用户的登录凭证
// 包括用户名和密码等信息。
const credentials = {
username: 'your_username',
password: 'your_password'
};
actions: {
// 通过使用 commit 方法来触发 mutation
login({
commit }, credentials) {
return axios.post('/api/login', credentials)
.then(response => {
const token = response.data.token;
commit('setToken', token);
localStorage.setItem('token', token); // 将token存储在本地
})
.catch(error => {
console.log(error);
});
}
},
mutations: {
// 修改 state中 token的状态
setToken(state, token) {
state.token = token;
}
}
15. JS 严格模式有什么特点?
- JavaScript 严格模式(strict mode)是 ECMAScript 5 引入的一种特殊模式,它可以让 JavaScript 的编写和执行更加严格,减少错误和不安全的行为。以下是严格模式的一些特点:
-
变量必须先声明后使用。在严格模式下,如果使用未声明的变量,会抛出
ReferenceError
错误。 -
函数内部的
this
值不能指向全局对象。在严格模式下,如果在函数内部没有正确地设置this
,则其值将为undefined
。
<script>
'use strict'
function fn(){
console.log('this',this) // undefined
} fn()
</script>
-
禁止删除不可删除的属性。在严格模式下,调用
delete
操作符尝试删除不可删除的属性时,会抛出一个错误。 -
禁止修改只读属性。在严格模式下,调用
Object.defineProperty()
方法尝试修改只读属性时,会抛出一个错误。 -
禁止使用八进制字面量。在严格模式下,数字前缀为 0 的字面量被视为无效的,并抛出一个错误。
-
在
eval()
中声明的变量和函数不会影响到当前作用域。在严格模式下,使用eval()
运行代码时,它创建的变量和函数不会污染当前作用域。
<script>
'use strict'
var x = 10;
eval(`var x = 20; console.log('in eval', x);`)
// eval有自己的作用域,上面代码打印 in eval 20
console.log('out eval', x); // 打印 out eval 10
</script>
- 禁止使用 with 语句。在严格模式下,使用 with 语句会抛出一个错误。
<script>
'use strict'
const obj = {
x:100,y:200}
with(obj){
console.log(x,y) // 会报错,不开启严格模式,打印100 200
}
</script>
- 函数的参数名不能重复。在严格模式下,函数的参数名不能重复,否则会抛出一个错误。
总之,JavaScript 严格模式可以使开发者写出更加规范、可靠、安全的代码,并减少一些常见的错误和不安全的行为。建议在编写 JavaScript 代码时启用严格模式。
16. HTTP 跨域请求时为何要发送 options 请求?
- jsonp是一种跨域数据传输的方式,它允许在客户端和服务端之间进行数据交互。
- 其原理是利用script标签可以跨域加载资源的特性,将需要获取的数据作为参数传递给服务端,服务端返回一段JavaScript代码并执行,这段代码会调用一个回调函数并将数据作为参数传入该函数中。
- 使用window对象来定义全局回调函数存在一些问题。首先,如果多个页面同时使用相同的回调函数名称,就会出现命名冲突的问题。其次,如果回调函数中的代码有错误,可能会导致整个页面崩溃。
- 因此,更好的做法是使用一个独立的命名空间来定义回调函数,例如:
var myNamespace = {
};
myNamespace.myCallback = function(data) {
console.log('Received data:', data);
}
var script = document.createElement('script');
script.src = 'https://example.com/data?callback=myNamespace.myCallback';
document.head.appendChild(script);