vue核心面试题汇总【查缺补漏】_vue的面试问题核心重点
在v-for
进行循环展示过程中,当数据发生变化进行渲染的过程中,会进行新旧节点列表的比对。首先新旧vnode
列表首先通过首首
、尾尾
、首尾
和尾首
的方式进行比对,如果key
相同则采取原地复用的策略进行节点的移动。
如果首尾两两比对的方式找不到对应关系,继续通过key
和vnode
的对应关系进行寻找。
如果key
和vnode
对应关系中找不到,继续通过sameVnode
的方式在未比对的节点中进行寻找。
如果都找不到,则将其按照新vnode
进行createElm
的方式进行创建,这种方式是比节点移动的方式计算量更大。
最后将旧的vnode
列表中没有进行匹配的vnode
中的vnode.elm
在父节点中移除。
简单总结就是,新的vnode
列表在旧的vnode
列表中去寻找具有相同的key
的节点进行原地复用,如果找不到则通过创建的方式createElm
去创建一个,如果旧的vnode
列表中没有进行匹配则在父节点中移除其vnode.elm
。这就是原地复用逻辑的大体实现。
具体key
和diff
算法的关系可以参考vue2从数据变化到视图变化:diff算法图解
十九、v-for
和v-if
能同时使用吗
答案是:用了也能出来预期的效果,但是会有性能浪费。
同时包含v-for
和v-if
的template
模板在编辑阶段会执行v-for
比v-if
优先级更高的编译流程;在生成vnode
的阶段,会包含属性isComment
为true
的空白占位vnode
;在patch
阶段,会生成真实的占位节点。虽然一个空的占位节点无妨,但是如果数据量比较大的话,也是一个性能问题。
当然,可以在获取到数据(一般是在beforeCreate
或者created
阶段)时进行过滤处理,也可以通过计算属性对其进行处理。
可以通过v-for和v-if可以一起使用吗?了解v-for
和v-if
的详细过程。
二十、vue
中的data
为什么是函数
答案是:是不是一定是函数,得看场景。并且,也无需担心什么时候该将data
写为函数还是对象,因为vue
内部已经做了处理,并在控制台输出错误信息。
场景一:new Vue({data: ...})
这种场景主要为项目入口或者多个html
页面各实例化一个Vue
时,这里的data
即可用对象的形式,也可用工厂函数返回对象的形式。因为,这里的data
只会出现一次,不存在重复引用而引起的数据污染问题。
场景二:组件场景中的选项
在生成组件vnode
的过程中,组件会在生成构造函数的过程中执行合并策略:
// data合并策略
strats.data = function (
parentVal,
childVal,
vm
) {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
);
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
};
如果合并过程中发现子组件的数据不是函数,即typeof childVal !== 'function'
成立,进而在开发环境会在控制台输出警告并且直接返回parentVal
,说明这里压根就没有把childVal
中的任何data
信息合并到options
中去。
可以通过vue中的data为什么是函数?了解详细过程。
二十一、this.$watch
使用场景:用来监听数据的变化,当数据发生变化的时候,可以做一些业务逻辑的处理。
配置参数:
deep
:监听数据的深层变化immediate
:立即触发回调函数
实现思路: Vue
构造函数定义完成以后,在执行stateMixin(Vue)
时为Vue.prototype
上定义$watch
。该方法通过const watcher = new Watcher(vm, expOrFn, cb, options)
进行Watcher
的实例化,将options
中的user
属性设置为true
。并且,$watch
逻辑结束的会返回函数function unwatchFn () { watcher.teardown() }
,用来取消侦听的函数。
可以通过watch
选项和$watch
方法的区别vue中的watch和$watch监听的事件,执行几次?来了解详细过程。
二十二、计算属性和侦听属性的区别
相同点: 两者都是Watcher
实例化过程中的产物
计算属性:
- 使用场景:模板内的表达式主要用于简单运算,对于复杂的计算逻辑可以用计算属性
- 计算属性是基于它们的响应式依赖进行缓存的,当依赖的数据未发生变化时,多次调用无需重复执行函数
- 计算属性计算结果依赖于
data
中的值 - 同步操作,不支持异步
侦听属性:
- 使用场景:当需要在数据变化时执行异步或开销较大的操作时,可以用侦听属性
- 可配置参数:可以通过配置
immediate
和deep
来控制立即执行和深度监听的行为 - 侦听属性侦听的是
data
中定义的
计算属性请参考vue2从数据变化到视图变化:计算属性
侦听属性请参考vue2从数据变化到视图变化:侦听器
二十三、v-model
// main.js
new Vue({
el: "#app",
data() {
return {
msg: ""
};
},
template: `<div>
<input v-model="msg" placeholder="edit me">
<p>msg is: {{ msg }}</p>
</div>`
});
普通input:input
中的v-model
,最终通过target.addEventListener
处理成在节点上监听input
事件function($event){msg=$event.target.value}}
的形式,当input
值变化时msg
也跟着改变。
// main.js
const inputBox = {
template: `<input @input="$emit('input', $event.target.value)">`,
};
new Vue({
el: "#app",
template: `<div>
<input-box v-model="msg"></input-box>
<p>{{msg}}</p>
</div>`,
components: {
inputBox
},
data() {
return {
msg: 'hello world!'
};
},
});
组件:v-model
在组件中则通过给点击事件绑定原生事件,当触发到$emit
的时候,再进行回调函数ƒunction input($$v) {msg=$$v}
的执行,进而达到子组件修改父组件中数据msg
的目的。
二十四、v-slot
v-slot
产生的主要目的是,在组件的使用过程中可以让父组件有修改子组件内容的能力,就像在子组件里面放了个插槽,让父组件往插槽内塞入父组件中的楔子;并且,父组件在子组件中嵌入的楔子也可以访问子组件中的数据。v-slot
的产生让组件的应用更加灵活。
1、具名插槽
let baseLayout = {
template: `<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`,
data() {
return {
url: ""
};
}
};
new Vue({
el: "#app",
template: `<base-layout>
<template v-slot:header>
<h1>title-txt</h1>
</template>
<p>paragraph-1-txt</p>
<p>paragraph-2-txt</p>
<template v-slot:footer>
<p>foot-txt</p>
</template>
</base-layout>`,
components: {
baseLayout
}
});
引入的组件baseLayout
中的template
被添加了属性v-slot:header
和v-slot:footer
,子组件中定义了对应的插槽被添加了属性name="header"
和name="footer"
,未被进行插槽标识的内容被插入到了匿名的<slot></slot>
中。
2、作用域插槽
let currentUser = {
template: `<span>
<slot name="user" v-bind:userData="childData">{{childData.firstName}}</slot>
</span>`,
data() {
return {
childData: {
firstName: "first",
lastName: "last"
}
};
}
};
new Vue({
el: "#app",
template: `<current-user>
<template v-slot:user="slotProps">{{slotProps.userData.lastName}}</template>
</current-user>`,
components: {
currentUser
}
});
当前例子中作用域插槽通过v-bind:userData="childData"
的方式,将childData
作为参数,父组件中通过v-slot:user="slotProps"
的方式进行接收,为父组件使用子组件中的数据提供了可能。
v-slot
的底层实现请参考vue中的v-slot(源码分析)
二十五、Vue.filters
filters
类似于管道流可以将上一个过滤函数的结果作为下一个过滤函数的第一个参数,又可以在其中传递参数让过滤器更灵活。
// main.js文件
import Vue from "vue";
Vue.filter("filterEmpty", function(val) {
return val || "";
});
Vue.filter("filterA", function(val) {
return val + "平时周末的";
});
Vue.filter("filterB", function(val, info, fn) {
return val + info + fn;
});
new Vue({
el: "#app",
template: `<div>{{msg | filterEmpty | filterA | filterB('爱好是', transformHobby('chess'))}}</div>`,
data() {
return {
msg: "张三"
};
},
methods: {
transformHobby(type) {
const map = {
bike: "骑行",
chess: "象棋",
game: "游戏",
swimming: "游泳"
};
return map[type] || "未知";
}
}
});
其中我们对msg
通过filterEmpty
、filterA
和filterB('爱好是', transformHobby('chess'))}
进行三层过滤。
Vue.filters
的底层实现请查看vue中的filters(源码分析)
二十六、Vue.use
- 作用:
Vue.use
被用来安装Vue.js插件,例如vue-router
、vuex
、element-ui
。 install
方法:如果插件是一个对象,必须提供install
方法。如果插件是一个函数,它会被作为install
方法。install
方法调用时,会将Vue
作为参数传入。- 调用时机:该方法需要在调用
new Vue()
之前被调用。 - 特点:当 install 方法被同一个插件多次调用,插件将只会被安装一次。
二十七、Vue.extend
和选项extends
1、Vue.extend
Vue.extend
使用基础Vue
构造器创建一个“子类”,参数是一个包含组件选项的对象,实例化的过程中可以修改其中的选项,为实现功能的继承提供了思路。
new Vue({
el: "#app",
template: `<div><div id="person1"></div><div id="person2"></div></div>`,
mounted() {
// 定义子类构造函数
var Profile = Vue.extend({
template: '<p @click="showInfo">{{name}} 喜欢 {{fruit}}</p>',
data: function () {
return {
name: '张三',
fruit: '苹果'
}
},
methods: {
showInfo() {
console.log(`${this.name}喜欢${this.fruit}`)
}
}
})
// 实例化1,挂载到`#person1`上
new Profile().$mount('#person1')
// 实例化2,并修改其`data`选项,挂载到`#person2`上
new Profile({
data: function () {
return {
name: '李四',
fruit: '香蕉'
}
},
}).$mount('#person2')
},
});
在当前例子中,通过Vue.extend
构建了子类构造函数Profile
,可以通过new Profile
的方式实例化无数个vm
实例。我们定义初始的template
、data
和methods
供vm
进行使用,如果有变化,在实例的过程中传入新的选项参数即可,比如例子中实例化第二个vm
的时候就对data
进行了调整。
2、选项extends
extends
允许声明扩展另一个组件 (可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend
。这主要是为了便于扩展单文件组件,以实现组件继承的目的。
const common = {
template: `<div>{{name}}</div>`,
data() {
return {
name: '表单'
}
}
}
const create = {
extends: common,
data() {
return {
name: '新增表单'
}
}
}
const edit = {
extends: common,
data() {
return {
name: '编辑表单'
}
}
}
new Vue({
el: "#app",
template: `<div>
<create></create>
<edit></edit>
</div>`,
components: {
create,
edit,
}
});
当前极简demo中定义了公共的表单common
,然后又在新增表单组件create
和编辑表单组件edit
中扩展了common
。
二十八、Vue.mixin
和选项mixins
全局混入和局部混入视情况而定,主要区别在全局混入是通过Vue.mixin
的方式将选项混入到了Vue.options
中,在所有获取子组件构建函数的时候都将其进行了合并,是一种影响全部组件的混入策略。
而局部混入是将选项通过配置mixins
选项的方式合并到当前的子组件中,只有配置了mixins
选项的组件才会受到混入影响,是一种局部的混入策略。
二十九、Vue.directive
和directives
1、使用场景
主要用于对于DOM的操作,比如:文本框聚焦,节点位置控制、防抖节流、权限管理、复制操作等功能
2、钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
3、钩子函数参数
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
4、动态指令参数
指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value"
中,argument
参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。
三十、vue
中的原生事件
vue
中可以通过@
或者v-on
的方式绑定事件,也可为其添加修饰符。
new Vue({
el: '#app',
template: `<div @click='divClick'><a @clickt='aClick' href=''>点击</a></div>`,
methods: {
divClick() {
console.log('divClick')
},
aClick() {
console.log('aClick')
},
}
})
以上例子如果点击a
会触发其默认行为,如果href
不为空还会进行跳转。除此之外,点击还会继续触发div
上绑定的点击事件。
如果通过@click.stop.prevent='aClick'
的方式为a
标签的点击事件添加修饰符stop
和prevent
,那么就不会触发其a
的默认行为,即使href
不为空也不会进行跳转,同时,div
上的点击事件也不会进行触发。
模板的渲染一般分为编译生成render
函数、render
函数执行生成vNode
和patch
进行渲染。下面按照这步骤进行简单分析。
1、render
通过编译生成的render
函数:
with(this) {
return _c('div', {
on: {
"click": divClick
}
}, [_c('a', {
attrs: {
"href": "http://www.baidu.com"
},
on: {
"click": function ($event) {
$event.stopPropagation();
$event.preventDefault();
return aClick($event)
}
}
}, [_v("点击")])])
}
其中div
的on
作为div
事件描述。a
标签的attrs
作为属性描述,on
作为事件描述,在描述中.stop
被编译成了$event.stopPropagation()
来阻止事件冒泡,.prevent
被编译成了$event.preventDefault()
用来阻止a
标签的默认行为。
2、vNode
通过执行Vue.prototype._render
将render
函数转换成vNode
。
3、patch
patch
的过程中,当完成$el
节点的渲染后会执行invokeCreateHooks(vnode, insertedVnodeQueue)
逻辑,其中,针对attrs
会将其设置为$el
的真实属性,当前例子中会为a
标签设置herf
属性。针对on
会通过target.addEventListener
的方式将其处理过的事件绑定到$el
上,当前例子中会分别对div
和a
中的click
进行处理,再通过addEventListener
的方式进行绑定。
小结
vue
中的事件,从编译生成render
再通过Vue.prototype._render
函数执行render
到生成vNode
,主要是通过on
作为描述。在patch
渲染阶段,将on
描述的事件进行处理再通过addEventListener
的方式绑定到$el
上。
三十一、常用修饰符
1、表单修饰符
(1).lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 ,可以添加 lazy
修饰符,从而转为在 change
事件之后进行同步:
<input v-model.lazy="msg">
(2).number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
(3).trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
2、事件修饰符
(1).stop
阻止单击事件继续传播。
<!--这里只会触发a-->
<div @click="divClick"><a v-on:click.stop="aClick">点击</a></div>
(2).prevent
阻止标签的默认行为。
<a href="http://www.baidu.com" v-on:click.prevent="aClick">点击</a>
(3).capture
事件先在有.capture
修饰符的节点上触发,然后在其包裹的内部节点中触发。
<!--这里先执行divClick事件,然后再执行aClick事件-->
<div @click="divClick"><a v-on:click="aClick">点击</a></div>
(4).self
只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的。
<!--在a标签上点击时只会触发aClick事件,只有点击phrase的时候才会触发divClick事件-->
<div @click.self="divClick">phrase<a v-on:click="aClick">点击</a></div>
(5).once
不像其它只能对原生的 DOM 事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上,表示当前事件只触发一次。
<a v-on:click.once="aClick">点击</a>
(6).passive
.passive
修饰符尤其能够提升移动端的性能
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
3、其他修饰符
除了表单和事件的修饰符,Vue
还提供了很多其他修饰符,在使用的时候可以查阅文档。
小结
Vue
中提供了很多好用的功能和api
,那么修饰符的出现就为功能和api
提供了更为丰富的扩展属性和更大的灵活度。
三十二、vue-router
vue
路由是单页面中视图切换的方案,有三种mode
:
- hash,#后的仅仅作为参数,不属于url部分
- history,路径作为请求url请求资源链接,如果找不到会出现404错误
- abstract,服务端渲染场景 hash场景下,会出现
url
链接,再修改其view-router中对应的值。
了解vue-router
的底层实现请参考vue2视图切换:vue-router
三十三、vuex
vuex
是状态管理仓库,一般使用的场景为:多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态。其管理的状态是响应式的,修改也只能显式提交mutation
的方式修改。vuex
有state
、getter
、mutation
、action
和module
五个核心,并且通过module
实现了vuex
树的管理。
了解vuex
的底层实现请参考vue2状态管理:vuex
三十四、eventBus
使用场景:兄弟组件传参
const eventBus = new Vue();
const A = {
template: `<div @click="send">component-a</div>`,
methods: {
send() {
eventBus.$emit('sendData', 'data from A')
}
},
}
const B = {
template: `<div>component-b</div>`,
created() {
eventBus.$on('sendData', (args) => {
console.log(args)
})
},
}
new Vue({
el: '#app',
components: {
A,
B,
},
template: `<div><A></A><B></B></div>`,
})
在当前例子中,A
组件和B
组件称为兄弟组件,A
组件通过事件总线eventBus
中的$emit
分发事件,B
组件则通过$on
来监听事件。
实现原理:eventsMixin
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
}
在Vue
构造函数定义完执行的eventsMixin
函数中,在Vue.prototype
上分别定义了$on
、$emit
、$off
和$once
的方法易实现对事件的绑定、分发、取消和只执行一次的方法。eventBus
就是利用了当new Vue
实例化后实例上的$on
、$emit
、$off
和$once
进行数据传递。
三十五、ref
使用场景: 父组件获取子组件数据或者执行子组件方法
const A = {
template: `<div>{{childData.age}}</div>`,
data() {
return {
childData: {
name: 'qb',
age: 30
},
}
},
methods: {
increaseAge() {
this.childData.age++;
}
}
}
new Vue({
el: '#app',
components: {
A,
},
template: `<A ref='childRef' @click.native='changeChildData'></A>`,
methods: {
changeChildData() {
// 执行子组件的方法
this.$refs.childRef.increaseAge()
// 获取子组件的数据
console.log(this.$refs.childRef.childData);
},
}
})
在当前例子中,通过ref='childRef'
的方式在当前组件中定义一个ref
,可以通过this.$refs.childRef
的方式获取到子组件A
。可以通过this.$refs.childRef.increaseAge()
的方式执行子组件中age
增加的方法,也可以通过this.$refs.childRef.childData
的方式获取到子组件中的数据。
三十六、props
使用场景: 父子传参
const A = {
template: `<div @click='emitData'>{{childData}}</div>`,
props: ['childData'],
methods: {
emitData() {
this.$emit('emitChildData', 'data from child')
}
},
}
new Vue({
el: '#app',
components: {
A
},
template: `<A :childData='parentData' @emitChildData='getChildData'></A>`,
data() {
return {
parentData: 'data from parent'
}
},
methods: {
getChildData(v) {
console.log(v);
}
}
})
从当前例子中可以看出,数据父传子是通过:childData='parentData'
的方式,数据子传父是通过this.$emit('emitChildData', 'data from child')
的方式,然后,父组件通过@emitChildData='getChildData'
的方式进行获取。
1、父组件render
函数
new Vue
中传入的模板template
经过遍历生成的render
函数如下:
with(this) {
return _c('A', {
attrs: {
"childData": parentData
},
on: {
"emitChildData": getChildData
}
})
}
其中data
部分有attrs
和on
来描述属性和方法。
在通过createComponent
创建组件vnode
的过程中,会通过const propsData = extractPropsFromVNodeData(data, Ctor, tag)
的方式获取props
,通过const listeners = data.on
的方式获取listeners
,最后将其作为参数通过new VNode(options)
的方式实例化组件vnode
。
2、子组件渲染
在通过const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )
创建组件实例的过程中,会执行到组件继承自Vue
的._init
方法,通过initEvents
将事件处理后存储到vm._events
中,通过initProps
将childData
赋值到子组件A
的vm
实例上,并进行响应式处理,让其可以通过vm.childData
的方式访问,并且数据发生变化时视图也可以发生改变。
组件模板编译后对应的render
函数是:
with(this) {
return _c('div', {
on: {
"click": emitData
}
}, [_v(_s(childData))])
}
在createElm
完成节点的创建后,在invokeCreateHooks(vnode, insertedVnodeQueue)
阶段,给DOM
原生节点节点绑定emitData
。
3、this.$emit
在点击执行this.$emit
时,会通过var cbs = vm._events[event]
取出_events
中的事件进行执行。
至此,父组件中的传递的数据就在子组件中可以通过this.xxx
的方式获得,也可以通过this.$emit
的方式将子组件中的数据传递给父组件。
prop
数据发生改变引起视图变化的底层逻辑请参考vue2从数据变化到视图变化:props引起视图变化详解
三十七、$attrs
和$listeners
使用场景: 父子组件非props
属性和非native
方法传递
// main.js文件
import Vue from "vue";
const B = {
template: `<div @click="emitData">{{ formParentData }}</div>`,
data() {
return {
formParentData: ''
}
},
inheritAttrs: false,
created() {
this.formParentData = this.$attrs;
console.log(this.$attrs, '--------------a-component-$attrs')
console.log(this.$listeners, '--------------b-component-$listeners')
},
methods: {
emitData() {
this.$emit('onFun', 'form B component')
}
},
}
const A = {
template: `<B v-bind='$attrs' v-on='$listeners'></B>`,
components: {
B,
},
props: ['propData'],
inheritAttrs: false,
created() {
console.log(this.$attrs, '--------------b-component-$attrs')
console.log(this.$listeners, '--------------b-component-$listeners')
}
}
new Vue({
el: '#app',
components: {
A,
},
template: `<A :attrData='parentData' :propData='parentData' @click.native="nativeFun" @onFun="onFun"></A>`,
data() {
return {
parentData: 'msg'
}
},
methods: {
nativeFun() {
console.log('方法A');
},
onFun(v) {
console.log('方法B', v);
},
}
})
当前例子中,new Vue
的template
模板中有attrData
、propData
、click.native
和onFun
在进行传递。实际运行后,在A
组件中this.$attrs
为{attrData: 'msg'}
,this.$listeners
为{onFun:f(...)}
。在A
组件中通过v-bind='$attrs'
和v-on='$listeners'
的方式继续进行属性和方法的传递,在B
组件中就可以获取到A
组件中传入的$attrs
和$listeners
。
当前例子中完成了非props
属性和非native
方法的传递,并且通过v-bind='$attrs'
和v-on='$listeners'
的方式实现了属性和方法的跨层级传递。
同时通过this.$emit
的方法触发了根节点中onFun
事件。
关于例子中的inheritAttrs: false
,默认情况下父作用域的不被认作props
的attribute
绑定将会“回退”且作为普通的HTML
属性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置inheritAttrs
到false
,这些默认行为将会被去掉。
三十八、$parent
和$children
使用场景: 利用父子关系进行数据的获取或者方法的调用
const A = {
template: `<div @click="changeParentData">{{childRandom}}</div>`,
data() {
return {
childRandom: Math.random()
}
},
mounted() {
console.log(this.$parent.parentCount, '--child-created--'); // 获取父组件中的parentCount
},
methods: {
changeParentData() {
console.log(this.$parent); // 打印当前实例的$parent
this.$parent.changeParentData(); // 调用当前父级中的方法`changeParentData`
},
changeChildData() {
this.childRandom = Math.random();
}
}
}
const B = {
template: `<div>b-component</div>`,
}
new Vue({
el: '#app',
components: {
A,
B,
},
template: `<div><A></A><B></B><p>{{parentCount}}</p><button @click="changeChildrenData">修改子组件数据</button></div>`,
data() {
return {
parentCount: 1
}
},
mounted() {
console.log(this.$children[0].childRandom, '--parent-created--'); // 获取第一个子组件中的childRandom
},
methods: {
changeParentData() {
this.parentCount++;
},
changeChildrenData() {
console.log(this.$children); // 此时有两个子组件
this.$children[0].changeChildData(); // 调起第一个子组件中的'changeChildData'方法
}
}
})
在当前例子中,父组件可以通过this.$children
获取所有的子组件,这里有A
组件和B
组件,可以通过this.$children[0].childRandom
的方式获取子组件A
中的数据,也可以通过this.$children[0].changeChildData()
的方式调起子组件A
中的方法。
子组件可以通过this.$parent
的方式获取父组件,可以通过this.$parent.parentCount
获取父组件中的数据,也可以通过this.$parent.changeParentData()
的方式修改父组件中的数据。
Vue
中$parent
和$children
父子关系的底层构建请参考杂谈:������/parent/children的底层逻辑
三十九、inject
和provide
使用场景:嵌套组件多层级传参
const B = {
template: `<div>{{parentData1}}{{parentData2}}</div>`,
inject: ['parentData1', 'parentData2'],
}
const A = {
template: `<B></B>`,
components: {
B,
},
}
new Vue({
el: '#app',
components: {
A,
},
template: `<A></A>`,
provide: {
parentData1: {
name: 'name-2',
age: 30
},
parentData2: {
name: 'name-2',
age: 29
},
}
})
例子中在new Vue
的时候通过provide
提供了两个数据来源parentData1
和parentData2
,然后跨了一个A
组件在B
组件中通过inject
注入了这两个数据。
1、initProvide
在执行组件内部的this._init
初始化方法时,会执行到initProvide
逻辑:
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
如果在当前vm.$options
中存在provide
,会将其执行结果赋值给vm._provided
。
2、initInjections
function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
如果当前组件中有选项inject
,会以while
循环的方式不断在source = source.$parent
中寻找_provided
,然后获取到祖先组件中提供的数据源,这是实现祖先组件向所有子孙后代注入依赖的核心。
四十、Vue
项目能做的性能优化
1、v-if
和v-show
- 频繁切换时使用
v-show
,利用其缓存特性 - 首屏渲染时使用
v-if
,如果为false
则不进行渲染
2、v-for
的key
- 列表变化时,循环时使用唯一不变的
key
,借助其本地复用策略 - 列表只进行一次渲染时,
key
可以采用循环的index
3、侦听器和计算属性
- 侦听器
watch
用于数据变化时引起其他行为 - 多使用
compouter
计算属性顾名思义就是新计算而来的属性,如果依赖的数据未发生变化,不会触发重新计算
Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
default’ in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === ‘function’
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== ‘production’) {
warn(Injection "${key}" not found
, vm)
}
}
}
return result
}
}
如果当前组件中有选项`inject`,会以`while`循环的方式不断在`source = source.$parent`中寻找`_provided`,然后获取到祖先组件中提供的数据源,这是实现祖先组件向所有子孙后代注入依赖的核心。
### 四十、`Vue`项目能做的性能优化
#### 1、`v-if`和`v-show`
* 频繁切换时使用`v-show`,利用其缓存特性
* 首屏渲染时使用`v-if`,如果为`false`则不进行渲染
#### 2、`v-for`的`key`
* 列表变化时,循环时使用唯一不变的`key`,借助其本地复用策略
* 列表只进行一次渲染时,`key`可以采用循环的`index`
#### 3、侦听器和计算属性
* 侦听器`watch`用于数据变化时引起其他行为
* 多使用`compouter`计算属性顾名思义就是新计算而来的属性,如果依赖的数据未发生变化,不会触发重新计算
## Vue 面试题
1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?
[外链图片转存中...(img-KLCvTnxB-1718770165097)]
原文地址:https://blog.csdn.net/2401_85124011/article/details/139799159
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!