共计 17178 个字符,预计需要花费 43 分钟才能阅读完成。
什么是 mixin?
- Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
- 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
- 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
Vue 中 diff 算法原理
DOM
操作是十分低廉的,因而咱们须要尽量地缩小 DOM
操作。这就须要找出本次 DOM
必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用 diff 算法
vue
的diff
算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归的形式 + 双指针 (头尾都加指针)
的形式进行比拟。
简略来说,Diff 算法有以下过程
- 同级比拟,再比拟子节点(依据
key
和tag
标签名判断) - 先判断一方有子节点和一方没有子节点的状况 (如果新的
children
没有子节点,将旧的子节点移除) - 比拟都有子节点的状况(外围
diff
) - 递归比拟子节点
- 失常
Diff
两个树的工夫复杂度是O(n^3)
,但理论状况下咱们很少会进行跨层级的挪动DOM
,所以Vue
将Diff
进行了优化,从O(n^3) -> O(n)
,只有当新旧children
都为多个子节点时才须要用外围的Diff
算法进行同层级比拟。 Vue2
的外围Diff
算法采纳了双端比拟
的算法,同时从新旧children
的两端开始进行比拟,借助key
值找到可复用的节点,再进行相干操作。相比React
的Diff
算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅- 在创立
VNode
时就确定其类型,以及在mount/patch
的过程中采纳位运算来判断一个VNode
的类型,在这个根底之上再配合外围的Diff
算法,使得性能上较Vue2.x
有了晋升
vue3 中采纳最长递增子序列来实现
diff
优化
答复范例
思路
diff
算法是干什么的- 它的必要性
- 它何时执行
- 具体执行形式
- 拔高:说一下
vue3
中的优化
答复范例
Vue
中的diff
算法称为patching
算法,它由Snabbdo
m 批改而来,虚构DOM
要想转化为实在DOM
就须要通过patch
办法转换- 最后
Vue1.x
视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构DOM
和patching
算法反对,然而这样粒度过细导致Vue1.x
无奈承载较大利用;Vue 2.x
中为了升高Watcher
粒度,每个组件只有一个Watcher
与之对应,此时就须要引入patching
算法能力准确找到发生变化的中央并高效更新 vue
中diff
执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render
函数取得最新的虚构DOM
,而后执行patc
h 函数,并传入新旧两次虚构 DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM
操作patch
过程是一个递归过程,遵循深度优先、同层比拟的策略;以vue3
的patch
为例- 首先判断两个节点是否为雷同同类节点,不同则删除从新创立
- 如果单方都是文本则更新文本内容
- 如果单方都是元素节点则递归更新子元素,同时更新元素属性
-
更新子节点时又分了几种状况
- 新的子节点是文本,老的子节点是数组则清空,并设置文本;
- 新的子节点是文本,老的子节点是文本则间接更新文本;
- 新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;
- 新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节 blabla
vue3
中引入的更新策略:动态节点标记等
vdom 中 diff 算法的繁难实现
以下代码只是帮忙大家了解 diff
算法的原理和流程
- 将
vdom
转化为实在dom
:
const createElement = (vnode) => {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if(!tag) {return null;}
// 创立元素
let elem = document.createElement(tag);
// 属性
let attrName;
for (attrName in attrs) {if(attrs.hasOwnProperty(attrName)) {elem.setAttribute(attrName, attrs[attrName]);
}
}
// 子元素
children.forEach(childVnode => {
// 给 elem 增加子元素
elem.appendChild(createElement(childVnode));
})
// 返回实在的 dom 元素
return elem;
}
- 用繁难
diff
算法做更新操作
function updateChildren(vnode, newVnode) {let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach((childVnode, index) => {let newChildVNode = newChildren[index];
if(childVnode.tag === newChildVNode.tag) {
// 深层次比照, 递归过程
updateChildren(childVnode, newChildVNode);
} else {
// 替换
replaceNode(childVnode, newChildVNode);
}
})
}
</details>
Vue 子组件和父组件执行程序
加载渲染过程:
- 父组件 beforeCreate
- 父组件 created
- 父组件 beforeMount
- 子组件 beforeCreate
- 子组件 created
- 子组件 beforeMount
- 子组件 mounted
- 父组件 mounted
更新过程:
- 父组件 beforeUpdate
- 子组件 beforeUpdate
- 子组件 updated
- 父组件 updated
销毁过程:
- 父组件 beforeDestroy
- 子组件 beforeDestroy
- 子组件 destroyed
- 父组件 destoryed
v-if、v-show、v-html 的原理
- v-if 会调用 addIfCondition 办法,生成 vnode 的时候会疏忽对应节点,render 的时候就不会渲染;
- v-show 会生成 vnode,render 的时候也会渲染成实在节点,只是在 render 过程中会在节点的属性中批改 show 属性值,也就是常说的 display;
- v-html 会先移除节点下的所有节点,调用 html 办法,通过 addProp 增加 innerHTML 属性,归根结底还是设置 innerHTML 为 v -html 的值。
Vue 的父子组件生命周期钩子函数执行程序
- 渲染程序:先父后子,实现程序:先子后父
- 更新程序:父更新导致子更新,子更新实现后父
- 销毁程序:先父后子,实现程序:先子后父
加载渲染过程
父 beforeCreate
-> 父 created
-> 父 beforeMount
-> 子 beforeCreate
-> 子 created
-> 子 beforeMount
-> 子 mounted
-> 父 mounted
。子组件先挂载,而后到父组件
子组件更新过程
父 beforeUpdate
-> 子 beforeUpdate
-> 子 updated
-> 父 updated
父组件更新过程
父 beforeUpdate
-> 父 updated
销毁过程
父 beforeDestroy
-> 子 beforeDestroy
-> 子 destroyed
-> 父 destroyed
之所以会这样是因为
Vue
创立过程是一个递归过程,先创立父组件,有子组件就会创立子组件,因而创立时先有父组件再有子组件;子组件首次创立时会增加mounted
钩子到队列,等到patch
完结再执行它们,可见子组件的mounted
钩子是先进入到队列中的,因而等到patch
完结执行这些钩子时也先执行。
function patch (oldVnode, vnode, hydrating, removeOnly) {if (isUndef(vnode)) {if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return
}
let isInitialPatch = false
const insertedVnodeQueue = [] // 定义收集所有组件的 insert hook 办法的数组 // somthing ...
createElm(
vnode,
insertedVnodeQueue, oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)// somthing...
// 最终会顺次调用收集的 insert hook
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
// createChildren 会递归创立儿子组件
createChildren(vnode, children, insertedVnodeQueue) // something...
}
// 将组件的 vnode 插入到数组中
function invokeCreateHooks (vnode, insertedVnodeQueue) {for (let i = 0; i < cbs.create.length; ++i) {cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
// insert 办法中会顺次调用 mounted 办法
insert (vnode: MountedComponentVNode) {const { context, componentInstance} = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
}
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the // element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {vnode.parent.data.pendingInsert = queue} else {for (let i = 0; i < queue.length; ++i) {queue[i].data.hook.insert(queue[i]); // 调用 insert 办法
}
}
}
Vue.prototype.$destroy = function () {callHook(vm, 'beforeDestroy')
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null) // 先销毁儿子
// fire destroyed hook
callHook(vm, 'destroyed')
}
Vue 模版编译原理
vue 中的模板 template 无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的 HTML 语法,所有须要将 template 转化成一个 JavaScript 函数,这样浏览器就能够执行这一个函数并渲染出对应的 HTML 元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
- 解析阶段:应用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为形象语法树 AST。
- 优化阶段:遍历 AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行 diff 比拟时,间接跳过这一些动态节点,优化 runtime 的性能。
- 生成阶段:将最终的 AST 转化为 render 函数字符串。
子组件能够间接扭转父组件的数据吗?
子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。
Vue 提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。
只能通过 $emit
派发一个自定义事件,父组件接管到后,由父组件批改。
Vue 中给 data 中的对象属性增加一个新的属性时会产生什么?如何解决?
<template>
<div>
<ul>
<li v-for="value in obj" :key="value"> {{value}} </li> </ul> <button @click="addObjB"> 增加 obj.b</button> </div>
</template>
<script>
export default {data () {return { obj: { a: 'obj.a'} } }, methods: {addObjB () {this.obj.b = 'obj.b' console.log(this.obj) } } }
</script>
点击 button 会发现,obj.b 曾经胜利增加,然而视图并未刷新。这是因为在 Vue 实例创立时,obj.b 并未申明,因而就没有被 Vue 转换为响应式的属性,天然就不会触发视图的更新,这时就须要应用 Vue 的全局 api $set():
addObjB () (this.$set(this.obj, 'b', 'obj.b')
console.log(this.obj)
}
$set()办法相当于手动的去把 obj.b 解决成一个响应式的属性,此时视图也会跟着扭转了。
diff 算法
工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。
了解:
-
最小量更新,
key
很重要。这个能够是这个节点的惟一标识,通知diff
算法,在更改前后它们是同一个 DOM 节点- 扩大
v-for
为什么要有key
,没有key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加key
只会挪动缩小操作 DOM。
- 扩大
- 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
- 只进行同层比拟,不会进行跨层比拟。
diff 算法的优化策略:四种命中查找,四个指针
- 旧前与新前(先比结尾,后插入和删除节点的这种状况)
- 旧后与新后(比结尾,前插入或删除的状况)
- 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
- 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)
参考:前端 vue 面试题具体解答
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。
delete 和 Vue.delete 删除数组的区别
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
理解 nextTick 吗?
异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用 nextTick
会将办法存入队列,通过异步办法清空以后队列。
$nextTick 原理及作用
Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种利用。
nextTick 的外围是利用了如 Promise、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 办法来模仿对应的微 / 宏工作的实现,实质是为了利用 JavaScript 的这些异步回调工作队列来实现 Vue 框架中本人的异步回调队列。
nextTick 不仅是 Vue 外部的异步队列的调用办法,同时也容许开发者在理论我的项目中应用这个办法来满足理论利用中对 DOM 更新数据机会的后续逻辑解决
nextTick 是典型的将底层 JavaScript 执行原理利用到具体案例中的示例,引入异步更新队列机制的起因∶
- 如果是同步更新,则屡次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,能够缩小一些无用渲染
- 同时因为 VirtualDOM 的引入,每一次状态发生变化后,状态变动的信号会发送给组件,组件外部应用 VirtualDOM 进行计算得出须要更新的具体的 DOM 节点,而后对 DOM 进行更新操作,每次更新状态后的渲染过程须要更多的计算,而这种无用功也将节约更多的性能,所以异步渲染变得更加至关重要
Vue 采纳了数据驱动视图的思维,然而在一些状况下,依然须要操作 DOM。有时候,可能遇到这样的状况,DOM1 的数据产生了变动,而 DOM2 须要从 DOM1 中获取数据,那这时就会发现 DOM2 的视图并没有更新,这时就须要用到了 nextTick
了。
因为 Vue 的 DOM 操作是异步的,所以,在下面的状况中,就要将 DOM2 获取数据的操作写在 $nextTick
中。
this.$nextTick(() => { // 获取数据的操作...})
所以,在以下状况下,会用到 nextTick:
- 在数据变动后执行的某个操作,而这个操作须要应用随数据变动而变动的 DOM 构造的时候,这个操作就须要办法在
nextTick()
的回调函数中。 - 在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也肯定要放在
nextTick()
的回调函数中。
因为在 created()钩子函数中,页面的 DOM 还未渲染,这时候也没方法操作 DOM,所以,此时如果想要操作 DOM,必须将操作的代码放在 nextTick()
的回调函数中。
虚构 DOM 的优劣如何?
长处:
- 保障性能上限: 虚构 DOM 能够通过 diff 找出最小差别, 而后批量进行 patch, 这种操作尽管比不上手动优化, 然而比起粗犷的 DOM 操作性能要好很多, 因而虚构 DOM 能够保障性能上限
- 无需手动操作 DOM: 虚构 DOM 的 diff 和 patch 都是在一次更新中主动进行的, 咱们无需手动操作 DOM, 极大进步开发效率
- 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干, 相比之下虚构 DOM 能够进行更不便地跨平台操作, 例如服务器渲染、挪动端开发等等
毛病:
- 无奈进行极致优化: 在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化, 比方 VScode 采纳间接手动操作 DOM 的形式进行极其的性能优化
Vue 组件之间通信形式有哪些
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信 :
父子组件通信
、隔代组件通信
、兄弟组件通信
,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信
组件传参的各种形式
组件通信罕用形式有以下几种
-
props / $emit
实用 父子组件通信- 父组件向子组件传递数据是通过
prop
传递的,子组件传递数据给父组件是通过$emit
触发事件来做到的
- 父组件向子组件传递数据是通过
-
ref
与$parent / $children(vue3 废除)
实用 父子组件通信ref
:如果在一般的DOM
元素上应用,援用指向的就是DOM
元素;如果用在子组件上,援用就指向组件实例$parent / $children
:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
-
EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信- 这种办法通过一个空的
Vue
实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
- 这种办法通过一个空的
-
$attrs / $listeners(vue3 废除)
实用于 隔代组件通信$attrs
:蕴含了父作用域中不被prop
所辨认 (且获取) 的个性绑定 (class
和style
除外 )。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 (class
和style
除外 ),并且能够通过v-bind="$attrs"
传入外部组件。通常配合inheritAttrs
选项一起应用$listeners
:蕴含了父作用域中的 (不含.native
润饰器的)v-on
事件监听器。它能够通过v-on="$listeners"
传入外部组件
-
provide / inject
实用于 隔代组件通信- 先人组件中通过
provider
来提供变量,而后在子孙组件中通过inject
来注入变量。provide / inject
API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系
- 先人组件中通过
$root
实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root
只对根组件有用-
Vuex
实用于 父子、隔代、兄弟组件通信Vuex
是一个专为Vue.js
利用程序开发的状态管理模式。每一个Vuex
利用的外围就是store
(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state
)Vuex
的状态存储是响应式的。当Vue
组件从store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会相应地失去高效更新。- 扭转
store
中的状态的惟一路径就是显式地提交 (commit
)mutation
。这样使得咱们能够不便地跟踪每一个状态的变动。
依据组件之间关系探讨组件通信最为清晰无效
- 父子组件:
props
/$emit
/$parent
/ref
- 兄弟组件:
$parent
/eventbus
/vuex
- 跨层级关系:
eventbus
/vuex
/provide+inject
/$attrs + $listeners
/$root
上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信
1. 父子组件通信
应用
props
,父组件能够应用props
向子组件传递数据。
父组件 vue
模板father.vue
:
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {child},
data () {
return {message: 'father message';}
}
}
</script>
子组件 vue
模板child.vue
:
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
回调函数(callBack)
父传子:将父组件里定义的 method
作为 props
传入子组件
// 父组件 Parent.vue:<Child :changeMsgFn="changeMessage">
methods: {changeMessage(){this.message = 'test'}
}
// 子组件 Child.vue:<button @click="changeMsgFn">
props:['changeMsgFn']
子组件向父组件通信
父组件向子组件传递事件办法,子组件通过
$emit
触发事件,回调给父组件
父组件 vue
模板father.vue
:
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {child},
methods: {func (msg) {console.log(msg);
}
}
}
</script>
子组件 vue
模板child.vue
:
<template>
<button @click="handleClick"> 点我 </button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
2. provide / inject 跨级拜访先人组件的数据
父组件通过应用 provide(){return{}}
提供须要传递的数据
export default {data() {
return {
title: '我是父组件',
name: 'poetry'
}
},
methods: {say() {alert(1)
}
},
// provide 属性 可能为前面的后辈组件 / 嵌套的组件提供所须要的变量和办法
provide() {
return {
message: '我是先人组件提供的数据',
name: this.name, // 传递属性
say: this.say
}
}
}
子组件通过应用 inject:[“参数 1”,”参数 2”,…]
接管父组件传递的参数
<template>
<p> 曾孙组件 </p>
<p>{{message}}</p>
</template>
<script>
export default {
// inject 注入 / 接管先人组件传递的所须要的数据即可
// 接管到的数据 变量 跟 data 外面的变量一样 能够间接绑定到页面 {{}}
inject: ["message","say"],
mounted() {this.say();
},
};
</script>
3. $parent + $children 获取父组件实例和子组件实例的汇合
this.$parent
能够间接拜访该组件的父实例或组件- 父组件也能够通过
this.$children
拜访它所有的子组件;须要留神$children
并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
<child1></child1>
<child2></child2>
<button @click="clickChild">$children 形式获取子组件值 </button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {data(){
return {total: 108}
},
components: {
child1,
child2
},
methods: {funa(e){console.log("index",e)
},
clickChild(){console.log(this.$children[0].msg);
console.log(this.$children[1].msg);
}
}
}
</script>
<!-- child1.vue -->
<template>
<div>
<button @click="parentClick"> 点击拜访父组件 </button>
</div>
</template>
<script>
export default {data(){
return {msg:"child1"}
},
methods: {
// 拜访父组件数据
parentClick(){this.$parent.funa("xx")
console.log(this.$parent.total);
}
}
}
</script>
<!-- child2.vue -->
<template>
<div>
child2
</div>
</template>
<script>
export default {data(){
return {msg: 'child2'}
}
}
</script>
4. $attrs + $listeners 多级组件通信
$attrs
蕴含了从父组件传过来的所有props
属性
// 父组件 Parent.vue:<Child :name="name" :age="age"/>
// 子组件 Child.vue:<GrandChild v-bind="$attrs" />
// 孙子组件 GrandChild
<p> 姓名:{{$attrs.name}}</p>
<p> 年龄:{{$attrs.age}}</p>
$listeners
蕴含了父组件监听的所有事件
// 父组件 Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>
// 子组件 Child.vue:<button @click="$listeners.changeNameFn"></button>
5. ref 父子组件通信
// 父组件 Parent.vue:<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){console.log(this.$refs.childComp.age);
this.$refs.childComp.changeAge()}
// 子组件 Child.vue:data(){
return{age:20}
},
methods(){changeAge(){this.age=15}
}
6. 非父子, 兄弟组件之间通信
vue2
中废除了broadcast
播送和散发事件的办法。父子组件中能够用props
和$emit()
。如何实现非父子组件间的通信,能够通过实例一个vue
实例Bus
作为媒介,要互相通信的兄弟组件之中,都引入Bus
,而后通过别离调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js
能够是这样:
// Bus.js
// 创立一个地方工夫总线类
class Bus {constructor() {this.callbacks = {}; // 寄存事件的名字
}
$on(name, fn) {this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将 $bus 挂载到 vue 实例的原型上
// 另一种形式
Vue.prototype.$bus = new Vue() // Vue 曾经实现了 Bus 的性能
<template>
<button @click="toBus"> 子组件传给兄弟组件 </button>
</template>
<script>
export default{
methods: {toBus () {this.$bus.$emit('foo', '来自兄弟组件')
}
}
}
</script>
另一个组件也在钩子函数中监听 on
事件
export default {data() {
return {message: ''}
},
mounted() {this.$bus.$on('foo', (msg) => {this.message = msg})
}
}
7. $root 拜访根组件中的属性或办法
- 作用:拜访根组件中的属性或办法
- 留神:是根组件,不是父组件。
$root
只对根组件有用
var vm = new Vue({
el: "#app",
data() {
return {rootInfo:"我是根元素的属性"}
},
methods: {alerts() {alert(111)
}
},
components: {
com1: {data() {
return {info: "组件 1"}
},
template: "<p>{{info}} <com2></com2></p>",
components: {
com2: {
template: "<p> 我是组件 1 的子组件 </p>",
created() {this.$root.alerts()// 根组件办法
console.log(this.$root.rootInfo)// 我是根元素的属性
}
}
}
}
}
});
8. vuex
- 实用场景: 简单关系的组件数据传递
- Vuex 作用相当于一个用来存储共享变量的容器
state
用来寄存共享变量的中央getter
,能够减少一个getter
派生状态,(相当于store
中的计算属性),用来取得共享变量的值mutations
用来寄存批改state
的办法。actions
也是用来寄存批改 state 的办法,不过action
是在mutations
的根底上进行。罕用来做一些异步操作
小结
- 父子关系的组件数据传递抉择
props
与$emit
进行传递,也可抉择ref
- 兄弟关系的组件数据传递可抉择
$bus
,其次能够抉择$parent
进行传递 - 先人与后辈组件数据传递可抉择
attrs
与listeners
或者Provide
与Inject
- 简单关系的组件数据传递能够通过
vuex
寄存共享的变量
Vue 路由 hash 模式和 history 模式
1. hash
模式
晚期的前端路由的实现就是基于 location.hash
来实现的。其实现原理很简略,location.hash
的值就是 URL
中 #
前面的内容。比方上面这个网站,它的 location.hash
的值为 '#search'
https://interview2.poetries.top#search
hash 路由模式的实现次要是基于上面几个个性
URL
中hash
值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash
局部不会被发送;hash
值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制hash
的切换;- 能够通过
a
标签,并设置href
属性,当用户点击这个标签后,URL
的hash
值会产生扭转;或者应用JavaScript
来对loaction.hash
进行赋值,扭转URL
的hash
值; - 咱们能够应用
hashchange
事件来监听hash
值的变动,从而对页面进行跳转(渲染)
window.addEventListener("hashchange", funcRef, false);
每一次扭转 hash
(window.location.hash
),都会在浏览器的拜访历史中减少一个记录利用 hash
的以上特点,就能够来实现前端路由“更新视图但不从新申请页面”的性能了
特点:兼容性好然而不美观
2. history
模式
history
采纳 HTML5
的新个性;且提供了两个新办法:pushState()
,replaceState()
能够对浏览器历史记录栈进行批改,以及 popState
事件的监听到状态变更
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
这两个办法有个独特的特点:当调用他们批改浏览器历史记录栈后,尽管以后 URL
扭转了,但浏览器不会刷新页面,这就为单页利用前端路由“更新视图但不从新申请页面”提供了根底。
history 路由模式的实现次要基于存在上面几个个性:
pushState
和repalceState
两个API
来操作实现URL
的变动;- 咱们能够应用
popstate
事件来监听url
的变动,从而对页面进行跳转(渲染); history.pushState()
或history.replaceState()
不会触发popstate
事件,这时咱们须要手动触发页面跳转(渲染)。
特点:尽管好看,然而刷新会呈现 404
须要后端进行配置
Vue.js 的 template 编译
简而言之,就是先转化成 AST 树,再失去的 render 函数返回 VNode(Vue 的虚构 DOM 节点),具体步骤如下:
首先,通过 compile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile 是 createCompiler 的返回值,createCompiler 是用以创立编译器的。另外 compile 还负责合并 option。
而后,AST 会通过 generate(将 AST 语法树转化成 render funtion 字符串的过程)失去 render 函数,render 的返回值是 VNode,VNode 是 Vue 的虚构 DOM 节点,外面有(标签名、子节点、文本等等)
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信
这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref
与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent
/$children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信
这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs
/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合 inheritAttrs 选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信
先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。
(6)Vuex 实用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue-router 根本应用
mode
hash
history
跳转
- 编程式(js 跳转)
this.$router.push('/')
- 申明式(标签跳转)
<router-link to=""></router-link>
vue 路由传参数
- 应用
query
办法传入的参数应用this.$route.query
承受 - 应用
params
形式传入的参数应用this.$route.params
承受
占位
<router-view></router-view>
简述 mixin、extends 的笼罩逻辑
(1)mixin 和 extends mixin 和 extends 均是用于合并、拓展组件的,两者均通过 mergeOptions 办法实现合并。
- mixins 接管一个混入对象的数组,其中混入对象能够像失常的实例对象一样蕴含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子依照传入程序顺次调用,并在调用组件本身的钩子之前被调用。
-
extends 次要是为了便于扩大单文件组件,接管一个对象或构造函数。
(2)mergeOptions 的执行过程
- 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
- 对未合并的选项,进行判断
if (!child._base) {if (child.extends) {parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}
- 合并解决。依据一个通用 Vue 实例所蕴含的选项进行分类逐个判断合并,如 props、data、methods、watch、computed、生命周期等,将合并后果存储在新定义的 options 对象里。
- 返回合并后果 options。