`为避免内存泄漏,需要销毁监听的自定义事件,有两种解决方案:`
(1) 在组件的`beforeDestroy`钩子函数中销毁监听的自定义事件:
beforeDestroy() {
//销毁监听事件
this.$bus.off("addProduct");
}
(2) 每次$on之前$off需要销毁的事件名称;
e. 页面的路由传参
2. 父子组件之间通讯:
a. 父组件使用props
向子组件传值, 子组件定义props
接收父组件传递过来的值。特别注意:
如果props
是基本数据类型
, 那么在子组件中,不能直接修改父组件传递过来的props
;
b. 子组件使用$emit
向父组件传递事件和值,父组件使用@事件名
接收事件和参数
1. 父子组件使用`$emit`和`@自定义事件`传递方法:
(1) `父组件中的方法按序接收传递过来的参数`
子组件传递多个参数的情况:
this.$emit('test', 1, 2, 3)
父组件接收子组件的自定义事件:
@test="test"
test(a, b, c) {
console.log('我是接收过来的参数', a, b, c)
}
`或`
test(...params) {
console.log('我是接收过来的参数', params[0], params[1], params[2])
}
(2) `父组件使用函数中内置的arguments伪数组(且必须为这个内置参数),接收传递过来的参数`
this.$emit('test', 1, 2, 3)
@test="test(arguments)"
test(params) {
console.log('我是接收过来的参数', params[0], params[1], params[2])
}
(3) `使用对象的方式组装数据`
this.$emit('test', { age, sex, city })
@test="test"
test(params) {
console.log('我是接收过来的参数', params.age, params.sex, params.city)
}
2. `$emit`的扩展: 使用`$on监听本组件的自定义事件`, 后文会讲到可以`使用$once只监听一次本组件的自定义事件`
mounted() {
`// 在钩子函数中定义了一个方法,用于closeModal调用时再去执行`
`// 至于$on调用的方法和父组件从子组件接收来的自定义方法执行的快慢就看它们的执行机制
(同步状态下,处理相同条件,父组件更快,一方异步一方同步的状态下,同步的那方先执行,都是异步看谁先执行完)`
this.$on('closeModal',res=>{
console.log(res);
})
},
destoryed() {
`// 使用$off移除事件监听
1)如果没有提供参数,则移除所有的事件监听器;
2)如果只提供了事件,则移除该事件所有的监听器;
3)如果同时提供了事件与回调,则只移除这个回调的监听器。
`
this.$off("closeModal");
},
closeModal(){
this.$emit('closeModal')
}
3. `$emit`的扩展:
`this.$emit('update:visible',false)`, 使用双向绑定的`语法糖`,在父组件中使用`.sync`对传入的`props`进行
双向绑定,更新父组`visible`的`prop`值;
c. 父组件使用this.$refs.子组件的ref名称
获取子组件的vue实例
,this.$refs.子组件的ref名称.$el
是获取组件的dom
元素;
d. 父组件使用this.$children
(包含所有子组件
(不包含孙子组件)的 VueComponent 对象数组
) 获取子组件的数据,例如this.$children[0].someMethod()
执行子组件的方法。对于子组件,则直接使用this.$parent
获取父组件的值。
e. 插槽
3. 祖先跨级通讯:
a. 祖先组件使用provide
返回需要传递的参数,后代组件使用inject
接收参数
// 祖先组件
provide() {
return {
`// keyName: { name: this.name }, // value 是对象才能实现响应式,也就是引用类型`
keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]
`// keyName: 'test' value 如果是基本类型,就无法实现响应式`
}
},
data() {
return {
name:'张三'
}
},
methods: {
changeValue(){
this.name = '改变后的名字-李四'
}
}
// 后代组件
inject:['keyName']
create() {
`因为是函数,所以得执行才能获取响应式的数据,改变后的名字-李四`
const keyName = this.keyName()
}
b. 使用$attrs
和$listeners
实现祖先的跨级通讯(详见本文第一点
)
e. 插槽
1. 在子组件A中使用`<slot></slot>`进行`占位`,父组件引入子组件A,在A下`添加的内容`会`自动转移`到`插槽占位的地方`;
2. 如果`<slot></slot>`中有内容,如果在父组件不给子组件添加内容,那么就会展示插槽的默认内容;
3. 如果未给`<slot></slot>`提供名称,那么该插槽的默认名称是`default`;
4. 如果需要给`插槽`指定名称,直接对子组件使用`name`命名即可;
`<slot name="footer"></slot>`
如果在父组件中,需要给对应`插槽`添加内容,则可以使用如下三种写法:
<template v-slot:footer>
<!-- footer 插槽的内容放这里 -->
</template>
<template #footer>
<!-- footer 插槽的内容放这里 -->
</template>
<template slot="footer">
<!-- footer 插槽的内容放这里 -->
</template>
5. `插槽`是有`作用域`的,父组件中的`插槽`内容无法访问子组件的内容,除非通过`作用域插槽`的方式进行传递:
子组件:
`<slot name="footer" :row="row"></slot>`
父组件:
<template v-slot:footer="{ row }">
<!-- footer 插槽的内容放这里 -->
</template>
<template #footer="{ row }">
<!-- footer 插槽的内容放这里 -->
</template>
<template slot="header" slot-scope="{ row }">
<!-- footer 插槽的内容放这里 -->
</template>
在这里`扩展`一个代码的优化点,`v-if`可以使用`template`包裹,`语义`会更加清晰。
6. 如果组件只有一个`插槽`,则在父组件上,可以直接使用`插槽`语法,而不需要`template`标签嵌套。
7. 自定义组件内部的`$scopedSlots`记载了对应的`作用域插槽信息`,以`key(插槽名)-value(对应函数。指定key值,
执行得到Vnode数组,对应$slots,一般更推荐使用$scopedSlots)`的形式出现。因此,根据这个特性,
当`有多个具有插槽的组件`定义在`一个自定义组件中`时,可以通过`遍历的方式动态添加插槽`
<template>
<div class="el-tree-select_component">
<el-select
ref="select"
:class="[defaultText && 'has-default-text']"
v-bind="selectProps"
:value="valueTitle"
:popperClass="concatPopperClass"
>
<template v-for="(val, key) in allSlots.inputScopedSlots" #[key]>
<slot :name="key"></slot>
</template>
<el-option :label="valueTitle" :value="valueId">
<el-tree
ref="selectTree"
:data="options || []"
:node-key="props.value"
:default-expanded-keys="defaultExpandedKey"
v-bind="treeProps"
@node-click="handleNodeClick"
>
<template v-for="(val, key) in allSlots.treeScopedSlots" #[key]="scope">
<slot :name="key" v-bind="scope"></slot>
</template>
</el-tree>
</el-option>
</el-select>
<span class="default-text" v-if="defaultText"> {{ defaultText }} </span>
</div>
</template>
const INPUT_SLOT_LIST = ['prefix', 'empty']
computed: {
// 获取select组件和tree组件的插槽
allSlots({ $scopedSlots }) {
const inputScopedSlots = {}
const treeScopedSlots = {}
for (let key in $scopedSlots) {
const val = $scopedSlots[key]
if (INPUT_SLOT_LIST.includes(key)) inputScopedSlots[key] = val
else treeScopedSlots[key] = val
}
return {
inputScopedSlots,
treeScopedSlots
}
}
}
f. vueX
`在平时的项目中,为了代码看上去不是那么臃肿,一般会使用多个store文件来维护vueX,比如product.js, order.js...`
`并可以通过函数的方式拿到vueX中存储的数据`
computed: {
...mapState({
has_image_gallery: (state) => state.customizer.has_image_gallery,
library: (state) => state.myImage.library.list,
meta: (state) => state.myImage.library.pagination,
last_page: (state) => state.myImage.library.pagination.last_page
})
}
g. 指令(以回到顶部组件说明)
`自定义指令中的第三个参数vnode的context记载了组件的一些信息,这个是我们比较需要关注的`
`1. 使用自定义指令,实现回到顶部的效果:`
`添加全局公共样式:`
.scroll-top-class {
position: fixed;
bottom: 120px;
right: 30px;
opacity: 0;
height: 40px;
width: 40px;
line-height: 40px;
font-size: 30px;
text-align: center;
color: #ddd;
opacity: 0;
z-index: 2021;
cursor: pointer;
border-radius: 50%;
box-shadow: 0px 0px 8px 1px #ccc;
background-color: rgba($color: #666, $alpha: 0.5);
transition: all 1s;
}
`指令挂载方法:在有滚动条的容器上,添加v-scrollTop指令, 并提供相应的值即可。如果不提供,则使用默认值`
<div class="topic-page" v-scrollTop> </div>
`指令注册方法: 同第k点, 先install, 在本文件暴露出去。然后在main.js文件中引入,并使用vue.use(引入的名称)全局注册`
`第一种方法:直接使用binding.value判断回到顶部图标出现的位置(相对推荐)`
Vue.directive('scrollTop', {
inserted(el, binding) {
`如果未设置binding.value的值,则默认为200`
`滚动条移动超过200px的距离就显示,反之则隐藏`
if (!binding.value) binding.value = 200
el.style.scrollBehavior = 'smooth'
const backEl = document.createElement('div')
backEl.className = 'scroll-top-class el-icon-top'
el.appendChild(backEl)
backEl.addEventListener('click', () => (el.scrollTop = 0))
el.addEventListener('scroll', () => {
if (el.scrollTop >= binding.value) backEl.style.opacity = 1
else backEl.style.opacity = 0
})
}
})
`第二种方法:使用binding.value,根据滚动条的总高度所占比例,间接判断回到顶部图标出现的位置`
`(不推荐,因为在产品列表无限滚动情况下,滚动条高度是动态变化的,无法适用,而且倍数不好控制)`
// 滚动条指令
Vue.directive('scrollTop', {
inserted(el, binding) {
if (binding.value >= 1 || binding.value <= 0) return new Error('v-scrollTop的绑定值需要介于0到1之间')
`获取元素的整体高度`
const elH = el.offsetHeight
`也可以给visibilityHeight定值(不推荐,无法兼容所有需要滚动条的页面)`
let visibilityHeight = 0
if (binding.value) visibilityHeight = binding.value * elH
`阈值默认为滚动区域整体高度的0.2倍`
else visibilityHeight = 0.2 * elH
`为滚动条返回顶部添加平滑的过渡效果`
el.style.scrollBehavior = 'smooth'
const backEl = document.createElement('div')
backEl.className = 'scroll-top-class el-icon-top'
`将创建的回到顶部图标作为孩子插入到el中`
el.appendChild(backEl)
backEl.addEventListener('click', () => (el.scrollTop = 0))
el.addEventListener('scroll', () => {
if (el.scrollTop >= visibilityHeight) backEl.style.opacity = 1
else backEl.style.opacity = 0
})
}
})
`2. 自定义组件,实现回到顶部的效果:`
`使用这种方式,需要在每个有回到顶部需求的文件中引入该自定义组件,并指定高度阈值及滚动条dom容器对应的字符串`
<template>
<el-button v-show="visible" class="back" @click="backTop">top</el-button>
</template>
<script>
export default {
props: {
height: {
required: false,
type: Number,
default: 200
},
target: {
required: false,
type: String,
default: '.topic-page'
}
},
data() {
return {
container: false,
visible: false
}
},
mounted() {
this.container = document.querySelector(this.target)
if (!this.container) throw new Error('target is not existed: ' + this.target)
this.container.style.scrollBehavior = 'smooth'
`最保险的做法是,使用this.$nextTick包裹下面的代码,因为vue是异步更新机制,dom可能还未更新`
this.container.addEventListener('scroll', this.scrollToTop)
this.$once('hook:beforeDestory', () => {
this.container.removeEventListener('scroll', this.scrollToTop)
})
},
methods: {
backTop() {
this.container.scrollTo({
top: 0,
behavior: 'smooth'
})
},
scrollToTop() {
this.visible = this.container.scrollTop > this.height ? true : false
}
}
}
</script>
<style lang="scss" scoped>
.back {
position: fixed;
bottom: 100px;
right: 100px;
}
</style>
h. 使用install和use进行全局注册
`lodopPrintPdf.js`
import Vue from 'vue'
import PrintBtn from './printBtn'
import merge from 'element-ui/src/utils/merge'
export default async function lodopPrintPdf(option) {
const ExtendPrintBtn = Vue.extend(PrintBtn)
`继承打印组件并初始化vue实例`
const vm = new ExtendPrintBtn({})
`合并option,等价于Object.assign(vm, option)`
`相当于遍历添加传入vm的prop参数`
merge(vm, option)
`调用实例的方法,js动态加载完成`
return vm.printHandler()
}
复制代码
`globalConst.js`
import lodopPrintPdf from '@/components/lodopPrint/lodopPrintPdf.js'
export default {
install(Vue) {
`在vue的原型对象上挂载$lodopPrintPdf,并暴露出去`
Vue.prototype.$lodopPrintPdf = lodopPrintPdf //lodop打印pdf
}
}
`main.js`
`Vue.use的用法: 安装Vue插件。
如果插件是一个对象,必须提供 install 方法。
如果插件是一个函数,它会被作为 install 方法。`
import globalConst from '@/commons/globalConst'
Vue.use(globalConst)
复制代码
`$lodopPrintPdf方法的使用`
`传入的五个参数就是前面定义的函数所接收的option值,相当于调用打印组件,传入对应的五个props`
this.$lodopPrintPdf({
type: 'html',
printable: this.$refs.label.$el,
paperSize: [841.89, 595.28],
onSuccess: this.resetLoading,
onError: this.resetLoading
})
i. 混入 && 继承
混入: 对于具有相同逻辑的vue
文件,其实可以抽取成一个混入
,存放公共的js
代码。在使用混入
的vue
文件中,可以定义相同的变量或者方法来覆盖
混入中的变量或者方法。
继承: 相比混入
,继承
更加霸道,可以继承
整个vue
文件。同时在继承
文件中,可以添加一些额外的js
代码。如果在被继承
的组件中存在这些js
变量和方法,那么继承
组件就会覆盖这些变量和方法,如果不存在则为添加。如果在继承
组件中添加html
和css
代码,不管这些代码之前是否和被继承
组件的html
和css
代码冲突,继承
组件的html
和css
代码都会以自身代码为主,不会继承被继承
组件的html
和css
代码。
<script>
import dialog from '@/extend/components/dialog/index'
export default {
extends: dialog
}
</script>
j. $props三兄弟和inherits属性
$props:当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。
$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。
$listeners:包含了父作用域中的(不含.native 修饰器的)v-on事件监听器。
inherits属性
的作用是禁止传入的属性添加到组件的根元素上。默认为true,即将传入的属性添加到组件的根元素上。
应用:v-bind="$attrs"
和v-on="$listeners"
一般用于组件封装上,必须得绑定在组件上。v-bind="$attrs"
相当于一个展开运算符,将从父组件传来的props
且未被当前组件props
接收的prop
挂载到组件上,使得组件具有可扩展性。如果未绑定,孙子组件可以通过this.$attrs
拿到子组件的props
,但是无法拿到父组件的props
。如果要拿到父组件的props
,则需要在子组件上绑定v-bind="$attrs"
,这样孙子组件中的this.$attrs
就指向父组件的props
。
k. v-model语法糖
l. 修饰符的顺序及理解
m. render函数 && 函数式组件
n. 路由守卫
2. vue的冷知识(vue间谍
,大部分内容来自Sunshine_Lin
的掘金博客。部分内容有自己的思考和扩展, 以扩展
两字进行标注)
a. 为什么不建议v-for和v-if同时存在?
<div v-for="item in [1, 2, 3, 4, 5, 6, 7]" v-if="item !== 3">
{{item}}
</div>
`拓展:`
`vue2中的v-for优先级高于v-if, vue3则相反。首先会把7个元素都遍历出来,然后再一个个判断是否为3,并把3的dom给隐藏掉,
这样的坏处就是,渲染了无用的3节点,增加无用的dom操作,建议使用computed来解决这个问题:`
<div v-for="item in list">
{{item}}
</div>
computed() {
list() {
return [1, 2, 3, 4, 5, 6, 7].filter(item => item !== 3)
}
}
b. 为什么不建议用index做key,为什么不建议用随机数做key?
<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
{ name: '小明', id: '123' },
{ name: '小红', id: '124' },
{ name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
新旧对比
<div key="0">小明</div> <div key="0">小林</div>
<div key="1">小红</div> <div key="1">小明</div>
<div key="2">小花</div> <div key="2">小红</div>
<div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
现在我们使用id来做key,渲染为
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' }),渲染为
<div key="122">小林</div>
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
新旧对比
<div key="122">小林</div>
<div key="123">小明</div> <div key="123">小明</div>
<div key="124">小红</div> <div key="124">小红</div>
<div key="125">小花</div> <div key="125">小花</div>
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果
用index
和用随机数
都是同理,随机数
每次都在变,做不到专一性,很渣男
,也很消耗性能,所以,拒绝渣男
,选择老实人
c. 为什么data是个函数并且返回一个对象呢?
`data`之所以是一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行`data函数`并返回新的数据对象。
这样,可以避免多处调用的`数据污染`。
d. vue2的内部指令
`拓展:`
1. `v-text`的用法:
本质其实和`插值表达式`一样。不同的是,`v-text`后面必须得`赋值`,它只与`赋予的变量的值`有关。比如:
<div v-text="123"> {{ unsignedCountries }} </div>
此处只`渲染123`, 而不会渲染`计算属性`的值。
2. `v-once`:指令所绑定的`html标签`和`组件`中的变量只会渲染一次,`不会随着变量的变化而变化`,利用好这一点可以优化
e. vue2的生命周期
`拓展:`
`1. vue生命周期钩子函数的组成:`
`vue的生命周期`是一个`组件/实例`从`创建到销毁`的过程中自动执行的函数,主要分为:`创建、挂载、更新、销毁`四个模块。
`挂载`:`$el`和`$mount`都是为了将`实例化后的vue挂载`到指定的`dom元素`中,但是`$el`的优先级要高于`$mount`。
如果`实例化vue`的时候指定`el`,则`vue`将会渲染到`此el对应的dom`中,
反之,若没有指定`el`,则`vue实例`会处于一种`未挂载`的状态,此时可以通过`$mount`来手动`执行挂载`。
`2. 被keep-alive缓存的组件的生命周期:`
第一次进入,`created` -> `mounted` -> `activated`,
退出时触发`deactivated`。当再次进入时,只触发`activated`。
f. 如何设置动态class,动态style?
(1) 动态class
(对象形式):
<div :class="{ 'is-active': true, 'red': isRed }"></div>
(2) 动态class
(数组形式):
拓展: <div :class="['is-active', isRed && 'red' ]"></div>
(3) 动态style
对象(其中的css属性
必须使用驼峰命名
):
<div :style="{ color: textColor, fontSize: '18px' }"></div>
(4) 动态style
数组(使用多个对象
包裹,对象
之间的css属性
使用逗号
隔开):
<div :style="[{ color: textColor, fontSize: '18px' }, { fontWeight: '300' }]"></div>
g. 如何处理非响应式数据?
在我们的Vue开发中,会有一些数据,从始至终都未曾改变过
,这种死数据
,既然不改变
,那也就不需要对他做响应式处理
了,不然只会做一些无用功消耗性能,比如一些写死的下拉框,写死的表格数据,这些数据量大的死数据
,如果都进行响应式处理,那会消耗大量性能。
// 方法一:将数据定义在data之外
data () {
`拓展:这种非响应式的数据可以直接定义,包括在methods方法里`
this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
return {}
}
// 方法二:Object.freeze()
data () {
return {
list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
}
}
h. 父子组件的生命周期?
挂载阶段: 父beforeCreate
>父created
> 父beforeMount
> 子beforeCreate
>子created
> 子beforeMount
> 子mounted
>父mounted
扩展:
子组件更新过程: 父beforeUpdate
> 子beforeUpdate
> 子updated
> 父updated
父组件更新过程(影响到子组件): 父beforeUpdate
> 子beforeUpdate
> 子updated
> 父updated
父组件更新过程(不影响子组件): 父beforeUpdate
> 父updated
销毁过程: 父beforeDestroy
> 子beforeDestroy
> 子destroyed
> 父destroyed
h. Vue采用的是异步更新
的策略,通俗点说就是,同一事件循环内
多次修改,会统一
进行一次视图更新
,这样才会节省性能。
i. 关于props的自定义校验
props: {
num: {
default: 1,
`// type:Number, 指定props的类型`
`// required: true, 指定props是否为必填项`
## 算法
1. 冒泡排序
2. 选择排序
3. 快速排序
4. 二叉树查找: 最大值、最小值、固定值
5. 二叉树遍历
6. 二叉树的最大深度
7. 给予链表中的任一节点,把它删除掉
8. 链表倒叙
9. 如何判断一个单链表有环
![](https://img-blog.csdnimg.cn/img_convert/e740d32f9201373c3fbe3fde6360699e.webp?x-oss-process=image/format,png)
>由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
unted` >`父mounted`
**扩展:**
**子组件更新过程:** `父beforeUpdate` > `子beforeUpdate` > `子updated` > `父updated`
**父组件更新过程(影响到子组件):** `父beforeUpdate` > `子beforeUpdate` > `子updated` > `父updated`
**父组件更新过程(不影响子组件):** `父beforeUpdate` > `父updated`
**销毁过程:** `父beforeDestroy` > `子beforeDestroy` > `子destroyed` > `父destroyed`
#### h. Vue采用的是`异步更新`的策略,通俗点说就是,`同一事件循环内`多次修改,会`统一`进行一次`视图更新`,这样才会节省性能。
#### i. 关于props的自定义校验
props: {
num: {
default: 1,
// type:Number, 指定props的类型
// required: true, 指定props是否为必填项
算法
-
冒泡排序
-
选择排序
-
快速排序
-
二叉树查找: 最大值、最小值、固定值
-
二叉树遍历
-
二叉树的最大深度
-
给予链表中的任一节点,把它删除掉
-
链表倒叙
-
如何判断一个单链表有环
[外链图片转存中…(img-K59A406x-1714513414057)]
由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!