created 和 mounted 的区别
- created: 在模板渲染成 html 前调用,即通常初始化某些属性值,而后再渲染成视图。
- mounted: 在模板渲染成 html 后调用,通常是初始化页面实现后,再对 html 的 dom 节点进行一些须要的操作。
如何从实在 DOM 到虚构 DOM
波及到 Vue 中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM) - 优化树
- 将
ast
树生成代码
Vue3 有理解过吗?能说说跟 vue2 的区别吗?
1. 哪些变动
从上图中,咱们能够概览 Vue3
的新个性,如下:
- 速度更快
- 体积缩小
- 更易保护
- 更靠近原生
- 更易使用
1.1 速度更快
vue3
相比vue2
- 重写了虚构
Dom
实现 - 编译模板的优化
- 更高效的组件初始化
undate
性能进步 1.3~2 倍SSR
速度进步了 2~3 倍
1.2 体积更小
通过 webpack
的tree-shaking
性能,能够将无用模块“剪辑”,仅打包须要的
可能tree-shaking
,有两大益处:
- 对开发人员,可能对
vue
实现更多其余的性能,而不用担心整体体积过大 - 对使用者,打包进去的包体积变小了
vue
能够开发出更多其余的性能,而不用担心 vue
打包进去的整体体积过多
1.3 更易保护
compositon Api
- 可与现有的
Options API
一起应用 - 灵便的逻辑组合与复用
Vue3
模块能够和其余框架搭配应用
更好的 Typescript 反对
VUE3
是基于 typescipt
编写的,能够享受到主动的类型定义提醒
1.4 编译器重写
1.5 更靠近原生
能够自定义渲染 API
1.6 更易使用
响应式 Api
裸露进去
轻松辨认组件从新渲染起因
2. Vue3 新增个性
Vue 3 中须要关注的一些新性能包含:
framents
Teleport
composition Api
createRenderer
2.1 framents
在 Vue3.x
中,组件当初反对有多个根节点
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
2.2 Teleport
Teleport
是一种可能将咱们的模板挪动到 DOM
中 Vue app
之外的其余地位的技术,就有点像哆啦 A 梦的“任意门”
在 vue2
中,像 modals
,toast
等这样的元素,如果咱们嵌套在 Vue
的某个组件外部,那么解决嵌套组件的定位、z-index
和款式就会变得很艰难
通过Teleport
,咱们能够在组件的逻辑地位写模板代码,而后在 Vue
利用范畴之外渲染它
<button @click="showToast" class="btn"> 关上 toast</button>
<!-- to 属性就是指标地位 -->
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg"> 我是一个 Toast 文案 </div>
</div>
</teleport>
2.3 createRenderer
通过createRenderer
,咱们可能构建自定义渲染器,咱们可能将 vue
的开发模型扩大到其余平台
咱们能够将其生成在 canvas
画布上
对于createRenderer
,咱们理解下根本应用,就不开展讲述了
import {createRenderer} from '@vue/runtime-core'
const {render, createApp} = createRenderer({
patchProp,
insert,
remove,
createElement,
// ...
})
export {render, createApp}
export * from '@vue/runtime-core'
2.4 composition Api
composition Api,也就是组合式api
,通过这种模式,咱们可能更加容易保护咱们的代码,将雷同性能的变量进行一个集中式的治理
对于 compositon api
的应用,这里以下图开展
简略应用:
export default {setup() {const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {count.value++}
onMounted(() => console.log('component mounted!'))
return {
count,
double,
increment
}
}
}
3. 非兼容变更
3.1 Global API
- 全局
Vue API
已更改为应用应用程序实例 - 全局和外部
API
曾经被重构为可tree-shakable
3.2 模板指令
- 组件上
v-model
用法已更改 <template v-for>
和 非v-for
节点上key
用法已更改- 在同一元素上应用的
v-if
和v-for
优先级已更改 v-bind="object"
当初排序敏感v-for
中的ref
不再注册ref
数组
3.3 组件
- 只能应用一般函数创立性能组件
functional
属性在单文件组件(SFC)
- 异步组件当初须要
defineAsyncComponent
办法来创立
3.4 渲染函数
- 渲染函数
API
扭转 $scopedSlots
property 已删除,所有插槽都通过$slots
作为函数裸露- 自定义指令 API 已更改为与组件生命周期统一
-
一些转换
class
被重命名了:v-enter
->v-enter-from
v-leave
->v-leave-from
- 组件
watch
选项和实例办法$watch
不再反对点分隔字符串门路,请改用计算函数作为参数 - 在
Vue 2.x
中,利用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板 / 渲染选项,则最终编译为模板)。VUE3.x
当初应用应用程序容器的innerHTML
。
3.5 其余小扭转
destroyed
生命周期选项被重命名为unmounted
beforeDestroy
生命周期选项被重命名为beforeUnmount
[prop default
工厂函数不再有权拜访this
是上下文- 自定义指令 API 已更改为与组件生命周期统一
data
应始终申明为函数- 来自
mixin
的data
选项当初可简略地合并 attribute
强制策略已更改- 一些过渡
class
被重命名 - 组建 watch 选项和实例办法
$watch
不再反对以点分隔的字符串门路。请改用计算属性函数作为参数。 <template>
没有非凡指令的标记 (v-if/else-if/else
、v-for
或v-slot
) 当初被视为一般元素,并将生成原生的<template>
元素,而不是渲染其外部内容。- 在
Vue 2.x
中,利用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板 / 渲染选项,则最终编译为模板)。Vue 3.x
当初应用利用容器的innerHTML
,这意味着容器自身不再被视为模板的一部分。
3.6 移除 API
keyCode
反对作为v-on
的修饰符$on
,$off
和$once
实例办法- 过滤
filter
- 内联模板
attribute
$destroy
实例办法。用户不应再手动治理单个Vue
组件的生命周期。
为什么要应用异步组件
- 节俭打包出的后果,异步组件离开打包,采纳
jsonp
的形式进行加载,无效解决文件过大的问题。 - 外围就是包组件定义变成一个函数,依赖
import()
语法,能够实现文件的宰割加载。
components:{AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([])
}
原理
export function (Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void {
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend
// 第二次渲染时 Ctor 不为 undefined
if (Ctor === undefined) {
return createAsyncPlaceholder( // 渲染占位符 空虚构节点
asyncFactory,
data,
context,
children,
tag
)
}
}
}
function resolveAsyncComponent (factory: Function, baseCtor: Class<Component>): Class<Component> | void {if (isDef(factory.resolved)) {
// 3. 在次渲染时能够拿到获取的最新组件
return factory.resolved
}
const resolve = once((res: Object | Class<Component>) => {factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {forceRender(true) //2. 强制更新视图从新渲染
} else {owners.length = 0}
})
const reject = once(reason => {if (isDef(factory.errorComp)) {factory.error = true forceRender(true)
}
})
const res = factory(resolve, reject)// 1. 将 resolve 办法和 reject 办法传入,用户调用 resolve 办法后
sync = false
return factory.resolved
}
Vue.set 的实现原理
- 给对应和数组自身都减少了
dep
属性 - 当给对象新增不存在的属性则触发对象依赖的
watcher
去更新 - 当批改数组索引时,咱们调用数组自身的
splice
去更新数组(数组的响应式原理就是从新了splice
等办法,调用splice
就会触发视图更新)
根本应用
以下办法调用会扭转原始数组:
push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
,Vue.set(target, key, value)
-
调用办法:
Vue.set(target, key, value)
target
:要更改的数据源(能够是对象或者数组)key
:要更改的具体数据value
:从新赋的值
<div id="app">{{user.name}} {{user.age}}</div>
<div id="app"></div>
<script>
// 1. 依赖收集的特点:给每个属性都减少一个 dep 属性,dep 属性会进行收集,收集的是 watcher
// 2. vue 会给每个对象也减少一个 dep 属性
const vm = new Vue({
el: '#app',
data: { // vm._data
user: {name:'poetry'}
}
});
// 对象的话:调用 defineReactive 在 user 对象上定义一个 age 属性,减少到响应式数据中,触发对象自身的 watcher,ob.dep.notify()更新
// 如果是数组 通过调用 splice 办法,触发视图更新
vm.$set(vm.user, 'age', 20); // 不能给根属性增加,因为给根增加属性 性能耗费太大,须要做很多解决
// 批改必定是同步的 -> 更新都是一步的 queuewatcher
</script>
相干源码
// src/core/observer/index.js 44
export class Observer {// new Observer(value)
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep() // 给所有对象类型减少 dep 属性}
}
// src/core/observer/index.js 201
export function set (target: Array<any> | Object, key: any, val: any): any {
// 1. 是开发环境 target 没定义或者是根底类型则报错
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 2. 如果是数组 Vue.set(array,1,100); 调用咱们重写的 splice 办法 (这样能够更新视图)
if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
// 利用数组的 splice 变异办法触发响应式
target.splice(key, 1, val)
return val
}
// 3. 如果是对象自身的属性,则间接增加即可
if (key in target && !(key in Object.prototype)) {target[key] = val // 间接批改属性值
return val
}
// 4. 如果是 Vue 实例 或 根数据 data 时 报错,(更新_data 无意义)const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 5. 如果不是响应式的也不须要将其定义成响应式属性
if (!ob) {target[key] = val
return val
}
// 6. 将属性定义成响应式的
defineReactive(ob.value, key, val)
// 告诉视图更新
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的
splice
办法触发相应式; - 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用
defineReactive
办法进行响应式解决(defineReactive
办法就是Vue
在初始化对象时,给对象属性采纳Object.defineProperty
动静增加getter
和setter
的性能所调用的办法)
v-if 和 v -for 哪个优先级更高
- 实际中不应该把
v-for
和v-if
放一起 - 在
vue2
中,v-for
的优先级是高于v-if
,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3
中则齐全相同,v-if
的优先级高于v-for
,所以v-if
执行时,它调用的变量还不存在,就会导致异样 -
通常有两种状况下导致咱们这样做:
- 为了过滤列表中的我的项目 (比方
v-for="user in users" v-if="user.isActive"
)。此时定义一个计算属性 (比方activeUsers
),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
) - 为了防止渲染本应该被暗藏的列表 (比方
v-for="user in users" v-if="shouldShowUsers"
)。此时把v-if
挪动至容器元素上 (比方ul
、ol
)或者外面包一层template
即可
- 为了过滤列表中的我的项目 (比方
- 文档中明确指出永远不要把
v-if
和v-for
同时用在同一个元素上,显然这是一个重要的注意事项 - 源码外面对于代码生成的局部,可能清晰的看到是先解决
v-if
还是v-for
,程序上vue2
和vue3
正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的
vue2.x 源码剖析
在 vue 模板编译的时候,会将指令系统转化成可执行的
render
函数
编写一个 p
标签,同时应用 v-if
与 v-for
<div id="app">
<p v-if="isShow" v-for="item in items">
{{item.title}}
</p>
</div>
创立 vue
实例,寄存 isShow
与items
数据
const app = new Vue({
el: "#app",
data() {
return {
items: [{ title: "foo"},
{title: "baz"}]
}
},
computed: {isShow() {return this.items && this.items.length > 0}
}
})
模板指令的代码都会生成在 render
函数中,通过 app.$options.render
就能失去渲染函数
ƒ anonymous() {with (this) { return
_c('div', { attrs: { "id": "app"} },
_l((items), function (item)
{return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e()}), 0) }
}
_l
是vue
的列表渲染函数,函数外部都会进行一次if
判断- 初步失去论断:
v-for
优先级是比v-i
f 高 - 再将
v-for
与v-if
置于不同标签
<div id="app">
<template v-if="isShow">
<p v-for="item in items">{{item.title}}</p>
</template>
</div>
再输入下 render
函数
ƒ anonymous() {with(this){return
_c('div',{attrs:{"id":"app"}},
[(isShow)?[_v("\n"),
_l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
}
这时候咱们能够看到,v-for
与 v-if
作用在不同标签时候,是先进行判断,再进行列表的渲染
咱们再在查看下 vue 源码
源码地位:\vue-dev\src\compiler\codegen\index.js
export function genElement (el: ASTElement, state: CodegenState): string {if (el.parent) {el.pre = el.pre || el.parent.pre}
if (el.staticRoot && !el.staticProcessed) {return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {return genOnce(el, state)
} else if (el.for && !el.forProcessed) {return genFor(el, state)
} else if (el.if && !el.ifProcessed) {return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {return genSlot(el, state)
} else {
// component or element
...
}
在进行 if
判断的时候,v-for
是比 v-if
先进行判断
最终论断:v-for
优先级比 v-if
高
参考 前端进阶面试题具体解答
如果让你从零开始写一个 vue 路由,说说你的思路
思路剖析:
首先思考 vue
路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。
- 借助
hash
或者 history api
实现url
跳转页面不刷新 - 同时监听
hashchange
事件或者popstate
事件处理跳转 - 依据
hash
值或者state
值从routes
表中匹配对应component
并渲染
答复范例:
一个 SPA
利用的路由须要解决的问题是 页面跳转内容扭转同时不刷新,同时路由还须要以插件模式存在,所以:
- 首先我会定义一个
createRouter
函数,返回路由器实例,实例外部做几件事 - 保留用户传入的配置项
- 监听
hash
或者popstate
事件 - 回调里依据
path
匹配对应路由 - 将
router
定义成一个Vue
插件,即实现install
办法,外部做两件事 - 实现两个全局组件:
router-link
和router-view
,别离实现页面跳转和内容显示 - 定义两个全局变量:
$route
和$router
,组件内能够拜访以后路由和路由器实例
生命周期钩子是如何实现的
Vue 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)
相干代码如下
export function callHook(vm, hook) {
// 顺次执行生命周期对应的办法
const handlers = vm.$options[hook];
if (handlers) {for (let i = 0; i < handlers.length; i++) {handlers[i].call(vm); // 生命周期外面的 this 指向以后实例
}
}
}
// 调用的时候
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options, options);
callHook(vm, "beforeCreate"); // 初始化数据之前
// 初始化状态
initState(vm);
callHook(vm, "created"); // 初始化数据之后
if (vm.$options.el) {vm.$mount(vm.$options.el);
}
};
子组件能够间接扭转父组件的数据么,阐明起因
这是一个实际知识点,组件化开发过程中有个 单项数据流准则
,不在子组件中批改父组件是个常识问题
思路
- 讲讲单项数据流准则,表明为何不能这么做
- 举几个常见场景的例子说说解决方案
- 联合实际讲讲如果须要批改父组件状态应该如何做
答复范例
- 所有的
prop
都使得其父子之间造成了一个单向上行绑定:父级prop
的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外变更父级组件的状态,从而导致你的利用的数据流向难以了解。另外,每次父级组件产生变更时,子组件中所有的prop
都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转prop
。如果你这样做了,Vue
会在浏览器控制台中收回正告
const props = defineProps(['foo'])
// ❌ 上面行为会被正告, props 是只读的!
props.foo = 'bar'
- 理论开发过程中有两个场景会想要批改一个属性:
这个 prop 用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的 prop 数据来应用。 在这种状况下,最好定义一个本地的 data
,并将这个 prop
用作其初始值:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
这个 prop 以一种原始的值传入且须要进行转换。 在这种状况下,最好应用这个 prop
的值来定义一个计算属性:
const props = defineProps(['size'])
// prop 变动,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
- 实际中如果的确想要扭转父组件属性应该
emit
一个事件让父组件去做这个变更。留神尽管咱们不能间接批改一个传入的对象或者数组类型的prop
,然而咱们还是可能间接改内嵌的对象或属性
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 节点,外面有(标签名、子节点、文本等等)
v-if 和 v -show 区别
v-show
暗藏则是为该元素增加css--display:none
,dom
元素仍旧还在。v-if
显示暗藏是将dom
元素整个增加或删除- 编译过程:
v-if
切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show
只是简略的基于css
切换 - 编译条件:
v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染 v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
办法- 性能耗费:
v-if
有更高的切换耗费;v-show
有更高的初始渲染耗费
v-show 与 v -if 的应用场景
v-if
与v-show
都能管制dom
元素在页面的显示v-if
相比v-show
开销更大的(间接操作dom 节
点减少与删除)- 如果须要十分频繁地切换,则应用 v-show 较好
- 如果在运行时条件很少扭转,则应用
v-if
较好
v-show 与 v -if 原理剖析
v-show
原理
不论初始条件是什么,元素总是会被渲染
咱们看一下在 vue 中是如何实现的
代码很好了解,有 transition
就执行 transition
,没有就间接设置display
属性
// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {transition.beforeEnter(el)
} else {setDisplay(el, value)
}
},
mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
}
},
updated(el, { value, oldValue}, {transition}) {// ...},
beforeUnmount(el, { value}) {setDisplay(el, value)
}
}
v-if
原理
v-if
在实现上比 v-show
要简单的多,因为还有else
else-if
等条件须要解决,这里咱们也只摘抄源码中解决 v-if
的一小部分
返回一个 node
节点,render
函数通过表达式的值来决定是否生成DOM
// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
(node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// ...
return () => {if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
Vue 组件通信有哪几种形式
- props 和 $emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
- $parent,$children 获取以后组件的父组件和以后组件的子组件
- $attrs 和 $listeners A->B->C。Vue 2.4 开始提供了 $attrs 和 $listeners 来解决这个问题
- 父组件中通过 provide 来提供变量,而后在子组件中通过 inject 来注入变量。(官网不举荐在理论业务中应用,然而写组件库时很罕用)
- $refs 获取组件实例
- envetBus 兄弟组件数据传递 这种状况下能够应用事件总线的形式
- vuex 状态治理
diff 算法
<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key
很重要。这个能够是这个节点的惟一标识,通知 diff
算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for
为什么要有 key
,没有 key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key
只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
— 问完下面这些如果都能很分明的话,根本 O 了 —
以下的这些简略的概念,你必定也是没有问题的啦😉
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。这样使得咱们能够不便地跟踪每一个状态的变动。
异步组件是什么?应用场景有哪些?
剖析
因为异步路由的存在,咱们应用异步组件的次数比拟少,因而还是有必要两者的不同。
体验
大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们
import {defineAsyncComponent} from 'vue'
// defineAsyncComponent 定义异步组件,返回一个包装组件。包装组件依据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
// 加载函数返回 Promise
return new Promise((resolve, reject) => {
// ... 能够从服务器加载组件
resolve(/* loaded component */)
})
})
// 借助打包工具实现 ES 模块动静导入
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
答复范例
- 在大型利用中,咱们须要宰割利用为更小的块,并且在须要组件时再加载它们。
- 咱们不仅能够在路由切换时懒加载组件,还能够在页面组件中持续应用异步组件,从而实现更细的宰割粒度。
- 应用异步组件最简略的形式是间接给
defineAsyncComponent
指定一个loader
函数,联合 ES 模块动静导入函数import
能够疾速实现。咱们甚至能够指定loadingComponent
和errorComponent
选项从而给用户一个很好的加载反馈。另外Vue3
中还能够联合Suspense
组件应用异步组件。 - 异步组件容易和路由懒加载混同,实际上不是一个货色。异步组件不能被用于定义懒加载路由上,解决它的是
vue
框架,解决路由组件加载的是vue-router
。然而能够在懒加载的路由组件中应用异步组件
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题?你能说说如下代码的实现原理么?
1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题
- Vue 应用了 Object.defineProperty 实现双向数据绑定
- 在初始化实例时对属性执行 getter/setter 转化
- 属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的(这也就造成了 Vue 无奈检测到对象属性的增加或删除)
所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下来咱们看看框架自身是如何实现的呢?
Vue 源码地位:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的 splice 变异办法触发响应式
target.splice(key, 1, val)
return val
}
// key 曾经存在,间接批改属性值
if (key in target && !(key in Object.prototype)) {target[key] = val
return val
}
const ob = (target: any).__ob__
// target 自身就不是响应式数据, 间接赋值
if (!ob) {target[key] = val
return val
}
// 对属性进行响应式解决
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
- 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法
双向绑定的原理是什么
咱们都晓得 Vue
是数据双向绑定的框架,双向绑定由三个重要局部形成
- 数据层(Model):利用的数据及业务逻辑
- 视图层(View):利用的展现成果,各类 UI 组件
- 业务逻辑层(ViewModel):框架封装的外围,它负责将数据与视图关联起来
而下面的这个分层的架构计划,能够用一个专业术语进行称说:MVVM
这里的管制层的外围性能便是“数据双向绑定”。天然,咱们只需弄懂它是什么,便能够进一步理解数据绑定的原理
了解 ViewModel
它的主要职责就是:
- 数据变动后更新视图
- 视图变动后更新数据
当然,它还有两个次要局部组成
- 监听器(
Observer
):对所有数据的属性进行监听 - 解析器(
Compiler
):对每个元素节点的指令进行扫描跟解析, 依据指令模板替换数据, 以及绑定相应的更新函数
为什么要用 Vuex 或者 Redux
因为传参的办法对于多层嵌套的组件将会十分繁琐,并且对于兄弟组件间的状态传递无能为力。咱们常常会采纳父子组件间接援用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式十分软弱,通常会导致代码无奈保护。
所以须要把组件的共享状态抽取进去,以一个全局单例模式治理。在这种模式下,组件树形成了一个微小的 ” 视图 ”,不论在树的哪个地位,任何组件都能获取状态或者触发行为。
另外,通过定义和隔离状态治理中的各种概念并强制恪守肯定的规定,代码将会变得更结构化且易保护。
$route 和 $router
的区别
- $route 是“路由信息对象”,包含 path,params,hash,query,fullPath,matched,name 等路由信息参数
- $router 是“路由实例”对象包含了路由的跳转办法,钩子函数等。
为什么在 Vue3.0 采纳了 Proxy, 摈弃了 Object.defineProperty?
Object.defineProperty 自身有肯定的监控到数组下标变动的能力, 然而在 Vue 中, 从性能 / 体验的性价比思考, 尤大大就弃用了这个个性。为了解决这个问题, 通过 vue 外部解决后能够应用以下几种办法来监听数组
push();
pop();
shift();
unshift();
splice();
sort();
reverse();
因为只针对了以上 7 种办法进行了 hack 解决, 所以其余数组的属性也是检测不到的, 还是具备肯定的局限性。
Object.defineProperty 只能劫持对象的属性, 因而咱们须要对每个对象的每个属性进行遍历。Vue 2.x 里, 是通过 递归 + 遍历 data 对象来实现对数据的监控的, 如果属性值也是对象那么须要深度遍历, 显然如果能劫持一个残缺的对象是才是更好的抉择。
Proxy 能够劫持整个对象, 并返回一个新的对象。Proxy 不仅能够代理对象, 还能够代理数组。还能够代理动静减少的属性。