上一篇链接
如果有看不懂的,别硬看,直接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 300const 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 方法来触发 mutationlogin({ 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);