关于vue.js:腾讯前端常考vue面试题整理

什么是 mixin ?

  • Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
  • 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、 办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
  • 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

Vue中diff算法原理

DOM操作是十分低廉的,因而咱们须要尽量地缩小DOM操作。这就须要找出本次DOM必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用diff算法

vuediff算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归的形式+双指针(头尾都加指针)的形式进行比拟。

简略来说,Diff算法有以下过程

  • 同级比拟,再比拟子节点(依据keytag标签名判断)
  • 先判断一方有子节点和一方没有子节点的状况(如果新的children没有子节点,将旧的子节点移除)
  • 比拟都有子节点的状况(外围diff)
  • 递归比拟子节点
  • 失常Diff两个树的工夫复杂度是O(n^3),但理论状况下咱们很少会进行跨层级的挪动DOM,所以VueDiff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才须要用外围的Diff算法进行同层级比拟。
  • Vue2的外围Diff算法采纳了双端比拟的算法,同时从新旧children的两端开始进行比拟,借助key值找到可复用的节点,再进行相干操作。相比ReactDiff算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅
  • 在创立VNode时就确定其类型,以及在mount/patch的过程中采纳位运算来判断一个VNode的类型,在这个根底之上再配合外围的Diff算法,使得性能上较Vue2.x有了晋升

vue3中采纳最长递增子序列来实现diff优化

答复范例

思路

  • diff算法是干什么的
  • 它的必要性
  • 它何时执行
  • 具体执行形式
  • 拔高:说一下vue3中的优化

答复范例

  1. Vue中的diff算法称为patching算法,它由Snabbdom批改而来,虚构DOM要想转化为实在DOM就须要通过patch办法转换
  2. 最后Vue1.x视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构DOMpatching算法反对,然而这样粒度过细导致Vue1.x无奈承载较大利用;Vue 2.x中为了升高Watcher粒度,每个组件只有一个Watcher与之对应,此时就须要引入patching算法能力准确找到发生变化的中央并高效更新
  3. vuediff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数取得最新的虚构DOM,而后执行patch函数,并传入新旧两次虚构DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM操作
  4. patch过程是一个递归过程,遵循深度优先、同层比拟的策略;以vue3patch为例
  5. 首先判断两个节点是否为雷同同类节点,不同则删除从新创立
  6. 如果单方都是文本则更新文本内容
  7. 如果单方都是元素节点则递归更新子元素,同时更新元素属性
  8. 更新子节点时又分了几种状况

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;
    • 新的子节点是文本,老的子节点是文本则间接更新文本;
    • 新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;
    • 新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节blabla
  9. vue3中引入的更新策略:动态节点标记等

vdom中diff算法的繁难实现

以下代码只是帮忙大家了解diff算法的原理和流程

  1. vdom转化为实在dom
const createElement = (vnode) => {
  let tag = vnode.tag;
  let attrs = vnode.attrs || {};
  let children = vnode.children || [];
  if(!tag) {
    return null;
  }
  //创立元素
  let elem = document.createElement(tag);
  //属性
  let attrName;
  for (attrName in attrs) {
    if(attrs.hasOwnProperty(attrName)) {
      elem.setAttribute(attrName, attrs[attrName]);
    }
  }
  //子元素
  children.forEach(childVnode => {
    //给elem增加子元素
    elem.appendChild(createElement(childVnode));
  })

  //返回实在的dom元素
  return elem;
}
  1. 用繁难diff算法做更新操作
function updateChildren(vnode, newVnode) {
  let children = vnode.children || [];
  let newChildren = newVnode.children || [];

  children.forEach((childVnode, index) => {
    let newChildVNode = newChildren[index];
    if(childVnode.tag === newChildVNode.tag) {
      //深层次比照, 递归过程
      updateChildren(childVnode, newChildVNode);
    } else {
      //替换
      replaceNode(childVnode, newChildVNode);
    }
  })
}

</details>

Vue 子组件和父组件执行程序

加载渲染过程:

  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

v-if、v-show、v-html 的原理

  • v-if会调用addIfCondition办法,生成vnode的时候会疏忽对应节点,render的时候就不会渲染;
  • v-show会生成vnode,render的时候也会渲染成实在节点,只是在render过程中会在节点的属性中批改show属性值,也就是常说的display;
  • v-html会先移除节点下的所有节点,调用html办法,通过addProp增加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

Vue 的父子组件生命周期钩子函数执行程序

  • 渲染程序 :先父后子,实现程序:先子后父
  • 更新程序 :父更新导致子更新,子更新实现后父
  • 销毁程序 :先父后子,实现程序:先子后父

加载渲染过程

beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted子组件先挂载,而后到父组件

子组件更新过程

beforeUpdate->子 beforeUpdate->子 updated->父 updated

父组件更新过程

beforeUpdate->父 updated

销毁过程

beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed

之所以会这样是因为Vue创立过程是一个递归过程,先创立父组件,有子组件就会创立子组件,因而创立时先有父组件再有子组件;子组件首次创立时会增加mounted钩子到队列,等到patch完结再执行它们,可见子组件的mounted钩子是先进入到队列中的,因而等到patch完结执行这些钩子时也先执行。

function patch (oldVnode, vnode, hydrating, removeOnly) { 
    if (isUndef(vnode)) { 
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return 
    }
    let isInitialPatch = false 
    const insertedVnodeQueue = [] // 定义收集所有组件的insert hook办法的数组 // somthing ... 
    createElm( 
        vnode, 
        insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, 
        nodeOps.nextSibling(oldElm) 
    )// somthing... 
    // 最终会顺次调用收集的insert hook 
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
}

function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { 
    // createChildren 会递归创立儿子组件 
    createChildren(vnode, children, insertedVnodeQueue) // something... 
} 
// 将组件的vnode插入到数组中 
function invokeCreateHooks (vnode, insertedVnodeQueue) { 
    for (let i = 0; i < cbs.create.length; ++i) { 
        cbs.create[i](emptyNode, vnode) 
    }
    i = vnode.data.hook // Reuse variable 
    if (isDef(i)) { 
        if (isDef(i.create)) i.create(emptyNode, vnode) 
        if (isDef(i.insert)) insertedVnodeQueue.push(vnode) 
    } 
} 
// insert办法中会顺次调用mounted办法 
insert (vnode: MountedComponentVNode) { 
    const { context, componentInstance } = vnode 
    if (!componentInstance._isMounted) { 
        componentInstance._isMounted = true 
        callHook(componentInstance, 'mounted') 
    } 
}
function invokeInsertHook (vnode, queue, initial) { 
    // delay insert hooks for component root nodes, invoke them after the // element is really inserted 
    if (isTrue(initial) && isDef(vnode.parent)) { 
        vnode.parent.data.pendingInsert = queue 
    } else { 
        for (let i = 0; i < queue.length; ++i) { 
            queue[i].data.hook.insert(queue[i]); // 调用insert办法 
        } 
    } 
}

Vue.prototype.$destroy = function () { 
    callHook(vm, 'beforeDestroy') 
    // invoke destroy hooks on current rendered tree 
    vm.__patch__(vm._vnode, null) // 先销毁儿子 
    // fire destroyed hook 
    callHook(vm, 'destroyed') 
}

Vue模版编译原理

vue中的模板template无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的HTML语法,所有须要将template转化成一个JavaScript函数,这样浏览器就能够执行这一个函数并渲染出对应的HTML元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。

  • 解析阶段:应用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为形象语法树AST。
  • 优化阶段:遍历AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行diff比拟时,间接跳过这一些动态节点,优化runtime的性能。
  • 生成阶段:将最终的AST转化为render函数字符串。

子组件能够间接扭转父组件的数据吗?

子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。

Vue提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。

只能通过 $emit 派发一个自定义事件,父组件接管到后,由父组件批改。

Vue 中给 data 中的对象属性增加一个新的属性时会产生什么?如何解决?

<template> 
   <div>
      <ul>
         <li v-for="value in obj" :key="value"> {{value}} </li>       </ul>       <button @click="addObjB">增加 obj.b</button>    </div>
</template>

<script>
    export default {        data () {           return {               obj: {                   a: 'obj.a'               }           }        },       methods: {           addObjB () {               this.obj.b = 'obj.b'               console.log(this.obj)           }       }   }
</script>

点击 button 会发现,obj.b 曾经胜利增加,然而视图并未刷新。这是因为在Vue实例创立时,obj.b并未申明,因而就没有被Vue转换为响应式的属性,天然就不会触发视图的更新,这时就须要应用Vue的全局 api $set():

addObjB () (
   this.$set(this.obj, 'b', 'obj.b')
   console.log(this.obj)
}

$set()办法相当于手动的去把obj.b解决成一个响应式的属性,此时视图也会跟着扭转了。

diff算法

工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3) ,vue进行优化转化成 O(n)

了解:

  • 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个DOM节点

    • 扩大 v-for 为什么要有 key ,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改DOM),加 key 只会挪动缩小操作DOM。
  • 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
  • 只进行同层比拟,不会进行跨层比拟。

diff算法的优化策略:四种命中查找,四个指针

  1. 旧前与新前(先比结尾,后插入和删除节点的这种状况)
  2. 旧后与新后(比结尾,前插入或删除的状况)
  3. 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
  4. 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)

参考:前端vue面试题具体解答

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

指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。

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

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

2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。

3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。

4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。

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

delete和Vue.delete删除数组的区别

  • delete 只是被删除的元素变成了 empty/undefined 其余的元素的键值还是不变。
  • Vue.delete 间接删除了数组 扭转了数组的键值。

理解nextTick吗?

异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeoutpromise那些),定义了一个异步办法,屡次调用nextTick会将办法存入队列,通过异步办法清空以后队列。

$nextTick 原理及作用

Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种利用。

nextTick 的外围是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 办法来模仿对应的微/宏工作的实现,实质是为了利用 JavaScript 的这些异步回调工作队列来实现 Vue 框架中本人的异步回调队列。

nextTick 不仅是 Vue 外部的异步队列的调用办法,同时也容许开发者在理论我的项目中应用这个办法来满足理论利用中对 DOM 更新数据机会的后续逻辑解决

nextTick 是典型的将底层 JavaScript 执行原理利用到具体案例中的示例,引入异步更新队列机制的起因∶

  • 如果是同步更新,则屡次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,能够缩小一些无用渲染
  • 同时因为 VirtualDOM 的引入,每一次状态发生变化后,状态变动的信号会发送给组件,组件外部应用 VirtualDOM 进行计算得出须要更新的具体的 DOM 节点,而后对 DOM 进行更新操作,每次更新状态后的渲染过程须要更多的计算,而这种无用功也将节约更多的性能,所以异步渲染变得更加至关重要

Vue采纳了数据驱动视图的思维,然而在一些状况下,依然须要操作DOM。有时候,可能遇到这样的状况,DOM1的数据产生了变动,而DOM2须要从DOM1中获取数据,那这时就会发现DOM2的视图并没有更新,这时就须要用到了nextTick了。

因为Vue的DOM操作是异步的,所以,在下面的状况中,就要将DOM2获取数据的操作写在$nextTick中。

this.$nextTick(() => {    // 获取数据的操作...})

所以,在以下状况下,会用到nextTick:

  • 在数据变动后执行的某个操作,而这个操作须要应用随数据变动而变动的DOM构造的时候,这个操作就须要办法在nextTick()的回调函数中。
  • 在vue生命周期中,如果在created()钩子进行DOM操作,也肯定要放在nextTick()的回调函数中。

因为在created()钩子函数中,页面的DOM还未渲染,这时候也没方法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。

虚构DOM的优劣如何?

长处:

  • 保障性能上限: 虚构DOM能够通过diff找出最小差别,而后批量进行patch,这种操作尽管比不上手动优化,然而比起粗犷的DOM操作性能要好很多,因而虚构DOM能够保障性能上限
  • 无需手动操作DOM: 虚构DOM的diff和patch都是在一次更新中主动进行的,咱们无需手动操作DOM,极大进步开发效率
  • 跨平台: 虚构DOM实质上是JavaScript对象,而DOM与平台强相干,相比之下虚构DOM能够进行更不便地跨平台操作,例如服务器渲染、挪动端开发等等

毛病:

  • 无奈进行极致优化: 在一些性能要求极高的利用中虚构DOM无奈进行针对性的极致优化,比方VScode采纳间接手动操作DOM的形式进行极其的性能优化

Vue组件之间通信形式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。 Vue 组件间通信只有指以下 3 类通信父子组件通信隔代组件通信兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信

组件传参的各种形式

组件通信罕用形式有以下几种

  • props / $emit 实用 父子组件通信

    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3废除) 实用 父子组件通信

    • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
    • $parent / $children:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
  • EventBus ($emit / $on) 实用于 父子、隔代、兄弟组件通信

    • 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
  • $attrs / $listeners(vue3废除) 实用于 隔代组件通信

    • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( classstyle 除外 )。当一个组件没有申明任何 prop时,这里会蕴含所有父作用域的绑定 ( classstyle 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用
    • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
  • provide / inject 实用于 隔代组件通信

    • 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。 provide / inject API 次要解决了跨级组件间的通信问题, 不过它的应用场景,次要是子组件获取下级组件的状态 ,跨级组件间建设了一种被动提供与依赖注入的关系
  • $root 实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root只对根组件有用
  • Vuex 实用于 父子、隔代、兄弟组件通信

    • Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
    • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

依据组件之间关系探讨组件通信最为清晰无效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信

1. 父子组件通信

应用props,父组件能够应用props向子组件传递数据。

父组件vue模板father.vue:

<template>
  <child :msg="message"></child>
</template>

<script>
import child from './child.vue';
export default {
  components: {
    child
  },
  data () {
    return {
      message: 'father message';
    }
  }
}
</script>

子组件vue模板child.vue:

<template>
    <div>{{msg}}</div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      required: true
    }
  }
}
</script>

回调函数(callBack)

父传子:将父组件里定义的method作为props传入子组件

// 父组件Parent.vue:
<Child :changeMsgFn="changeMessage">
methods: {
    changeMessage(){
        this.message = 'test'
    }
}
// 子组件Child.vue:
<button @click="changeMsgFn">
props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件办法,子组件通过$emit触发事件,回调给父组件

父组件vue模板father.vue:

<template>
    <child @msgFunc="func"></child>
</template>

<script>
import child from './child.vue';
export default {
    components: {
        child
    },
    methods: {
        func (msg) {
            console.log(msg);
        }
    }
}
</script>

子组件vue模板child.vue:

<template>
    <button @click="handleClick">点我</button>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    methods () {
        handleClick () {
          //........
          this.$emit('msgFunc');
        }
    }
}
</script>

2. provide / inject 跨级拜访先人组件的数据

父组件通过应用provide(){return{}}提供须要传递的数据

export default {
  data() {
    return {
      title: '我是父组件',
      name: 'poetry'
    }
  },
  methods: {
    say() {
      alert(1)
    }
  },
  // provide属性 可能为前面的后辈组件/嵌套的组件提供所须要的变量和办法
  provide() {
    return {
      message: '我是先人组件提供的数据',
      name: this.name, // 传递属性
      say: this.say
    }
  }
}

子组件通过应用inject:[“参数1”,”参数2”,…]接管父组件传递的参数

<template>
  <p>曾孙组件</p>
  <p>{{message}}</p>
</template>
<script>
export default {
  // inject 注入/接管先人组件传递的所须要的数据即可 
  //接管到的数据 变量 跟data外面的变量一样 能够间接绑定到页面 {{}}
  inject: [ "message","say"],
  mounted() {
    this.say();
  },
};
</script>

3. $parent + $children 获取父组件实例和子组件实例的汇合

  • this.$parent 能够间接拜访该组件的父实例或组件
  • 父组件也能够通过 this.$children 拜访它所有的子组件;须要留神 $children 并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
  <child1></child1>   
  <child2></child2> 
  <button @click="clickChild">$children形式获取子组件值</button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {
  data(){
    return {
      total: 108
    }
  },
  components: {
    child1,
    child2  
  },
  methods: {
    funa(e){
      console.log("index",e)
    },
    clickChild(){
      console.log(this.$children[0].msg);
      console.log(this.$children[1].msg);
    }
  }
}
</script>
<!-- child1.vue -->
<template>
  <div>
    <button @click="parentClick">点击拜访父组件</button>
  </div>
</template>
<script>
export default {
  data(){
    return {
      msg:"child1"
    }
  },
  methods: {
    // 拜访父组件数据
    parentClick(){
      this.$parent.funa("xx")
      console.log(this.$parent.total);
    }
  }
}
</script>
<!-- child2.vue -->
<template>
  <div>
    child2
  </div>
</template>
<script>
export default {
  data(){
    return {
     msg: 'child2'
    }
  }
}
</script>

4. $attrs + $listeners多级组件通信

$attrs 蕴含了从父组件传过来的所有props属性

// 父组件Parent.vue:
<Child :name="name" :age="age"/>

// 子组件Child.vue:
<GrandChild v-bind="$attrs" />

// 孙子组件GrandChild
<p>姓名:{{$attrs.name}}</p>
<p>年龄:{{$attrs.age}}</p>

$listeners蕴含了父组件监听的所有事件

// 父组件Parent.vue:
<Child :name="name" :age="age" @changeNameFn="changeName"/>

// 子组件Child.vue:
<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件Parent.vue:
<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){
    console.log(this.$refs.childComp.age);
    this.$refs.childComp.changeAge()
}

// 子组件Child.vue:
data(){
    return{
        age:20
    }
},
methods(){
    changeAge(){
        this.age=15
  }
}

6. 非父子, 兄弟组件之间通信

vue2中废除了broadcast播送和散发事件的办法。父子组件中能够用props$emit()。如何实现非父子组件间的通信,能够通过实例一个vue实例Bus作为媒介,要互相通信的兄弟组件之中,都引入Bus,而后通过别离调用Bus事件触发和监听来实现通信和参数传递。Bus.js能够是这样:

// Bus.js

// 创立一个地方工夫总线类  
class Bus {  
  constructor() {  
    this.callbacks = {};   // 寄存事件的名字  
  }  
  $on(name, fn) {  
    this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {  
    if (this.callbacks[name]) {  
      this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  

// main.js  
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
// 另一种形式  
Vue.prototype.$bus = new Vue() // Vue曾经实现了Bus的性能  
<template>
    <button @click="toBus">子组件传给兄弟组件</button>
</template>

<script>
export default{
    methods: {
    toBus () {
      this.$bus.$emit('foo', '来自兄弟组件')
    }
  }
}
</script>

另一个组件也在钩子函数中监听on事件

export default {
  data() {
    return {
      message: ''
    }
  },
  mounted() {
    this.$bus.$on('foo', (msg) => {
      this.message = msg
    })
  }
}

7. $root 拜访根组件中的属性或办法

  • 作用:拜访根组件中的属性或办法
  • 留神:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({
  el: "#app",
  data() {
    return {
      rootInfo:"我是根元素的属性"
    }
  },
  methods: {
    alerts() {
      alert(111)
    }
  },
  components: {
    com1: {
      data() {
        return {
          info: "组件1"
        }
      },
      template: "<p>{{ info }} <com2></com2></p>",
      components: {
        com2: {
          template: "<p>我是组件1的子组件</p>",
          created() {
            this.$root.alerts()// 根组件办法
            console.log(this.$root.rootInfo)// 我是根元素的属性
          }
        }
      }
    }
  }
});

8. vuex

  • 实用场景: 简单关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器
  • state用来寄存共享变量的中央
  • getter,能够减少一个getter派生状态,(相当于store中的计算属性),用来取得共享变量的值
  • mutations用来寄存批改state的办法。
  • actions也是用来寄存批改state的办法,不过action是在mutations的根底上进行。罕用来做一些异步操作

小结

  • 父子关系的组件数据传递抉择 props$emit进行传递,也可抉择ref
  • 兄弟关系的组件数据传递可抉择$bus,其次能够抉择$parent进行传递
  • 先人与后辈组件数据传递可抉择attrslisteners或者 ProvideInject
  • 简单关系的组件数据传递能够通过vuex寄存共享的变量

Vue路由hash模式和history模式

1. hash模式

晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL# 前面的内容。比方上面这个网站,它的 location.hash 的值为 '#search'

https://interview2.poetries.top#search

hash 路由模式的实现次要是基于上面几个个性

  • URLhash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;
  • hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;
  • 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URLhash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URLhash 值;
  • 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)
window.addEventListener("hashchange", funcRef, false);

每一次扭转 hashwindow.location.hash),都会在浏览器的拜访历史中减少一个记录利用 hash 的以上特点,就能够来实现前端路由“更新视图但不从新申请页面”的性能了

特点 :兼容性好然而不美观

2. history模式

history采纳HTML5的新个性;且提供了两个新办法: pushState()replaceState()能够对浏览器历史记录栈进行批改,以及popState事件的监听到状态变更

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

这两个办法有个独特的特点:当调用他们批改浏览器历史记录栈后,尽管以后 URL 扭转了,但浏览器不会刷新页面,这就为单页利用前端路由“更新视图但不从新申请页面”提供了根底。

history 路由模式的实现次要基于存在上面几个个性:

  • pushStaterepalceState 两个 API 来操作实现 URL 的变动 ;
  • 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
  • history.pushState()history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。

特点 :尽管好看,然而刷新会呈现 404 须要后端进行配置

Vue.js的template编译

简而言之,就是先转化成AST树,再失去的render函数返回VNode(Vue的虚构DOM节点),具体步骤如下:

首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创立编译器的。另外compile还负责合并option。

而后,AST会通过generate(将AST语法树转化成render funtion字符串的过程)失去render函数,render的返回值是VNode,VNode是Vue的虚构DOM节点,外面有(标签名、子节点、文本等等)

Vue 组件间通信有哪几种形式?

​ Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。

(1)props / $emit 实用 父子组件通信

这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。

(2)ref$parent / $children 实用 父子组件通信

  • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
  • $parent / $children:拜访父 / 子实例

(3)EventBus ($emit / $on) 实用于 父子、隔代、兄弟组件通信

这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。

(4)$attrs/$listeners 实用于 隔代组件通信

  • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( class 和 style 除外 )。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 ( class 和 style 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用。
  • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件

(5)provide / inject 实用于 隔代组件通信

先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。 provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。

(6)Vuex 实用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
  • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

Vue-router根本应用

mode

  • hash
  • history

跳转

  • 编程式(js跳转)this.$router.push('/')
  • 申明式(标签跳转) <router-link to=""></router-link>

vue路由传参数

  • 应用query办法传入的参数应用this.$route.query承受
  • 应用params形式传入的参数应用this.$route.params承受

占位

<router-view></router-view>

简述 mixin、extends 的笼罩逻辑

(1)mixin 和 extends mixin 和 extends均是用于合并、拓展组件的,两者均通过 mergeOptions 办法实现合并。

  • mixins 接管一个混入对象的数组,其中混入对象能够像失常的实例对象一样蕴含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子依照传入程序顺次调用,并在调用组件本身的钩子之前被调用。
  • extends 次要是为了便于扩大单文件组件,接管一个对象或构造函数。

    (2)mergeOptions 的执行过程

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
  • 对未合并的选项,进行判断
if (!child._base) {
  if (child.extends) {
    parent = mergeOptions(parent, child.extends, vm);
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
}
  • 合并解决。依据一个通用 Vue 实例所蕴含的选项进行分类逐个判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并后果存储在新定义的 options 对象里。
  • 返回合并后果 options。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理