关于前端:校招前端vue面试题集锦

34次阅读

共计 24647 个字符,预计需要花费 62 分钟才能阅读完成。

为什么在 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 形容
beforeCreate beforeCreate 在实例初始化之后,数据观测(data observer) 之前被调用。
created created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算,watch/event 事件回调。这里没有$el
beforeMount beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用
mounted mounted el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用该钩子
beforeUpdate beforeUpdate 组件数据更新之前调用,产生在虚构 DOM 打补丁之前
updated updated 因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用该钩子
beforeDestroy beforeUnmount 实例销毁之前调用。在这一步,实例依然齐全可用
destroyed unmounted 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

其余几个生命周期

生命周期 vue2 生命周期 vue3 形容
activated activated keep-alive专属,组件被激活时调用
deactivated deactivated keep-alive专属,组件被销毁时调用
errorCaptured errorCaptured 捕捉一个来自子孙组件的谬误时被调用
renderTracked 调试钩子,响应式依赖被收集时调用
renderTriggered 调试钩子,响应式依赖被触发时调用
serverPrefetch ssr only,组件实例在服务器上被渲染前调用
  1. 要把握每个生命周期外部能够做什么事
  2. beforeCreate 初始化 vue 实例,进行数据观测。执行时组件实例还未创立,通常用于插件开发中执行一些初始化工作
  3. created 组件初始化结束,能够拜访各种数据,获取接口数据等
  4. beforeMount 此阶段 vm.el 虽已实现 DOM 初始化,但并未挂载在 el 选项上
  5. mounted 实例曾经挂载实现,能够进行一些 DOM 操作
  6. beforeUpdate 更新前,可用于获取更新前各种状态。此时 view 层还未更新,可用于获取更新前各种状态。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  7. updated 实现 view 层的更新,更新后,所有状态已是最新。能够执行依赖于 DOM 的操作。然而在大多数状况下,你应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  8. destroyed 能够执行一些优化操作, 清空定时器,解除绑定事件
  9. vue3 beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的勾销
  10. vue3 unmounted:销毁一个实例。可清理它与其它实例的连贯,解绑它的全副指令及事件监听器
<div id="app">{{name}}</div>
<script>
    const vm = new Vue({data(){return {name:'poetries'}
        },
        el: '#app',
        beforeCreate(){// 数据观测(data observer) 和 event/watcher 事件配置之前被调用。console.log('beforeCreate');
        },
        created(){
            // 属性和办法的运算,watch/event 事件回调。这里没有 $el
            console.log('created')
        },
        beforeMount(){
            // 相干的 render 函数首次被调用。console.log('beforeMount')
        },
        mounted(){
            // 被新创建的 vm.$el 替换
            console.log('mounted')
        },
        beforeUpdate(){
            //  数据更新时调用,产生在虚构 DOM 从新渲染和打补丁之前。console.log('beforeUpdate')
        },
        updated(){
            //  因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用该钩子。console.log('updated')
        },
        beforeDestroy(){
            // 实例销毁之前调用 实例依然齐全可用
            console.log('beforeDestroy')
        },
        destroyed(){ 
            // 所有货色都会解绑定,所有的事件监听器会被移除
            console.log('destroyed')
        }
    });
    setTimeout(() => {
        vm.name = 'poetry';
        setTimeout(() => {vm.$destroy()  
        }, 1000);
    }, 1000);
</script>
  1. 组合式 API 生命周期钩子

你能够通过在生命周期钩子后面加上“on”来拜访组件的生命周期钩子。

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

选项式 API Hook inside setup
beforeCreate 不须要 *
created 不须要 *
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

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

export default {setup() {
    // mounted
    onMounted(() => {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. 其余问题
  2. 什么是 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 层

<div id=”app”>

<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>

</div>

(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 = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {return cachedCtors[SuperId]
}

const name = extendOptions.name || Super.options.name
if (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 = Sub
Sub.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 usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.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-lookup
if (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.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)

// cache constructor
cachedCtors[SuperId] = Sub
return Sub

}
}




### v-model 能够被用在自定义组件上吗?如果能够,如何应用?能够。v-model 实际上是一个语法糖,如:

<input v-model=”searchText”>


实际上相当于:

<input
v-bind:value=”searchText”
v-on:input=”searchText = $event.target.value”


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

<custom-input v-model=”searchText”>


相当于:

<custom-input
v-bind:value=”searchText”
v-on:input=”searchText = $event”

</custom-input>


显然,custom-input 与父组件的交互如下:1. 父组件将 `searchText` 变量传入 custom-input 组件,应用的 prop 名为 `value`;2. custom-input 组件向父组件传出名为 `input` 的事件,父组件将接管到的值赋值给 `searchText`;所以,custom-input 组件的实现应该相似于这样:

Vue.component(‘custom-input’, {
props: [‘value’],
template: <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" >
})




### 组件通信

组件通信的形式如下:###(1)props  /   $emit

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

##### 1. 父组件向子组件传值

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

// 父组件
<template>
<div id=”father”>

<son :msg="msgData" :fn="myFunction"></son>

</div>
</template>

<script>
import son from “./son.vue”;
export default {
name: father,
data() {

msgData: "父组件数据";

},
methods: {

myFunction() {console.log("vue");
},

},
components: {son},
};
</script>

// 子组件
<template>
<div id=”son”>

<p>{{msg}}</p>
<button @click="fn"> 按钮 </button>

</div>
</template>
<script>
export default {name: “son”, props: [“msg”, “fn”] };
</script>


##### 2. 子组件向父组件传值

- `$emit` 绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过 `v-on` 监听并接管参数。

// 父组件
<template>
<div class=”section”>

<com-article
  :articles="articleList"
  @onEmitIndex="onEmitIndex"
></com-article>
<p>{{currentIndex}}</p>

</div>
</template>

<script>
import comArticle from “./test/article.vue”;
export default {
name: “comArticle”,
components: {comArticle},
data() {

return {currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] };

},
methods: {

onEmitIndex(idx) {this.currentIndex = idx;},

},
};
</script>

// 子组件
<template>
<div>

<div
  v-for="(item, index) in articles"
  :key="index"
  @click="emitIndex(index)"
>
  {{item}}
</div>

</div>
</template>

<script>
export default {
props: [“articles”],
methods: {

emitIndex(index) {this.$emit("onEmitIndex", index); // 触发父组件的办法,并传递参数 index
},

},
};
</script>


###(2)eventBus 事件总线(`$emit / $on`)`eventBus` 事件总线实用于 ** 父子组件 **、** 非父子组件 ** 等之间的通信,应用步骤如下:**(1)创立事件核心治理组件之间的通信 **

// event-bus.js

import Vue from ‘vue’
export const EventBus = new Vue()


**(2)发送事件 ** 假如有两个兄弟组件 `firstCom` 和 `secondCom`:

<template>
<div>

<first-com></first-com>
<second-com></second-com>

</div>
</template>

<script>
import firstCom from “./firstCom.vue”;
import secondCom from “./secondCom.vue”;
export default {components: { firstCom, secondCom} };
</script>


在 `firstCom` 组件中发送事件:

<template>
<div>

<button @click="add"> 加法 </button>

</div>
</template>

<script>
import {EventBus} from “./event-bus.js”; // 引入事件核心

export default {
data() {

return {num: 0};

},
methods: {

add() {EventBus.$emit("addition", { num: this.num++});
},

},
};
</script>


**(3)接管事件 ** 在 `secondCom` 组件中发送事件:

<template>
<div> 求和: {{count}}</div>
</template>

<script>
import {EventBus} from “./event-bus.js”;
export default {
data() {

return {count: 0};

},
mounted() {

EventBus.$on("addition", (param) => {this.count = this.count + param.num;});

},
};
</script>


在上述代码中,这就相当于将 `num` 值存贮在了事件总线中,在其余组件中能够间接拜访。事件总线就相当于一个桥梁,不必组件通过它来通信。尽管看起来比较简单,然而这种办法也有不变之处,如果我的项目过大,应用这种形式进行通信,前期保护起来会很艰难。###(3)依赖注入(provide / inject)这种形式就是 Vue 中的 ** 依赖注入 **,该办法用于 ** 父子组件之间的通信 **。当然这里所说的父子不肯定是真正的父子,也能够是祖孙组件,在层数很深的状况下,能够应用这种办法来进行传值。就不必一层一层的传递了。`provide / inject` 是 Vue 提供的两个钩子,和 `data`、`methods` 是同级的。并且 `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')
}

}
}


在父组件中:

<template>
<child ref=”child”></component-a>
</template>
<script>
import child from “./child.vue”;
export default {
components: {child},
mounted() {

console.log(this.$refs.child.name); // JavaScript
this.$refs.child.sayHello(); // hello

},
};
</script>


###(4)`$parent / $children`

- 应用 `$parent` 能够让组件拜访父组件的实例(拜访的是上一级父组件的属性和办法)- 应用 `$children` 能够让组件拜访子组件的实例,然而,`$children` 并不能保障程序,并且拜访的数据也不是响应式的。在子组件中:

<template>
<div>

<span>{{message}}</span>
<p> 获取父组件的值为: {{parentVal}}</p>

</div>
</template>

<script>
export default {
data() {

return {message: "Vue"};

},
computed: {

parentVal() {return this.$parent.msg;},

},
};
</script>


在父组件中:

// 父组件中
<template>
<div class=”hello_world”>

<div>{{msg}}</div>
<child></child>
<button @click="change"> 点击扭转子组件值 </button>

</div>
</template>

<script>
import child from “./child.vue”;
export default {
components: {child},
data() {

return {msg: "Welcome"};

},
methods: {

change() {
  // 获取到子组件
  this.$children[0].message = "JavaScript";
},

},
};
</script>


在下面的代码中,子组件获取到了父组件的 `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`):

<template>
<div id=”app”>

// 此处监听了两个事件,能够在 B 组件或者 C 组件中间接触发
<child1
  :p-child1="child1"
  :p-child2="child2"
  @test1="onTest1"
  @test2="onTest2"
></child1>

</div>
</template>
<script>
import Child1 from “./Child1.vue”;
export default {
components: {Child1},
methods: {

onTest1() {console.log("test1 running");
},
onTest2() {console.log("test2 running");
},

},
};
</script>


B 组件(`Child1.vue`):

<template>
<div class=”child-1″>

<p>props: {{pChild1}}</p>
<p>$attrs: {{$attrs}}</p>
<child2 v-bind="$attrs" v-on="$listeners"></child2>

</div>
</template>
<script>
import Child2 from “./Child2.vue”;
export default {
props: [“pChild1”],
components: {Child2},
inheritAttrs: false,
mounted() {

this.$emit("test1"); // 触发 APP.vue 中的 test1 办法

},
};
</script>


C 组件 (`Child2.vue`):

<template>
<div class=”child-2″>

<p>props: {{pChild2}}</p>
<p>$attrs: {{$attrs}}</p>

</div>
</template>
<script>
export default {
props: [“pChild2”],
inheritAttrs: false,
mounted() {

this.$emit("test2"); // 触发 APP.vue 中的 test2 办法

},
};
</script>


在上述代码中:- 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` 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)

<script>

// Vue.options 中会寄存所有全局属性

// 会用本身的 + Vue.options 中的属性进行合并
// Vue.mixin({//     beforeCreate() {//         console.log('before 0')
//     },
// })
debugger;
const vm = new Vue({
    el: '#app',
    beforeCreate: [function() {console.log('before 1')
        },
        function() {console.log('before 2')
        }
    ]
});
console.log(vm);

</script>


相干代码如下

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')

}


** 原理流程图 **

![](https://img-blog.csdnimg.cn/img_convert/0de63c1d02a9bc77d428c5c4a66d20de.png)



### 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 的区别

** 相同点:** `assets` 和 `static` 两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点

** 不相同点:**`assets` 中寄存的动态资源文件在我的项目打包时,也就是运行 `npm run build` 时会将 `assets` 中搁置的动态资源文件进行打包上传,所谓打包简略点能够了解为压缩体积,代码格式化。而压缩后的动态资源文件最终也都会搁置在 `static` 文件中跟着 `index.html` 一起上传至服务器。`static` 中搁置的动态资源文件就不会要走打包压缩格式化等流程,而是间接进入打包好的目录,间接上传至服务器。因为防止了压缩间接进行上传,在打包时会进步肯定的效率,然而 `static` 中的资源文件因为没有进行压缩等操作,所以文件的体积也就绝对于 `assets` 中打包后的文件提交较大点。在服务器中就会占据更大的空间。** 倡议:** 将我的项目中 `template` 须要的款式文件 js 文件等都能够搁置在 `assets` 中,走打包这一流程。缩小体积。而我的项目中引入的第三方的资源文件如 `iconfoont.css` 等文件能够搁置在 `static` 中,因为这些引入的第三方文件曾经通过解决,不再须要解决,间接上传。## 谈谈你对 MVVM 的了解

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

**MVC 模式 ** 代表:`Backbone` + `underscore` + `jquery`

![](https://img-blog.csdnimg.cn/img_convert/7be9ea159299032da61ddf4239ebe343.png)

* 传统的 `MVC` 指的是, 用户操作会申请服务端路由,路由会调用对应的控制器来解决,控制器会获取数据。将后果返回给前端, 页面从新渲染
* `MVVM`:传统的前端会将数据手动渲染到页面上, `MVVM` 模式不须要用户收到操作 `dom` 元素, 将数据绑定到 `viewModel` 层上,会主动将数据渲染到页面中,视图变动会告诉 `viewModel` 层 更新数据。`ViewModel` 就是咱们 `MVVM` 模式中的桥梁

**MVVM 模式 ** 映射关系的简化,暗藏了 `controller`

![](https://img-blog.csdnimg.cn/img_convert/3162a144e02f7d3b32fc4be403e0ef2c.png)

> `MVVM` 是 `Model-View-ViewModel` 缩写,也就是把 `MVC` 中的 `Controller` 演变成 `ViewModel`。`Model` 层代表数据模型,`View` 代表 UI 组件,`ViewModel` 是 `View` 和 `Model` 层的桥梁,数据会绑定到 `viewModel` 层并主动将数据渲染到页面中,视图变动的时候会告诉 `viewModel` 层更新数据。* `Model`: 代表数据模型,也能够在 `Model` 中定义数据批改和操作的业务逻辑。咱们能够把 `Model` 称为数据层,因为它仅仅关注数据自身,不关怀任何行为
* `View`: 用户操作界面。当 `ViewModel` 对 `Model` 进行更新的时候,会通过数据绑定更新到 `View`
* `ViewModel`:业务逻辑层,`View` 须要什么数据,`ViewModel` 要提供这个数据;`View` 有某些操作,`ViewModel` 就要响应这些操作,所以能够说它是 `Model for View`.

** 总结 **:`MVVM` 模式简化了界面与业务的依赖,解决了数据频繁更新。`MVVM` 在应用当中,利用双向绑定技术,使得 `Model` 变动时,`ViewModel` 会自动更新,而 `ViewModel` 变动时,`View` 也会主动变动。咱们以下通过一个 `Vue` 实例来阐明 `MVVM` 的具体实现

<!– View 层 –>

<div id=”app”>

<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>

</div>

// 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` 来劫持各个属性的 `setter`、`getter`(只会劫持曾经存在的属性),数组则是通过 ` 重写数组 7 个办法 ` 来实现。当页面应用对应属性时,每个属性都领有本人的 `dep` 属性,寄存他所依赖的 `watcher`(依赖收集),当属性变动后会告诉本人对应的 `watcher` 去更新(派发更新)

**Object.defineProperty 根本应用 **

function observer(value) {// proxy reflect

if (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)


** 源码剖析 **

![](https://img-blog.csdnimg.cn/img_convert/e7a7b704996fdd0393aca3dc1d9552dd.png)

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` 为例阐明,通过数据响应式加上虚构 `DOM` 和 `patch` 算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的 DOM 操作,从而大大晋升开发效率,升高开发难度
* `vue2` 中的数据响应式会依据数据类型来做不同解决,如果是 ** 对象则采纳 `Object.defineProperty()` 的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的 7 个变更办法 **,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用 `Vue.set/delete` 这样非凡的 `api` 能力失效;对于 `es6` 中新产生的 `Map`、`Set` 这些数据结构不反对等问题
* 为了解决这些问题,`vue3` 从新编写了这一部分的实现:利用 `ES6` 的 `Proxy` 代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡 `api`,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的 `reactivity` 包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了

正文完
 0