你们为什么要选择vue.js呢,它给你们解决了什么问题?
生态丰富,学习成本低,市场上拥有大量成熟的稳定的基于vue.js的ui框架及组件,拿来即用实现快速开发。
vue的生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程。
例如:从开始创建、初始化数据、编译模板、挂载Dom、数据变化时更新DOM、卸载等一系列过程。
我们称 这一系列的过程 就是Vue的生命周期。
通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
3.0组合式和选项式区别
beforeCreate -> use setup() created -> use setup() beforeMount -> onBeforeMount (注册一个钩子,在组件被挂载之前被调用) mounted -> onMounted (注册一个回调函数,在组件挂载完成后执行) beforeUpdate -> onBeforeUpdate (注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用) updated -> onUpdated (注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用) beforeUnmount -> onBeforeUnmount (注册一个钩子,在组件实例被卸载之前调用) unmounted -> onUnmounted (注册一个回调函数,在组件实例被卸载之后调用)
v-if 和 v-show有什么区别?
v-if和v-show的实现方式不同
v-if是一种条件渲染指令,它会根据表达式的值来插入或删除元素。当表达式的值为真时,元素会被插入到DOM中,否则会从DOM中删除。
v-show是一种简单的显示/隐藏指令,它会根据表达式的值来显示或隐藏元素。当表达式的值为真时,元素会被显示,否则会被隐藏。
v-if和v-show的性能不同
由于v-if的工作原理是从DOM中添加或删除元素,因此它对于频繁切换的元素具有较高的开销。每次切换时,v-if都需要重新渲染元素及其所有子元素。
v-show则不会从DOM中删除元素,而是使用CSS的display属性来控制元素的显示或隐藏。因此,v-show对于频繁切换的元素具有更好的性能,因为元素的渲染不需要重复进行。
v-if和v-show的适用场景不同
v-if适用于只有在满足特定条件时才需要渲染的元素,因为它可以节省不必要的DOM元素和子元素的渲染开销。
v-show适用于需要在不同的状态之间切换的元素,因为它可以避免在切换时重新渲染元素及其所有子元素。
v-if和v-show的语法不同
v-if的语法是在元素上添加v-if属性,并将条件表达式作为属性值:<template> <div> <p v-if=”show”>显示的内容</p> </div></template>
v-show的语法是在元素上添加v-show属性,并将条件表达式作为属性值:<template> <div> <p v-show=”show”>显示的内容</p> </div></template>
vue.js中组件之间是如何通信的?
一下使用<script setup />的编写方式,比options API更自由。有七种方法。
- props
- emit
- v-model
- refs
- provide/inject
- eventBus
- vuex/pinia
1、Props
props 是 Vue 中最常见的父子通信方式,使用起来也比较简单。根据上面的demo,我们在父组件中定义了数据和对数据的操作,子组件只渲染一个列表。父组件代码如下:
<template>
<!-- child component -->
<child-components :list="list"></child-components>
<!-- parent component -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件只需要渲染父组件传递的值。
代码如下:
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in props.list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
</script>
2、Emit
Emit也是Vue中最常见的组件通信方式,用于子组件向父组件传递消息。
我们在父组件中定义列表,子组件只需要传递添加的值。
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleSubmit" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const value = ref('')
const emits = defineEmits(['add'])
const handleSubmit = () => {
emits('add', value.value)
value.value = ''
}
</script>
点击子组件中的【添加】按钮后,我们会发出一个自定义事件,并将添加的值作为参数传递给父组件。
父组件代码如下:
<template>
<!-- parent component -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- child component -->
<child-components @add="handleAdd"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
// event handling function triggered by add
const handleAdd = value => {
list.value.push(value)
}
</script>
在父组件中,只需要监听子组件的自定义事件,然后执行相应的添加逻辑即可。
3、v-model
v-model 是 Vue 中一个优秀的语法糖,比如下面的代码。
<ChildComponent v-model:title="pageTitle" />
这是以下代码的简写形式
<ChildComponent :title=”pageTitle” @update:title=”pageTitle = $event” />
这确实容易了很多。现在我们将使用 v-model 来实现上面的示例。
子组件
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
const emits = defineEmits(['update:list'])
// Add action
const handleAdd = () => {
const arr = props.list
arr.push(value.value)
emits('update:list', arr)
value.value = ''
}
</script>
在子组件中,我们先定义props和emits,添加完成后再发出指定的事件。
注意:update:*是Vue中固定的写法,*代表props中的一个属性名。
在父组件中使用比较简单,代码如下:
<template>
<!-- parent component -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- child component -->
<child-components v-model:list="list"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
</script>
4、Refs
使用API选项时,我们可以通过this.$refs.name获取指定的元素或组件,但在组合API中不行。如果我们想通过ref获取,需要定义一个同名的Ref对象,在组件挂载后可以访问。示例代码如下:
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in childRefs?.list" :key="i">
{{ i }}
</li>
</ul>
<!-- The value of the child component ref is the same as that in the <script> -->
<child-components ref="childRefs"></child-components>
<!-- parent component -->
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const childRefs = ref(null)
</script>
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
defineExpose({ list })
</script>
注意:默认情况下,setup 组件是关闭的,通过模板 ref 获取组件的公共实例。如果需要公开,需要通过defineExpose API 公开。
5、provide/inject
provide/inject是 Vue 中提供的一对 API。无论层级多深,API 都可以实现父组件到子组件的数据传递。示例代码如下所示:父组件
<template>
<!-- child component -->
<child-components></child-components>
<!-- parent component -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, provide } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// Provide data to child components.
provide('list', list.value)
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { inject } from 'vue'
// Accept data provided by parent component
const list = inject('list')
</script>
注意:使用 provide 进行数据传输时,尽量使用 readonly 封装数据,避免子组件修改父组件传递的数据。
6、eventBus
Vue 3 中移除了 eventBus,但可以借助第三方工具来完成。Vue 官方推荐使用 mitt 或 tiny-emitter。在大多数情况下,不建议使用全局事件总线来实现组件通信。虽然比较简单粗暴,但是维护事件总线从长远来看是个大问题,这里就不解释了。有关详细信息,您可以阅读特定工具的文档。
7、vuex/pinia
Vuex 和 Pinia 是 Vue 3 中的状态管理工具,使用这两个工具可以轻松实现组件通信。由于这两个工具都比较强大,这里就不一一展示了。有关详细信息,请参阅文档。
vue中数据双向绑定原理了解吗?
Vue 3.0中的响应式系统依赖于ES6的Proxy对象,通过对数据进行代理,实现数据的监听和数据更新时的通知。
JS中的 ==和===有什么区别?
= = 和 === 都是用来判断相等的,区别是:相等的程度的深浅。
1.= = 判断相等的程度较浅,只判断数值而不判断数据类型,换言之 ,用= = 作比较的时候可以自动为我们转换数据的类型;
2.=== 判断相等的程度比 = =深,即判断数值,也判断数据类型,不可以自动转换数据的类型。
1、= =(判定值相等)
let a = 1; //数字类型let b = ‘1’; //字符类型console.log(a==b);123
结果:true
2、=== (全等,判定值和类型都相等)
let a = 1; //数字类型let b = ‘1’;//字符类型console.log(a===b);123
结果:false
注:= = 会自动转换数据的类型 ,所以有的时候会产生一些问题,例如:
let a = 1;let b = true;console.log(a==b);//结果是true123let a = 0;let b = false;console.log(a==b);//结果是true123let a = null;let b = undefined;console.log(a==b);//结果是true123
小结:=== 更加严谨,建议使用 ===
JS中的深拷贝和浅拷贝
浅拷贝与深拷贝的定义
对于引用类型的数据(数组或对象),在拷贝数据之后,对于新的数据所做的改变不会影响到之前的数据,该拷贝则被称为深拷贝,否则是浅拷贝。对于深拷贝,会重新创建一个数组或对象,与之前的地址不一样。以下是对于不同情况的分析。
基本数据类型的赋值
let a = 5;
let b = a;
以上是基本数据类型的赋值,不属于浅拷贝与深拷贝的讨论范畴
引用数据类型直接赋值
let arr = [1,2,3];
let newArr = arr;
newArr.push(4);
console.log(arr,newArr);
/*运行结果
[1,2,3,4]
[1,2,3,4]
*/
可以看到对新数组的操作也影响到了旧数组,这属于浅拷贝。
引用数据类型解构赋值(扩展运算符...
)
let arr = [1,2,3];
let newArr = [...arr];
newArr.push(4);
console.log(arr,newArr);
/*运行结果
[1,2,3]
[1,2,3,4]
*/
可以看到修改新数组没有影响到旧数组,那么这是深拷贝吗。先持保留意见。
多维数组与较复杂的对象(扩展运算符...
)
let arr = [1,2,3,[4,5]];
let newArr = [...arr];
newArr[3].push(6);
console.log(arr,newArr);
/*运行结果
[1,2,3,[4,5,6]]
[1,2,3,[4,5,6]]
*/
可以看到同样是解构赋值,这次旧数组也产生了变化,这是浅拷贝。所以对于解构赋值,在一维数组中可以勉强看做是深拷贝,但不能说解构赋值就是深拷贝。
如何实现深拷贝
深拷贝不能影响之前的数据,而基本数据类型的赋值不会影响之前的数据。那么我们应该把数组或对象全部拆成一个一个的基本数据类型的数据,再按照原本的结构赋值给新的数组或对象。
JSON.parse(JSON.stringify(obj))
通过JSON方法进行深拷贝,利用JSON.stringify(obj)
可以将js对象序列化(转化为JSON字符串),然后再用JSON.parse()
将JSON字符串反序列化为js对象。
但是使用这个方法需要注意以下问题。
- 如果原对象中含有时间
Date
对象,拷贝后的对象中时间对象会变成字符串String
,而不再是时间对象 - 如果原对象中含有
RegExp
、Error
对象,则结果会是空对象 - 如果原对象含有函数
function
,undefined
,则结果会丢失函数function
和undefined
- 如果原对象含有
NaN
、Infinity
和-Infinity
,结果会变成null
- 如果原对象中有构造函数
constructor
生成的对象,那么结果会丢失对象的构造函数constructor
Object.assign(target,source)
Object.assign()可以实现对对象属性值的拷贝。它用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。参数target代表目标对象,source代表原对象,原对象可以是多个对象。
利用Object.assign()进行深拷贝时,由于Object.assign()拷贝的是属性值,假如源对象的属性值是一个对象的引用,那么它也只指向那个引用的地址。这样拷贝过来的对象会影响之前的对象,属于是浅拷贝。这个方法和之前提到的扩展运算符…有相通之处,以下两段代码等价。
let target = Object.assign(target,source)
let target = {...source}
当然,利用他们进行深拷贝都会遇到上面提到的问题。
递归遍历对象的所有属性(标准的深拷贝)
定义一个函数deepClone(),他有一个参数source,返回一个数组或对象target。首先判断一下接收到的参数是一个数组还是对象,这关系到我们要返回一个数组还是对象。我们可以用constructor来达成这一目的,使用三目运算符?:,给要返回的值target 赋值。const target = source.constructor === Array ? [] : {}。紧接着,我们使用for in循环来遍历source,用变量keys来参与循环,并判断当前遍历的对象是否有这个属性hasOwnProperty(),有值才做处理,没有直接跳过。这里的source[keys]的数据类型有两种不同的取值,一是基本数据类型,二是引用数据类型,而引用数据类型又分为对象’object’、数组’array’。对于基本数据类型,可以直接赋值给target,对于引用数据类型,需要进行递归,将source[keys]继续传给下一个deepClone()处理,因为递归的deepClone()中会判断是对象还是数组,所以这里不需要提前判断。
以下是完整代码:
function deepClone(source){
const target = source.constructor === Array ? [] : {}
for (const keys in source) {
if (source.hasOwnProperty(keys)) {
if(source[keys]&&typeof source[keys] === 'object'){
target[keys] = deepClone(source[keys])
}else{
target[keys] = source[keys]
}
}
}
return target
}
带入数据进行测试
let a = {
a:123,
b:'123',
c:[1,2,3],
d:{
aa:123,
bb:function cc(){
console.log('dd');
}
}
}
let b = deepClone(a)
console.log(a,b);
b.d.bb()
/*运行结果
{
a: 123,
b: '123',
c: [ 1, 2, 3 ],
d: { aa: 123, bb: [Function: cc] }
}
{
a: 123,
b: '123',
c: [ 1, 2, 3 ],
d: { aa: 123, bb: [Function: cc] }
}
dd
*/
什么是闭包?闭包解决了什么问题?闭包会导致什么问题呢?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包的特点
- 1.让外部访问函数内部变量变成可能
- 2.变量会常驻在内存中
- 3.可以避免使用全局变量,防止全局变量污染;
闭包的好处和坏处
- 好处:可以读取其他函数内部的变量,并将其一直保存在内存中。
- 坏处:可能会造成内存泄漏或溢出。
闭包有两个常用的用途
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
之前有没有解决过跨域问题?在你们项目里。当时是怎么解决的?
跨域问题其实就是浏览器的同源策略所导致的
只有当 protocol(协议)、domain(域名)、port(端口)三者一致才是同源。
cors、nginx反向代理 // 待补充