关于vue.js:19道高频vue面试题解答上

6次阅读

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

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

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

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

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

用 Vue3.0 写过组件吗?如果想实现一个 Modal 你会怎么设计?

一、组件设计

组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式

当初有一个场景,点击新增与编辑都弹框出来进行填写,性能上大同小异,可能只是题目内容或者是显示的主体内容略微不同

这时候就没必要写两个组件,只须要依据传入的参数不同,组件显示不同内容即可

这样,下次开发雷同界面程序时就能够写更少的代码,意义着更高的开发效率,更少的 Bug和更少的程序体积

二、需要剖析

实现一个 Modal 组件,首先确定须要实现的内容:

  • 遮罩层
  • 题目内容
  • 主体内容
  • 确定和勾销按钮

主体内容须要灵便,所以能够是字符串,也能够是一段 html 代码

特点是它们在以后 vue 实例之外独立存在,通常挂载于 body 之上

除了通过引入 import 的模式,咱们还可通过 API 的模式进行组件的调用

还能够包含配置全局款式、国际化、与 typeScript 联合

三、实现流程

首先看看大抵流程:

  • 目录构造
  • 组件内容
  • 实现 API 模式
  • 事件处理
  • 其余欠缺

目录构造

Modal组件相干的目录构造

├── plugins
│   └── modal
│       ├── Content.tsx // 保护 Modal 的内容,用于 h 函数和 jsx 语法
│       ├── Modal.vue // 根底组件
│       ├── config.ts // 全局默认配置
│       ├── index.ts // 入口
│       ├── locale // 国际化相干
│       │   ├── index.ts
│       │   └── lang
│       │       ├── en-US.ts
│       │       ├── zh-CN.ts
│       │       └── zh-TW.ts
│       └── modal.type.ts // ts 类型申明相干

因为 Modal 会被 app.use(Modal) 调用作为一个插件,所以都放在 plugins 目录下

组件内容

首先实现 modal.vue 的主体显示内容大抵如下

<Teleport to="body" :disabled="!isTeleport">
    <div v-if="modelValue" class="modal">
        <div
             class="mask"
             :style="style"
             @click="maskClose && !loading && handleCancel()"
             ></div>
        <div class="modal__main">
            <div class="modal__title line line--b">
                <span>{{title || t("r.title") }}</span>
                <span
                      v-if="close"
                      :title="t('r.close')"
                      class="close"
                      @click="!loading && handleCancel()"
                      >✕</span
                    >
            </div>
            <div class="modal__content">
                <Content v-if="typeof content ==='function'":render="content" />
                <slot v-else>
                    {{content}}
                </slot>
            </div>
            <div class="modal__btns line line--t">
                <button :disabled="loading" @click="handleConfirm">
                    <span class="loading" v-if="loading"> ❍ </span>{{t("r.confirm") }}
                </button>
                <button @click="!loading && handleCancel()">
                    {{t("r.cancel") }}
                </button>
            </div>
        </div>
    </div>
</Teleport>

最外层上通过 Vue3 Teleport 内置组件进行包裹,其相当于传送门,将外面的内容传送至 body 之上

并且从 DOM 构造上来看,把 modal 该有的内容(遮罩层、题目、内容、底部按钮)都实现了

对于主体内容

<div class="modal__content">
    <Content v-if="typeof content==='function'":render="content" />
    <slot v-else>
        {{content}}
    </slot>
</div>

能够看到依据传入 content 的类型不同,对应显示不同失去内容

最常见的则是通过调用字符串和默认插槽的模式

// 默认插槽
<Modal v-model="show"
       title="演示 slot">
    <div>hello world~</div>
</Modal>

// 字符串
<Modal v-model="show"
       title="演示 content"
       content="hello world~" />

通过 API 模式调用 Modal 组件的时候,content能够应用上面两种

  • h 函数
$modal.show({
  title: '演示 h 函数',
  content(h) {
    return h(
      'div',
      {
        style: 'color:red;',
        onClick: ($event: Event) => console.log('clicked', $event.target)
      },
      'hello world ~'
    );
  }
});
  • JSX
$modal.show({
  title: '演示 jsx 语法',
  content() {
    return (
      <div
        onClick={($event: Event) => console.log('clicked', $event.target)}
      >
        hello world ~
      </div>
    );
  }
});

实现 API 模式

那么组件如何实现 API 模式调用 Modal 组件呢?

Vue2 中,咱们能够借助 Vue 实例以及 Vue.extend 的形式取得组件实例,而后挂载到 body

import Modal from './Modal.vue';
const ComponentClass = Vue.extend(Modal);
const instance = new ComponentClass({el: document.createElement("div") });
document.body.appendChild(instance.$el);

尽管 Vue3 移除了 Vue.extend 办法,但能够通过 createVNode 实现

import Modal from './Modal.vue';
const container = document.createElement('div');
const vnode = createVNode(Modal);
render(vnode, container);
const instance = vnode.component;
document.body.appendChild(container);

Vue2 中,能够通过 this 的模式调用全局 API

export default {install(vue) {vue.prototype.$create = create}
}

而在 Vue3 的 setup 中曾经没有 this概念了,须要调用 app.config.globalProperties 挂载到全局

export default {install(app) {app.config.globalProperties.$create = create}
}

事件处理

上面再看看看 Modal 组件外部是如何解决「确定」「勾销」事件的,既然是Vue3,当然采纳Compositon API 模式

// Modal.vue
setup(props, ctx) {let instance = getCurrentInstance(); // 取得以后组件实例
  onBeforeMount(() => {
    instance._hub = {'on-cancel': () => {},
      'on-confirm': () => {}
    };
  });

  const handleConfirm = () => {ctx.emit('on-confirm');
    instance._hub['on-confirm']();};
  const handleCancel = () => {ctx.emit('on-cancel');
    ctx.emit('update:modelValue', false);
    instance._hub['on-cancel']();};

  return {
    handleConfirm,
    handleCancel
  };
}

在下面代码中,能够看失去除了应用传统 emit 的模式使父组件监听,还可通过 _hub 属性中增加 on-cancelon-confirm办法实现在 API 中进行监听

app.config.globalProperties.$modal = {show({}) {/* 监听 确定、勾销 事件 */}
}

上面再来目击下 _hub 是如何实现

// index.ts
app.config.globalProperties.$modal = {
    show({
        /* 其余选项 */
        onConfirm,
        onCancel
    }) {
        /* ... */

        const {props, _hub} = instance;

        const _closeModal = () => {
            props.modelValue = false;
            container.parentNode!.removeChild(container);
        };
        // 往 _hub 新增事件的具体实现
        Object.assign(_hub, {async 'on-confirm'() {if (onConfirm) {const fn = onConfirm();
                // 当办法返回为 Promise
                if (fn && fn.then) {
                    try {
                        props.loading = true;
                        await fn;
                        props.loading = false;
                        _closeModal();} catch (err) {
                        // 产生谬误时,不敞开弹框
                        console.error(err);
                        props.loading = false;
                    }
                } else {_closeModal();
                }
            } else {_closeModal();
            }
        },
            'on-cancel'() {onCancel && onCancel();
                _closeModal();}
    });
}
};

其余欠缺

对于组件实现国际化、与 typsScript 联合,大家能够依据本身状况在此基础上进行更改

Proxy 与 Object.defineProperty 优劣比照

Proxy 的劣势如下:

  • Proxy 能够间接监听对象而非属性;
  • Proxy 能够间接监听数组的变动;
  • Proxy 有多达 13 种拦挡办法, 不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象, 咱们能够只操作新的对象达到目标, 而 Object.defineProperty 只能遍历对象属性间接批改;

Proxy 作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利;

Object.defineProperty 的劣势如下:

  • 兼容性好,反对 IE9,而 Proxy 的存在浏览器兼容性问题, 而且无奈用 polyfill 磨平,因而 Vue 的作者才申明须要等到下个大版本 (3.0) 能力用 Proxy 重写。

computed 和 watch 的区别和使用的场景?

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值;

watch: 更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作;

使用场景:

  • 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时,都要从新计算;
  • 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许咱们执行异步操作 (拜访一个 API),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。

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

如何保留页面的以后的状态

既然是要放弃页面的状态(其实也就是组件的状态),那么会呈现以下两种状况:

  • 前组件会被卸载
  • 前组件不会被卸载

那么能够依照这两种状况别离失去以下办法:

组件会被卸载:

(1)将状态存储在 LocalStorage / SessionStorage

只须要在组件行将被销毁的生命周期 componentWillUnmount(react)中在 LocalStorage / SessionStorage 中把以后组件的 state 通过 JSON.stringify() 贮存下来就能够了。在这外面须要留神的是组件更新状态的机会。

比方从 B 组件跳转到 A 组件的时候,A 组件须要更新本身的状态。然而如果从别的组件跳转到 B 组件的时候,实际上是心愿 B 组件从新渲染的,也就是不要从 Storage 中读取信息。所以须要在 Storage 中的状态退出一个 flag 属性,用来管制 A 组件是否读取 Storage 中的状态。

长处:

  • 兼容性好,不须要额定库或工具。
  • 简略快捷,根本能够满足大部分需要。

毛病:

  • 状态通过 JSON 办法贮存(相当于深拷贝),如果状态中有非凡状况(比方 Date 对象、Regexp 对象等)的时候会失去字符串而不是原来的值。(具体参考用 JSON 深拷贝的毛病)
  • 如果 B 组件后退或者下一页跳转并不是前组件,那么 flag 判断会生效,导致从其余页面进入 A 组件页面时 A 组件会从新读取 Storage,会造成很奇怪的景象

(2)路由传值

通过 react-router 的 Link 组件的 prop —— to 能够实现路由间传递参数的成果。

在这里须要用到 state 参数,在 B 组件中通过 history.location.state 就能够拿到 state 值,保留它。返回 A 组件时再次携带 state 达到路由状态放弃的成果。

长处:

  • 简略快捷,不会净化 LocalStorage / SessionStorage。
  • 能够传递 Date、RegExp 等非凡对象(不必放心 JSON.stringify / parse 的有余)

毛病:

  • 如果 A 组件能够跳转至多个组件,那么在每一个跳转组件内都要写雷同的逻辑。

组件不会被卸载:

(1)单页面渲染

要切换的组件作为子组件全屏渲染,父组件中失常贮存页面状态。

长处:

  • 代码量少
  • 不须要思考状态传递过程中的谬误

毛病:

  • 减少 A 组件保护老本
  • 须要传入额定的 prop 到 B 组件
  • 无奈利用路由定位页面

除此之外,在 Vue 中,还能够是用 keep-alive 来缓存页面,当组件在 keep-alive 内被切换时组件的 activated、deactivated 这两个生命周期钩子函数会被执行
被包裹在 keep-alive 中的组件的状态将会被保留:

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>

router.js

{
  path: '/',
  name: 'xxx',
  component: ()=>import('../src/views/xxx.vue'),
  meta:{keepAlive: true // 须要被缓存}
},

常见的事件修饰符及其作用

  • .stop:等同于 JavaScript 中的 event.stopPropagation(),避免事件冒泡;
  • .prevent:等同于 JavaScript 中的 event.preventDefault(),避免执行预设的行为(如果事件可勾销,则勾销该事件,而不进行事件的进一步流传);
  • .capture:与事件冒泡的方向相同,事件捕捉由外到内;
  • .self:只会触发本人范畴内的事件,不蕴含子元素;
  • .once:只会触发一次。

为什么在 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 不仅能够代理对象, 还能够代理数组。还能够代理动静减少的属性。

说说 Vue 的生命周期吧

什么时候被调用?

  • beforeCreate:实例初始化之后,数据观测之前调用
  • created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、 watch/event 事件回调。无 $el .
  • beforeMount:在挂载之前调用,相干 render 函数首次被调用
  • mounted:了被新创建的 vm.$el 替换,并挂载到实例下来之后调用改钩子。
  • beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • beforeDestroy:实例销毁前调用,实例依然可用。
  • destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除

每个生命周期外部能够做什么?

  • created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
  • mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
  • beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
  • updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
  • destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。

ajax 放在哪个生命周期?:个别放在 mounted 中,保障逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。复数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不反对 mounted 办法。 什么时候应用 beforeDestroy?:以后页面应用 $on,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove

Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题?

受古代 JavaScript 的限度,Vue 无奈检测到对象属性的增加或删除。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的。然而 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象增加响应式属性,那框架自身是如何实现的呢?

咱们查看对应的 Vue 源码:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  • 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  • 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)

Vue 模版编译原理

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

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

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

为什么 Vue 采纳异步渲染呢?

Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick

dep.notify() 告诉 watcher 进行更新, subs[i].update 顺次调用 watcher 的 update queueWatcher 将 watcher 去重放入队列,nextTick( flushSchedulerQueue)在下一 tick 中刷新 watcher 队列(异步)。

Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。

相干代码如下

import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法

export function reactive(target) {
  // 依据不同参数创立不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {return function get(target, key, receiver) {
    // 对获取的值进行喷射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
      // 如果获取的值是对象类型,则返回以后对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
  get, // 当获取属性时调用此办法
  set, // 当批改属性时调用此办法
};

如何从实在 DOM 到虚构 DOM

波及到 Vue 中的模板编译原理,次要过程:

  1. 将模板转换成 ast 树, ast 用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM)
  2. 优化树
  3. ast 树生成代码

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

对 keep-alive 的了解,它是如何实现的,具体缓存的是什么?

如果须要在组件切换的时候,保留一些组件的状态避免屡次渲染,就能够应用 keep-alive 组件包裹须要保留的组件。

(1)keep-alive

keep-alive 有以下三个属性:

  • include 字符串或正则表达式,只有名称匹配的组件会被匹配;
  • exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
  • max 数字,最多能够缓存多少组件实例。

留神:keep-alive 包裹动静组件时,会缓存不流动的组件实例。

次要流程

  1. 判断组件 name,不在 include 或者在 exclude 中,间接返回 vnode,阐明该组件不被缓存。
  2. 获取组件实例 key,如果有获取实例的 key,否则从新生成。
  3. key 生成规定,cid +”∶∶”+ tag,仅靠 cid 是不够的,因为雷同的构造函数能够注册为不同的本地组件。
  4. 如果缓存对象内存在,则间接从缓存对象中获取组件实例给 vnode,不存在则增加到缓存对象中。5. 最大缓存数量,当缓存组件数量超过 max 值时,革除 keys 数组内第一个组件。

(2)keep-alive 的实现

const patternTypes: Array<Function> = [String, RegExp, Array] // 接管:字符串,正则,数组

export default {
  name: 'keep-alive',
  abstract: true, // 形象组件,是一个形象组件:它本身不会渲染一个 DOM 元素,也不会呈现在父组件链中。props: {
    include: patternTypes, // 匹配的组件,缓存
    exclude: patternTypes, // 不去匹配的组件,不缓存
    max: [String, Number], // 缓存组件的最大实例数量, 因为缓存的是组件实例(vnode),数量过多的时候,会占用过多的内存,能够用 max 指定下限
  },

  created() {
    // 用于初始化缓存虚构 DOM 数组和 vnode 的 key
    this.cache = Object.create(null)
    this.keys = []},

  destroyed() {
    // 销毁缓存 cache 的组件实例
    for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted() {// prune 削减精简[v.]
    // 去监控 include 和 exclude 的扭转,依据最新的 include 和 exclude 的内容,来实时削减缓存的组件的内容
    this.$watch('include', (val) => {pruneCache(this, (name) => matches(val, name))
    })
    this.$watch('exclude', (val) => {pruneCache(this, (name) => !matches(val, name))
    })
  },
}

render 函数:

  1. 会在 keep-alive 组件外部去写本人的内容,所以能够去获取默认 slot 的内容,而后依据这个去获取组件
  2. keep-alive 只对第一个组件无效,所以获取第一个子组件。
  3. 和 keep-alive 搭配应用的个别有:动静组件 和 router-view
render () {
  //
  function getFirstComponentChild (children: ?Array<VNode>): ?VNode {if (Array.isArray(children)) {for (let i = 0; i < children.length; i++) {const c = children[i]
    if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {return c}
  }
  }
  }
  const slot = this.$slots.default // 获取默认插槽
  const vnode: VNode = getFirstComponentChild(slot)// 获取第一个子组件
  const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 组件参数
  if (componentOptions) { // 是否有组件参数
    // check pattern
    const name: ?string = getComponentName(componentOptions) // 获取组件名
    const {include, exclude} = this
    if (
      // not included
      (include && (!name || !matches(include, name))) ||
      // excluded
      (exclude && name && matches(exclude, name))
    ) {
      // 如果不匹配以后组件的名字和 include 以及 exclude
      // 那么间接返回组件的实例
      return vnode
    }

    const {cache, keys} = this

    // 获取这个组件的 key
    const key: ?string = vnode.key == null
      // same constructor may get registered as different local components
      // so cid alone is not enough (#3269)
      ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
      : vnode.key

    if (cache[key]) {
      // LRU 缓存策略执行
      vnode.componentInstance = cache[key].componentInstance // 组件首次渲染的时候 componentInstance 为 undefined

      // make current key freshest
      remove(keys, key)
      keys.push(key)
      // 依据 LRU 缓存策略执行,将 key 从原来的地位移除,而后将这个 key 值放到最初面
    } else {
      // 在缓存列表外面没有的话,则退出,同时判断以后退出之后,是否超过了 max 所设定的范畴,如果是,则去除
      // 应用工夫距离最长的一个
      cache[key] = vnode
      keys.push(key)
      // prune oldest entry
      if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
    }
    // 将组件的 keepAlive 属性设置为 true
    vnode.data.keepAlive = true // 作用:判断是否要执行组件的 created、mounted 生命周期函数
  }
  return vnode || (slot && slot[0])
}

keep-alive 具体是通过 cache 数组缓存所有组件的 vnode 实例。当 cache 内原有组件被应用时会将该组件 key 从 keys 数组中删除,而后 push 到 keys 数组最初,以便革除最不罕用组件。

实现步骤:

  1. 获取 keep-alive 下第一个子组件的实例对象,通过他去获取这个组件的组件名
  2. 通过以后组件名去匹配原来 include 和 exclude,判断以后组件是否须要缓存,不须要缓存,间接返回以后组件的实例 vNode
  3. 须要缓存,判断他以后是否在缓存数组外面:
  4. 存在,则将他原来地位上的 key 给移除,同时将这个组件的 key 放到数组最初面(LRU)
  • 不存在,将组件 key 放入数组,而后判断以后 key 数组是否超过 max 所设置的范畴,超过,那么削减未应用工夫最长的一个组件的 key
  • 最初将这个组件的 keepAlive 设置为 true

(3)keep-alive 自身的创立过程和 patch 过程

缓存渲染的时候,会依据 vnode.componentInstance(首次渲染 vnode.componentInstance 为 undefined)和 keepAlive 属性判断不会执行组件的 created、mounted 等钩子函数,而是对缓存的组件执行 patch 过程∶ 间接把缓存的 DOM 对象直接插入到指标元素中,实现了数据更新的状况下的渲染过程。

首次渲染

  • 组件的首次渲染∶判断组件的 abstract 属性,才往父组件外面挂载 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) { // 判断组件的 abstract 属性,才往父组件外面挂载 DOM
    while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
  • 判断以后 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创立 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) { // componentInstance 在首次是 undefined!!!
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch 函数执行的是组件更新的过程
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

prepatch 操作就不会在执行组件的 mounted 和 created 生命周期函数,而是间接将 DOM 插入

(4)LRU(least recently used)缓存策略

LRU 缓存策略∶ 从内存中找出最久未应用的数据并置换新的数据。
LRU(Least rencently used)算法依据数据的历史拜访记录来进行淘汰数据,其核心思想是 “ 如果数据最近被拜访过,那么未来被拜访的几率也更高 ”。最常见的实现是应用一个链表保留缓存数据,具体算法实现如下∶

  • 新数据插入到链表头部
  • 每当缓存命中(即缓存数据被拜访),则将数据移到链表头部
  • 链表满的时候,将链表尾部的数据抛弃。

什么是 mixin?

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

Vue 中的 key 到底有什么用?

key 是给每一个 vnode 的惟一 id, 依附 key, 咱们的 diff 操作能够更精确、更疾速 (对于简略列表页渲染来说 diff 节点也更快, 但会产生一些暗藏的副作用, 比方可能不会产生过渡成果, 或者在某些节点有绑定数据(表单)状态,会呈现状态错位。)

diff 算法的过程中, 先会进行新旧节点的首尾穿插比照, 当无奈匹配的时候会用新节点的 key 与旧节点进行比对, 从而找到相应旧节点.

更精确 : 因为带 key 就不是就地复用了, 在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确, 如果不加 key, 会导致之前节点的状态被保留下来, 会产生一系列的 bug。

更疾速 : key 的唯一性能够被 Map 数据结构充分利用, 相比于遍历查找的工夫复杂度 O(n),Map 的工夫复杂度仅仅为 O(1), 源码如下:

function createKeyToOldIdx(children, beginIdx, endIdx) {
  let i, key;
  const map = {};
  for (i = beginIdx; i <= endIdx; ++i) {key = children[i].key;
    if (isDef(key)) map[key] = i;
  }
  return map;
}

Vue 组件如何通信?

Vue 组件通信的办法如下:

  • props/$emit+v-on: 通过 props 将数据自上而下传递,而通过 $emit 和 v -on 来向上传递信息。
  • EventBus: 通过 EventBus 进行信息的公布与订阅
  • vuex: 是全局数据管理库,能够通过 vuex 治理全局的数据流
  • $attrs/$listeners: Vue2.4 中退出的 $attrs/$listeners 能够进行跨级的组件通信
  • provide/inject:以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效,这成为了跨组件通信的根底

还有一些用 solt 插槽或者 ref 实例进行通信的,应用场景过于无限就不赘述了。

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},
正文完
 0