校招前端vue面试题集锦
创始人
2025-05-28 06:02:10
0

为什么在 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 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

Vuex 为什么要分模块并且加命名空间

  • 模块 : 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块
  • 命名空间 :默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名

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 子组件和父组件执行顺序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

写过自定义指令吗 原理是什么

指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。

自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind

1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。

原理

1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性

2.通过 genDirectives 生成指令代码

3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子

4.当执行指令对应钩子函数时,调用对应指令定义的方法

Vue模版编译原理知道吗,能简单说一下吗?

简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

  • 生成AST树
  • 优化
  • codegen

首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的AST树转换为可执行的代码

参考 前端进阶面试题详细解答

描述下Vue自定义指令

在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
一般需要对DOM元素进行底层操作时使用,尽量只用来操作 DOM展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定v-model的值也不会同步更新;如必须修改可以在自定义指令中使用keydown事件,在vue组件中使用 change事件,回调中修改vue数据;

(1)自定义指令基本内容

  • 全局定义:Vue.directive("focus",{})

  • 局部定义:directives:{focus:{}}

  • 钩子函数:指令定义对象提供钩子函数

    o bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

    o inSerted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。

    o update:所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。

    o ComponentUpdate:指令所在组件的 VNode及其子VNode全部更新后调用。

    o unbind:只调用一次,指令与元素解绑时调用。

  • 钩子函数参数
    o el:绑定元素

    o bing: 指令核心对象,描述指令全部信息属性

    o name

    o value

    o oldValue

    o expression

    o arg

    o modifers

    o vnode 虚拟节点

    o oldVnode:上一个虚拟节点(更新钩子函数中才有用)

(2)使用场景

  • 普通DOM元素进行底层操作的时候,可以使用自定义指令

  • 自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。

(3)使用案例

初级应用:

  • 鼠标聚焦
  • 下拉菜单
  • 相对时间转换
  • 滚动动画

高级应用:

  • 自定义指令实现图片懒加载
  • 自定义指令集成第三方插件

Vue的生命周期方法有哪些

  1. Vue 实例有一个完整的生命周期,也就是从开始创建初始化数据编译模版挂载Dom -> 渲染更新 -> 渲染卸载等一系列过程,我们称这是Vue的生命周期
  2. Vue生命周期总共分为8个阶段创建前/后载入前/后更新前/后销毁前/后

beforeCreate => created => beforeMount => Mounted => beforeUpdate => updated => beforeDestroy => destroyedkeep-alive下:activated deactivated

生命周期vue2生命周期vue3描述
beforeCreatebeforeCreate在实例初始化之后,数据观测(data observer) 之前被调用。
createdcreated实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
beforeMountbeforeMount在挂载开始之前被调用:相关的 render 函数首次被调用
mountedmountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdatebeforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
updatedupdated由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
beforeDestroybeforeUnmount实例销毁之前调用。在这一步,实例仍然完全可用
destroyedunmounted实例销毁后调用。调用后, Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

其他几个生命周期

生命周期vue2生命周期vue3描述
activatedactivatedkeep-alive专属,组件被激活时调用
deactivateddeactivatedkeep-alive专属,组件被销毁时调用
errorCapturederrorCaptured捕获一个来自子孙组件的错误时被调用
-renderTracked调试钩子,响应式依赖被收集时调用
-renderTriggered调试钩子,响应式依赖被触发时调用
-serverPrefetchssr only,组件实例在服务器上被渲染前调用
  1. 要掌握每个生命周期内部可以做什么事
  • beforeCreate 初始化vue实例,进行数据观测。执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
  • created 组件初始化完毕,可以访问各种数据,获取接口数据等
  • beforeMount 此阶段vm.el虽已完成DOM初始化,但并未挂载在el选项上
  • mounted 实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate 更新前,可用于获取更新前各种状态。此时view层还未更新,可用于获取更新前各种状态。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated 完成view层的更新,更新后,所有状态已是最新。可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
  • destroyed 可以执行一些优化操作,清空定时器,解除绑定事件
  • vue3 beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消
  • vue3 unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

{{name}}
  1. 组合式API生命周期钩子

你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

下表包含如何在 setup() 内部调用生命周期钩子:

选项式 APIHook inside setup
beforeCreate不需要*
created不需要*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写

export default {setup() {// mountedonMounted(() => {console.log('Component is mounted!')})}
}

setupcreated谁先执行?

  • beforeCreate:组件被创建出来,组件的methodsdata还没初始化好
  • setup:在beforeCreatecreated之间执行
  • created:组件被创建出来,组件的methodsdata已经初始化好了

由于在执行setup的时候,created还没有创建好,所以在setup函数内我们是无法使用datamethods的。所以vue为了让我们避免错误的使用,直接将setup函数内的this执行指向undefined

import { ref } from "vue"
export default {// setup函数是组合api的入口函数,注意在组合api中定义的变量或者方法,要在template响应式需要return{}出去setup(){let count = ref(1)function myFn(){count.value +=1}return {count,myFn}},}
  1. 其他问题
  • 什么是vue生命周期? Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

  • vue生命周期的作用是什么? 它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

  • vue生命周期总共有几个阶段? 它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。

  • 第一次页面加载会触发哪几个钩子? 会触发下面这几个beforeCreatecreatedbeforeMountmounted

  • 你的接口请求一般放在哪个生命周期中? 接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created

  • DOM 渲染在哪个周期中就已经完成?mounted中,

    • 注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
    mounted: function () {this.$nextTick(function () {// Code that will run only after the// entire view has been rendered})}
    
### keep-alive 中的生命周期哪些keep-alive是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,防止重复渲染DOM。如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated钩子函数。### 什么是 MVVM?Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于2005年在他的博客上发表MVVM 源自于经典的 Model–View–Controller(MVC)模式  ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用(1)View 层View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。(2)Model 层Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。(3)ViewModel 层ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:(1)View 层```javascript

{{message}}

(2)ViewModel 层

var app = new Vue({el: '#app',data: {  // 用于描述视图状态   message: 'Hello Vue!', },methods: {  // 用于描述视图行为  showMessage(){let vm = this;alert(vm.message);}},created(){let vm = this;// Ajax 获取 Model 层的数据ajax({url: '/your/server/data/api',success(res){vm.message = res;}});}
})

(3) Model 层

{"url": "/your/server/data/api","res": {"success": true,"name": "IoveC","domain": "www.cnblogs.com"}
}

Vue组件data为什么必须是个函数?

  • 根实例对象data可以是对象也可以是函数 (根实例是单例),不会产生数据污染情况
  • 组件实例对象data必须为函数 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数,

简版理解

// 1.组件的渲染流程 调用Vue.component -> Vue.extend -> 子类 -> new 子类
// Vue.extend 根据用户定义产生一个新的类
function Vue() {}
function Sub() { // 会将data存起来this.data = this.constructor.options.data();
}
Vue.extend = function(options) {Sub.options = options; // 静态属性return Sub;
}
let Child = Vue.extend({data:()=>( { name: 'zf' })
});// 两个组件就是两个实例, 希望数据互不感染
let child1 = new Child();
let child2 = new Child();console.log(child1.data.name);
child1.data.name = 'poetry';
console.log(child2.data.name);// 根不需要 任何的合并操作   根才有vm属性 所以他可以是函数和对象  但是组件mixin他们都没有vm 所以我就可以判断 当前data是不是个函数

相关源码

// 源码位置 src/core/global-api/extend.js
export function initExtend (Vue: GlobalAPI) {Vue.extend = function (extendOptions: Object): Function {extendOptions = extendOptions || {}const Super = thisconst SuperId = Super.cidconst cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})if (cachedCtors[SuperId]) {return cachedCtors[SuperId]}const name = extendOptions.name || Super.options.nameif (process.env.NODE_ENV !== 'production' && name) {validateComponentName(name)}const Sub = function VueComponent (options) {this._init(options)}// 子类继承大Vue父类的原型Sub.prototype = Object.create(Super.prototype)Sub.prototype.constructor = SubSub.cid = cid++Sub.options = mergeOptions(Super.options,extendOptions)Sub['super'] = Super// For props and computed properties, we define the proxy getters on// the Vue instances at extension time, on the extended prototype. This// avoids Object.defineProperty calls for each instance created.if (Sub.options.props) {initProps(Sub)}if (Sub.options.computed) {initComputed(Sub)}// allow further extension/mixin/plugin usageSub.extend = Super.extendSub.mixin = Super.mixinSub.use = Super.use// create asset registers, so extended classes// can have their private assets too.ASSET_TYPES.forEach(function (type) {Sub[type] = Super[type]})// enable recursive self-lookupif (name) { Sub.options.components[name] = Sub // 记录自己 在组件中递归自己  -> jsx}// keep a reference to the super options at extension time.// later at instantiation we can check if Super's options have// been updated.Sub.superOptions = Super.optionsSub.extendOptions = extendOptionsSub.sealedOptions = extend({}, Sub.options)// cache constructorcachedCtors[SuperId] = Subreturn Sub}
}

v-model 可以被用在自定义组件上吗?如果可以,如何使用?

可以。v-model 实际上是一个语法糖,如:

实际上相当于:

用在自定义组件上也是同理:

相当于:

显然,custom-input 与父组件的交互如下:

  1. 父组件将searchText变量传入custom-input 组件,使用的 prop 名为value
  2. custom-input 组件向父组件传出名为input的事件,父组件将接收到的值赋值给searchText

所以,custom-input 组件的实现应该类似于这样:

Vue.component('custom-input', {props: ['value'],template: `      `
})

组件通信

组件通信的方式如下:

(1) props  /   $emit

父组件通过props向子组件传递数据,子组件通过$emit和父组件通信

1. 父组件向子组件传值
  • props只能是父组件向子组件进行传值,props使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
  • props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
  • props属性名规则:若在props中使用驼峰形式,模板中需要使用短横线的形式
// 父组件
// 子组件

2. 子组件向父组件传值
  • $emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。
// 父组件
//子组件

(2)eventBus事件总线($emit / $on

eventBus事件总线适用于父子组件非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信

// event-bus.jsimport Vue from 'vue'
export const EventBus = new Vue()

(2)发送事件 假设有两个兄弟组件firstComsecondCom

firstCom组件中发送事件:

(3)接收事件secondCom组件中发送事件:

在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。

虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

(3)依赖注入(provide / inject)

这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。

provide / inject是Vue提供的两个钩子,和datamethods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法

在父组件中:

provide() { return {     num: this.num  };
}

在子组件中:

inject: ['num']

还可以这样写,这样写就可以访问父组件中的所有属性:

provide() {return {app: this};
}
data() {return {num: 1};
}inject: ['app']
console.log(this.app.num)

注意: 依赖注入所提供的属性是非响应式的。

(3)ref / $refs

这种方式也是实现父子组件之间的通信。

ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。

在子组件中:

export default {data () {return {name: 'JavaScript'}},methods: {sayHello () {console.log('hello')}}
}

在父组件中:


(4)$parent / $children

  • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
  • 使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。

在子组件中:

在父组件中:

// 父组件中

在上面的代码中,子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。 需要注意:

  • 通过$parent访问到的是上一级父组件的实例,可以使用$root来访问根组件的实例
  • 在组件中使用$children拿到的是所有的子组件的实例,它是一个数组,并且是无序的
  • 在根组件#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组
  • $children 的值是数组,而$parent是个对象

(5)$attrs / $listeners

考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢?

如果是用props/$emit来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。

针对上述情况,Vue引入了$attrs / $listeners,实现组件之间的跨代通信。

先来看一下inheritAttrs,它的默认值true,继承所有的父组件属性除props之外的所有属性;inheritAttrs:false 只继承class属性 。

  • $attrs:继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上
  • $listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

A组件(APP.vue):


B组件(Child1.vue):


C 组件 (Child2.vue):


在上述代码中:

  • C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
  • 在B组件中通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)

(6)总结

(1)父子组件间通信

  • 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
  • 通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。
  • 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。

(2)兄弟组件间通信

  • 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
  • 通过 $parent/$refs 来获取到兄弟组件,也可以进行通信。

(3)任意组件之间

  • 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

Vue模版编译原理知道吗,能简单说一下吗?

简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

  • 生成AST树
  • 优化
  • codegen

首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的AST树转换为可执行的代码

Vue生命周期钩子是如何实现的

  • vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法
  • 内部会对钩子函数进行处理,将钩子函数维护成数组的形式

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);}
};// 销毁实例实现
Vue.prototype.$destory = function() {// 触发钩子callHook(vm, 'beforeDestory')// 自身及子节点remove() // 删除依赖watcher.teardown() // 删除监听vm.$off() // 触发钩子callHook(vm, 'destoryed')
}

原理流程图

Computed 和 Watch 的区别

对于Computed:

  • 它支持缓存,只有依赖的数据发生了变化,才会重新计算
  • 不支持异步,当Computed中有异步操作时,无法监听数据的变化
  • computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
  • 如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。

对于Watch:

  • 它不支持缓存,数据变化时,它就会触发相应的操作
  • 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
  • 当一个属性发生变化时,就需要执行相应的操作
  • 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
    • immediate:组件加载立即触发回调函数
    • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。

当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。

总结:

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

运用场景:

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Vue模版编译原理知道吗,能简单说一下吗?

简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

  • 生成AST树
  • 优化
  • codegen

首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的AST树转换为可执行的代码

assets和static的区别

相同点: assetsstatic 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点

不相同点:assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。

建议: 将项目中 template需要的样式文件js文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。

谈谈你对MVVM的理解

为什么要有这些模式,目的:职责划分、分层(将Model层、View层进行分类)借鉴后端思想,对于前端而已,就是如何将数据同步到页面上

MVC模式 代表:Backbone + underscore + jquery

  • 传统的 MVC 指的是,用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新渲染
  • MVVM:传统的前端会将数据手动渲染到页面上, MVVM 模式不需要用户收到操作 dom 元素,将数据绑定到 viewModel 层上,会自动将数据渲染到页面中,视图变化会通知 viewModel层 更新数据。ViewModel 就是我们 MVVM 模式中的桥梁

MVVM模式 映射关系的简化,隐藏了controller

MVVMModel-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModelModel层代表数据模型,View代表UI组件,ViewModelViewModel层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

  • Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。我们可以把Model称为数据层,因为它仅仅关注数据本身,不关心任何行为
  • View: 用户操作界面。当ViewModelModel进行更新的时候,会通过数据绑定更新到View
  • ViewModel: 业务逻辑层,View需要什么数据,ViewModel要提供这个数据;View有某些操作,ViewModel就要响应这些操作,所以可以说它是Model for View.

总结MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。

我们以下通过一个 Vue 实例来说明 MVVM 的具体实现

{{message}}

// ViewModel 层var app = new Vue({el: '#app',data: {  // 用于描述视图状态   message: 'Hello Vue!', },methods: {  // 用于描述视图行为  showMessage(){let vm = this;alert(vm.message);}},created(){let vm = this;// Ajax 获取 Model 层的数据ajax({url: '/your/server/data/api',success(res){vm.message = res;}});}
})
// Model 层{"url": "/your/server/data/api","res": {"success": true,"name": "test","domain": "www.baidu.com"}
}

Vue2.x 响应式数据原理

整体思路是数据劫持+观察者模式

对象内部通过 defineReactive 方法,使用 Object.defineProperty 来劫持各个属性的 settergetter(只会劫持已经存在的属性),数组则是通过重写数组7个方法来实现。当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)

Object.defineProperty基本使用

function observer(value) { // proxy reflectif (typeof value === 'object' && typeof value !== null)for (let key in value) {defineReactive(value, key, value[key]);}
}function defineReactive(obj, key, value) {observer(value);Object.defineProperty(obj, key, {get() { // 收集对应的key 在哪个方法(组件)中被使用return value;},set(newValue) {if (newValue !== value) {observer(newValue);value = newValue; // 让key对应的方法(组件重新渲染)重新执行}}})
}
let obj1 = { school: { name: 'poetry', age: 20 } };
observer(obj1);
console.log(obj1)

源码分析

class Observer {// 观测值constructor(value) {this.walk(value);}walk(data) {// 对象上的所有属性依次进行观测let keys = Object.keys(data);for (let i = 0; i < keys.length; i++) {let key = keys[i];let value = data[key];defineReactive(data, key, value);}}
}
// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {observe(value); // 递归关键// --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止//   思考?如果Vue数据嵌套层级过深 >>性能会受影响Object.defineProperty(data, key, {get() {console.log("获取值");//需要做依赖收集过程 这里代码没写出来return value;},set(newValue) {if (newValue === value) return;console.log("设置值");//需要做派发更新过程 这里代码没写出来value = newValue;},});
}
export function observe(value) {// 如果传过来的是对象或者数组 进行属性劫持if (Object.prototype.toString.call(value) === "[object Object]" ||Array.isArray(value)) {return new Observer(value);}
}

说一说你对vue响应式理解回答范例

  • 所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制
  • MVVM框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理
  • vue为例说明,通过数据响应式加上虚拟DOMpatch算法,开发人员只需要操作数据,关心业务,完全不用接触繁琐的DOM操作,从而大大提升开发效率,降低开发难度
  • vue2中的数据响应式会根据数据类型来做不同处理,如果是 对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖数组对象原型的7个变更方法 ,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的MapSet这些数据结构不支持等问题
  • 为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6Proxy代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,第三方的扩展开发起来更加灵活了

相关内容

热门资讯

如何挺过创业最初期那段日子? ... 如何挺过创业最初期那段日子?作者:方滔分类:网赚问答发布于:2020-6-次浏览0条评论2013年底...
自媒体是未来最值得创业的行业,...   文|韦彪图|网络今年疫情爆发以来,我踏入了自媒体行业。初入自媒体的我,一股干劲,对写作情有独钟,...
马斯克官宣:将离开特朗普政府 据央视新闻消息,当地时间5月28日,美国政府效率部负责人埃隆·马斯克在其社交媒体平台X上发文表示,在...
化妆品原料参考数据相关问答 转自:中国医药报 问:《〈国际化妆品安全评估数据索引〉收录的部分原料使用信息》的制定背景是什么...
关注|8个中央生态环保督察组全... 第三轮第四批中央生态环保督察全部实现进驻记者28日从生态环境部获悉,到当日下午,第三轮第四批8个中央...
富邦科技:公司多项业务进展及成... 投资者提问:固氮杆菌产品在国内大田作物(如玉米、水稻)的示范田效果如何?用户反馈的核心痛点(如成本、...
内蒙古第十届百县健身气功交流赛... 近日,内蒙古自治区第十届百县健身气功交流比赛在巴彦淖尔市乌拉特前旗启幕,吸引了来自全区12个盟市、1...
券商晨会精华:铝业公司迎来弱供...   炒股就看金麒麟分析师研报,权威,专业,及时,全面,助您挖掘潜力主题机会! 财联社5月29日讯,...
把爱送给小患者 来源:新华网 5月28日,中国红十字基金会的志愿者和来自新疆维吾尔自治区阿克苏地区的先天性心脏病患儿...
理论热点丨准确认识绿色消费升级... 转自:中国经济时报核心观点  绿色消费作为可持续消费模式,是连接消费端与产业端的重要纽带。近年来,我...
西太平洋银行:美国贸易法庭的裁... 格隆汇5月29日|西太平洋银行称,美国贸易法院阻止特朗普全球关税的裁决是一个司法举动,而不是政府的决...
北京警方:诈骗手段发生变化 冒... 转自:千龙网北京市公安局刑侦总队5月28日通报,今年以来,北京警方共破获电诈案件2600余起,同比上...
用对方法才能有效改善黑头 转自:中国医药报 黑头作为常见的皮肤问题,困扰着很多人。任何年龄、肤质的人,都有可能出现黑头。...
【ICH基础知识500问】E1... 转自:中国医药报 问:什么是QT/QTc间期?这个间期延长意味着什么? 答:QT间期是指...
美以插手加沙物资分发 现场混乱... 转自:央视联合国官员28日在日内瓦表示,由美国和以色列支持的私营援助组织“加沙人道主义基金会”27日...
查尔斯三世演讲后,特朗普喊话加... 姜蔼玲 闫佩云针对美国总统特朗普对加拿大产品征税且威胁要让加拿大成为美国“第51个州”等言行,英国国...
时政微观察丨总书记深情寄望“小... 来源:央视新闻客户端“‘六一’国际儿童节快要到了,祝全国的小朋友们节日快乐!”5月27日,习近平总书...
“95后”成为线上购险率最高的... 来源:@经济观察报微博记者 姜鑫以“95后”为代表的20—30岁年轻群体,成为线上购险率最高的客群。...
誓让家乡换新颜——记庆阳市西峰...   原标题:誓让家乡换新颜——记庆阳市西峰区肖金镇左咀村党总支书记兼村委会主任左新郎  新甘肃·甘肃...
新湖期货:PTA延续去库格局 自修复4月初跳空缺口以后,PTA上方压力渐显,价格有所回落。尽管当下PTA供需两端都有弱化,但二季度...