总结题
VUE
ref :是 元素的属性,用于设置在元素上
$refs :是 ref 的集合,集合里面包含了当前.vue中的所有 ref
用于获取普通元素中的 DOM 以及 子组件中方法/参数的
$el :是 用于获取组件内 DOM(包括子组件,当前.vue组件,以及父组件)
vue中扩展组件
mixin
一个组件引入了mixin,mixin里面生命周期和组件内的生命周期哪个先执行, data会不会覆盖, 方法同名会不会覆盖
mixin生命周期总在组件生命周期之前执行
mixin里面可以写data,methods,computed
mixins中的data会合并到data中,有冲突的话,data中数据覆盖mixins中的数据。
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用 例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
slot
插槽主要用于vue组件中的内容分发,也可以用于组件扩展。
子组件Child
匿名插槽
<div>
<slot>这个内容会被父组件传递的内容替换</slot>
</div>
复制代码
父组件Parent
<div>
<Child> (父组件传递的内容)</Child> // 可以传组件
</div>
复制代码
如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽。
具名插槽
<header>
<slot name="header"></slot>
<slot></slot>
</header>
<!-- old -->
<children>
<template slot="header">
<h1>Here might be a page title</h1>
</template>
- 注意:一个不带 name 的 出口会带有隐含的名字“default”。
<template slot="default">
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
</children>
<!-- new -->
<children>
<template v-slot:header>
<!-- <template #header> 具名插槽可缩写形式 -->
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
</children>
作用域插槽
Vue.js中的作用域插槽是一种特殊类型的插槽,用作一个(能被传递数据的)可重用模板,来代替已经渲染好的元素。作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容¹。
以下是Vue.js中作用域插槽的示例代码:
<!-- 子组件 -->
<template>
<div>
<slot name="content" :data="data">后备内容</slot>
// 后备内容什么是后备内容呢,一个slot有它的默认的内容,
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,
它只会在没有提供内容的时候被渲染。
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<child-component>
<template #content="slotProps"> //slotProps可以随便命名
<p>{{ slotProps.data }}</p>
</template>
</child-component>
</div>
</template>
跟其他不同,是由子组件传值给父组件展示,数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定,也就是说,作用域插槽的不同之处就在于,数据不在父组件身上,而是在子组件身上,且组件的结构和内容由父组件决定。作用域组件限定了组件内结构和数据的展示范围,以便在开发中我们可以根据一个组件而不断变换其中的内容和结构。
3、extend
使用基础Vue构造器,创建一个"子类"。参数是一个包含组件选项的对象。 就是把一个组件通过Vue.extend创造一个子类,然后再通过.$mount()来挂载, extend提供了一个能够构造组件的函数(也就是构造器)。在一些特定的应用场景(如自己构建一个复杂弹窗)下,我们使用这种函数式的构造组件的方法,会更灵活一些。 将dom插入body
document.body.appendChild(demo.$el);
以下是一个使用Vue.extend的例子:¹
```javascript
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
vue3中被移除 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。 类似,this.$message.info('xxx')就是通过这种方法创造的组件实例,再讲这个组件传到了body上,
keep-alive 是什么?
- 作用:实现组件缓存,保持组件的状态,避免反复渲染导致的性能问题。
- 工作原理:Vue.js 内部将 DOM 节点,抽象成了一个个的 VNode 节点,
keep-alive组件的缓存也是基于 VNode 节点的。它将满足条件的组件在 cache 对象中缓存起来,重新渲染的时候再将 VNode 节点从 cache 对象中取出并渲染。 - 可以设置以下属性:
①include:字符串或正则,只有名称匹配的组件会被缓存。
②exclude:字符串或正则,任何名称匹配的组件都不会被缓存。
③max:数字,最多可以缓存多少组件实例。
匹配首先检查组件的name选项,如果name选项不可用,则匹配它的局部注册名称(父组件 components选项的键值),匿名组件不能被匹配。
如果同时使用了include、exclude,那么exclude的优先级高于include。
设置了keep-alive缓存的组件,会多出两个生命周期钩子:activated、deactivated。
首次进入组件时:beforeCreate --> created --> beforeMount --> mounted --> activated --> beforeUpdate --> updated --> deactivated
再次进入组件时:activated --> beforeUpdate --> updated --> deactivated
vue.install
在 Vue.js 中,install 方法是插件的一个方法,用于定义 Vue.js 插件。当使用 Vue.use() 安装插件时,会调用插件的 install 方法。在 install 方法中,我们可以定义全局组件、指令、混入等,并将它们挂载到 Vue 的原型上,以便在其他组件中使用。
以下是一个 Vue.js 插件的 install 方法示例代码,其中第二个参数是可选的选项对象,用于传递插件的配置信息。在 install 方法中,我们可以使用这个选项对象来配置插件的行为。例如,我们可以定义一个名为 prefix 的选项,用于指定全局组件名的前缀。
export default {
install(Vue, options) {
// 使用 options 配置插件
const prefix = options && options.prefix ? options.prefix : ''
// 全局注册组件
Vue.component(`${prefix}my-component`, {
// ...
})
// 全局注册指令
Vue.directive(`${prefix}my-directive`, {
// ...
})
// 全局注册混入
Vue.mixin({
// ...
})
// 挂载到 Vue 原型上
Vue.prototype.$myMethod = function (methodOptions) {
// ...
}
}
}
父子组件的生命周期执行顺序
watch的执行顺序
如果不设置 immediate: true,watch在beforeUpdate 之后执行,如果设置了immediate属性,在beforeCreate后执行
props,methods,data以及computed的初始化都是在beforeCreated和created之间完成的
computed数据的执行是在beforeMount和mounted之间
讲讲生命周期方法有哪些,对应哪个阶段
什么阶段能发起请求
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
- 但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面loading 时间;
- ssr不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有nextTick 来访问 Dom
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。最后一次更改数据的机会
mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点
beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁(patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated 发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行善后收尾工作,比如清除计时器。
destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
activated keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
vue生命周期执行顺序
执行顺序是:created中的同步任务–mounted中的同步任务–created中的异步任务–mounted中的同步任务
只是比较他们的执行顺序的话,不用考虑太多created与mounted,他们可以理解为将created与mounted放在一起,只是created放在上面,mounted放下面的代码顺序,然后按正常执行顺序执行
只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。
前端解决跨域
待补充
虚拟DOM
待补充
watch 和computed区别
watch和computed都是以Vue的依赖追踪机制为基础 watch处理的场景是:一个数据影响多个数据 computed处理场景是:一个数据受多个数据影响 在computed中定义的每一个计算属性,都会被缓存起来,只有当计算属性里面依赖的一个或多个属性变化了,才会重新计算当前计算属性的值。
token
获取token的方式,插件cookie-util,或者document.cookie 循环,再截取=号
vue组件通讯方式
-
Props 和 Events(父子组件通信)
-
解释:
Props是父组件向子组件传递数据的方式。父组件可以将数据作为props传递给子组件,子组件通过定义props选项来接收数据。$emit事件则是子组件向父组件通信的方式,子组件可以通过触发一个自定义事件,将数据传递给父组件。
-
代码示例:
- 父组件
App.vue:
- 父组件
-
<template>
<div id="app">
<child-component :message="parentMessage" @child-event="handleChildEvent"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '这是父组件传递的数据'
};
},
methods: {
handleChildEvent(data) {
console.log('父组件接收到子组件的数据:', data);
}
}
}
</script>
- 子组件
ChildComponent.vue:
<template>
<div>
<p>{{ message }}</p>
<button @click="sendDataToParent">向父组件发送数据</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
sendDataToParent() {
this.$emit('child - event', '这是子组件发送的数据');
}
}
}
</script>
2. Event Bus(非父子组件通信)
* **解释**:
* 当组件不是父子关系时,可以使用事件总线(`Event Bus`)来进行通信。`Event Bus`是一个空的`Vue`实例,它可以作为一个事件中心,各个组件可以在这个事件中心上监听和触发事件,从而实现数据的传递。
* **代码示例**:
* 创建事件总线`event - bus.js`:
import Vue from 'vue';
export const eventBus = new Vue();
- 组件
ComponentA.vue(发送数据):
<template>
<div>
<button @click="sendData">发送数据</button>
</div>
</template>
<script>
import { eventBus } from './event - bus.js';
export default {
methods: {
sendData() {
eventBus.$emit('data - sent', '这是从ComponentA发送的数据');
}
}
}
</script>
- 组件
ComponentB.vue(接收数据):
<template>
<div>
等待接收数据...
</div>
</template>
<script>
import { eventBus } from './event - bus.js';
export default {
mounted() {
eventBus.$on('data - sent', (data) => {
console.log('ComponentB接收到的数据:', data);
});
}
}
</script>
3. Vuex(状态管理,适用于复杂的组件通信)
* **解释**:
* 对于大型应用,多个组件可能需要共享状态。`Vuex`是一个专为`Vue.js`应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并且以可预测的方式发生变化。`Vuex`有`state`(存储状态)、`mutations`(修改状态的唯一途径,同步操作)、`actions`(包含异步操作,可以提交`mutations`)和`getters`(类似于计算属性,用于获取状态)。
* **代码示例**:
* 安装`Vuex`并创建`store.js`:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
});
export default store;
- 在
main.js中引入store:
import Vue from 'vue';
import App from './App.vue';
import store from './store.js';
new Vue({
store,
render: h => h(App)
}).$mount('#app');
- 组件
SomeComponent.vue使用Vuex:
<template>
<div>
<p>当前计数: {{ count }}</p>
<button @click="increment">同步增加计数</button>
<button @click="incrementAsync">异步增加计数</button>
<p>双倍计数: {{ doubleCount }}</p>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.store.state.count;
},
doubleCount() {
return this.store.getters.doubleCount;
}
},
methods: {
increment() {
this.store.commit('increment');
},
incrementAsync() {
this.store.dispatch('incrementAsync');
}
}
}
</script>
4. provide/inject(祖先 - 后代组件通信)
* **解释**:
* `provide`选项允许一个祖先组件向其所有子孙组件提供数据,而`inject`选项允许一个后代组件接收祖先组件提供的数据。这是一种跨层级组件通信的方式,相比层层传递`props`更加方便。
* **代码示例**:
* 祖先组件`Ancestor.vue`:
<template>
<div>
<h1>祖先组件</h1>
<descendant - component></descendant - component>
</div>
</template>
<script>
import DescendantComponent from './DescendantComponent.vue';
export default {
components: {
DescendantComponent
},
provide: {
message: '这是祖先组件提供的数据'
}
}
</script>
- 后代组件
DescendantComponent.vue:
<template>
<div>
<p>后代组件接收到的数据: {{ message }}</p>
</div>
</template>
<script>
export default {
inject: ['message']
}
</script>
5. Refs(直接访问子组件实例)
* **解释**:
* `refs`可以用于在父组件中直接访问子组件实例。这使得父组件可以直接调用子组件的方法或访问子组件的数据。不过这种方式应该谨慎使用,因为它会破坏组件的封装性。
* **代码示例**:
* 父组件`Parent.vue`:
<template>
<div>
<child - component ref="child"></child - component>
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
callChildMethod() {
this.$refs.child.childMethod();
}
}
}
</script>
- 子组件
ChildComponent.vue:
<template>
<div>
子组件
</div>
</template>
<script>
export default {
methods: {
childMethod() {
console.log('子组件方法被调用');
}
}
}
</script>
v-model双向绑定
v-model是语法糖,默认情况下相当于:value和@input。使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。 父组件传值子组件接收,修改值
子组件里面的model的prop可以重新命名双向绑定的值,model里面的event可以修改emit调用方法名
.sync 和 v-model 的区别
先上结论:两者的本质都是语法糖,目的都是实现组件与外部数据的双向绑定。v-model 是 .sync的一种体现。.sync 比较灵活;v-model较单一
v-model只能有一个,(一个组件只有一个model)
个人理解,别的就是语义的区别了,prop.sync表示这个子组件会修改父组件的值,v-model表示这是个表单类型的组件。
v-model一般是表单组件,绑定的是value属性,这个值的双向绑定也不是父组件和子组件的关系,而是view和model的对应关系,因为表单组件的值的变化来自于用户输入
而sync是指父子组件之间的通信
keep-alive
两个生命周期 activated/deactivated,用来得知当前组件是否处于活跃状态。
当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated
keep-alive 提供了include和exclude两个属性,允许组件有条件的缓存
实现原理是在created的时候,将需要缓存的vnode节点放到cache中,在render时根据name再进行取出
注意 vue不建议过多使用 refs 属性
ref属性是把子组件的实例拿到父组件里面来操作,这在一定程度上破坏了组件逻辑分离的原则,强化了组件之间的耦合度
如果你要向子组件传递数据,应该直接使用props或者Vuex、Pinia这类状态管理插件
如果是要传递事件,应该是用事件总线eventbus机制,或者自定义一些小型的发布-订阅管理机制
只要父子组件是在你自己的控制范围内的,绝大多数情况下都不应该使用ref来完成业务
一般不得不使用ref的地方常见于调用第三方的组件库,对方没有暴露某些内部逻辑的接口,但由于业务需求必须要使用这些逻辑,这时候就只能通过ref拿到子组件实例然后强行调用子组件内部的函数了
虽然短时间内可以快速完成业务逻辑,然而弊端是显而易见的,因为很可能这些内部函数在某次组件库升级更新后就弃用了,或者改变了名字,参数等等,这时候你的程序就会直接报错甚至崩溃了
你使用ref的地方越多,将来需要维护的地方也就越多,你的程序潜在崩溃风险就越大
hook
this.$on一个自定义事件,this.$once一个自定义事件,但是只触发一次 vue2中有hook的概念
created() {
this.a
// 执行一大堆的其他操作
},
mounted() {
this.b
// 执行一大堆的其他操作
}
可以写成,更加简洁,也可以用来清除定时器,监听
created() {
this.a
// 执行一大堆的其他操作
this.$once((hook:mounted),()=>{
this.b
})
},
@hook绑定
再来说第二种场景吧,假如你用了第三方的 组件,想在第三方组件数据变化时进行一些操作,而这个组件正好没有提供change方法
这时候应该怎么办?当然最好是可以深入组件去修改。但是假如第三方组件又是打包过后的代码呢?痛苦的去看么?
<template>
<!--通过@hook:updated监听组件的updated生命钩子函数-->
<!--组件的所有生命周期钩子都可以通过@hook:钩子函数名 来监听触发-->
<xxx-comp @hook:updated="handleUpdateChange" />
</template>
<script>
import XxxComp from '../components/xxx-comp'
export default {
components: {
XxxComp
},
methods: {
handleUpdateChange () {
console.log('组件的updated钩子函数被触发')
}
}
}
</script>
v-if和v-for
在vue2中,v-for大于v-if,每次重新渲染会遍历整个数组,很费时间,而vue3中 v-if大于v-for,当v-if执行时,v-for还没生成会直接报错,永远不要把v-if和v-for放在同一层,可以分层放
vue优化
待补充
图片懒加载
img标签会在html渲染解析到的时候,如果解析到img src值,则浏览器会立即开启一个线程去请求该资源。 正常情况是解析到了src便发起请求
可视化的时候再渲染图片 document.documentElement.clientHeight//获取屏幕可视区域的高度 document.documentElement.scrollTop//获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离 element.offsetTop//获取元素相对于文档顶部的高度
图片过大加载慢时候
- 如果是相册之类的可以预加载,在展示当前图片的时候,就加载它的前一个和后一个图片
- 加载的时候可以先加载一个压缩率非常高的缩略图,以提高用户体验,点击再或加载到之后再查看清晰图
- 使用渐进式jpeg,会提高用户体验
echarts不均匀刻度
一、修改纵坐标的数值,formatter二、处理原数据,采用的方法是把以前100以内的数据模拟到6000以内,就是同比例放大。
修饰符
- .stop 阻止事件继续传播
- .prevent 阻止标签默认行为
- .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
- .self 只当在 event.target 是当前元素自身时触发处理函数
- .once 事件将只会触发一次
- .passive 告诉浏览器你不想阻止事件的默认行为
- .lazy 通过这个修饰符,转变为在 change 事件再同步
- .number 自动将用户的输入值转化为数值类型
- .trim 自动过滤用户输入的首尾空格
vue数组视图更新
VUE框架理解
axios
get
调用型
`axios.get('url', { params: { id: 'aaa' } }).then(res => {
console.log(res)
})`
axios型
axios({ method: 'get', url: 'url', params: { id: '111' } }).then(res => {
console.log(res)
})
post
调用型
`axios.post('url', data).then(res => {
console.log(res)
})`
axios型
axios({ method: 'get', url: 'url', data:'data' }).then(res => {
console.log(res)
})
请求拦截,响应拦截
// 请求拦截
axiosInstance.interceptors.request.use(
config => {
// 让每个请求携带自定义token
let token = ''
const cookie = document.cookie.split('; ')
for (let i = 0; i < cookie.length; i++) {
const arr = cookie[i].split('=')
if (arr[0] === 'token-oe') {
token = arr[1]
}
}
if (token) config.headers.Authorization = token
return config
},
err => {
return Promise.reject(err)
}
)
// 响应拦截
axiosInstance.interceptors.response.use(response => {
const { status, message } = response.data
// 获取状态码,可以处理token失效之类的问题
return new Promise((resolve, reject) => {
if (status === 200) {
resolve(response.data)
} else if (status === 400) {
// 清除cookie
// cookieUtil.removeToken()
router.push({
path: '/login'
})
} else {
console.log(message)
reject(response.data)
}
})
}