为什么要应用异步组件

  1. 节俭打包出的后果,异步组件离开打包,采纳jsonp的形式进行加载,无效解决文件过大的问题。
  2. 外围就是包组件定义变成一个函数,依赖import() 语法,能够实现文件的宰割加载。
components:{   AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) }

原理

export function ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void {     // async component     let asyncFactory     if (isUndef(Ctor.cid)) {         asyncFactory = Ctor         Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend         // 第二次渲染时Ctor不为undefined         if (Ctor === undefined) {             return createAsyncPlaceholder( // 渲染占位符 空虚构节点                 asyncFactory,                 data,                 context,                 children,                 tag             )         }     } }function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component> ): Class<Component> | void {     if (isDef(factory.resolved)) {         // 3.在次渲染时能够拿到获取的最新组件         return factory.resolved     }    const resolve = once((res: Object | Class<Component>) => {         factory.resolved = ensureCtor(res, baseCtor)         if (!sync) {             forceRender(true) //2. 强制更新视图从新渲染         } else {             owners.length = 0         }     })    const reject = once(reason => {         if (isDef(factory.errorComp)) {             factory.error = true forceRender(true)         }     })    const res = factory(resolve, reject)// 1.将resolve办法和reject办法传入,用户调用 resolve办法后     sync = false     return factory.resolved }

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节点,外面有(标签名、子节点、文本等等)

nextTick在哪里应用?原理是?

  • nextTick 中的回调是在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM
  • 在批改数据之后立刻应用这个办法,获取更新后的 DOM
  • 次要思路就是采纳微工作优先的形式调用异步办法去执行 nextTick 包装的办法
nextTick 办法次要是应用了宏工作和微工作,定义了一个异步办法.屡次调用 nextTick 会将办法存入队列中,通过这个异步办法清空以后队列。所以这个 nextTick 办法就是异步办法

依据执行环境别离尝试采纳

  • 先采纳Promise
  • Promise不反对,再采纳MutationObserver
  • MutationObserver不反对,再采纳setImmediate
  • 如果以上都不行则采纳setTimeout
  • 最初执行flushCallbacks,把callbacks外面的数据顺次执行

答复范例

  1. nextTick 中的回调是在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM
  2. Vue有个异步更新策略,意思是如果数据变动,Vue不会立即更新DOM,而是开启一个队列,把组件更新函数保留在队列中,在同一事件循环中产生的所有数据变更会异步的批量更新。这一策略导致咱们对数据的批改不会立即体现在DOM上,此时如果想要获取更新后的DOM状态,就须要应用nextTick
  3. 开发时,有两个场景咱们会用到nextTick
  4. created中想要获取DOM
  • 响应式数据变动后获取DOM更新后的状态,比方心愿获取列表更新后的高度
  • nextTick签名如下:function nextTick(callback?: () => void): Promise<void>

所以咱们只须要在传入的回调函数中拜访最新DOM状态即可,或者咱们能够await nextTick()办法返回的Promise之后做这件事

  1. Vue外部,nextTick之所以可能让咱们看到DOM更新后的后果,是因为咱们传入的callback会被增加到队列刷新函数(flushSchedulerQueue)的前面,这样等队列外部的更新函数都执行结束,所有DOM操作也就完结了,callback天然可能获取到最新的DOM值

根本应用

const vm = new Vue({    el: '#app',    data() {        return { a: 1 }    }}); // vm.$nextTick(() => {// [nextTick回调函数fn,外部更新flushSchedulerQueue]//     console.log(vm.$el.innerHTML)// })// 是将内容保护到一个数组里,最终依照程序程序。 第一次会开启一个异步工作vm.a = 'test'; // 批改了数据后并不会马上更新视图vm.$nextTick(() => {// [nextTick回调函数fn,外部更新flushSchedulerQueue]    console.log(vm.$el.innerHTML)})// nextTick中的办法会被放到 更新页面watcher的前面去

相干代码如下

// src/core/utils/nextTicklet callbacks = [];let pending = false;function flushCallbacks() {  pending = false; //把标记还原为false  // 顺次执行回调  for (let i = 0; i < callbacks.length; i++) {    callbacks[i]();  }}let timerFunc; //定义异步办法  采纳优雅降级if (typeof Promise !== "undefined") {  // 如果反对promise  const p = Promise.resolve();  timerFunc = () => {    p.then(flushCallbacks);  };} else if (typeof MutationObserver !== "undefined") {  // MutationObserver 次要是监听dom变动 也是一个异步办法  let counter = 1;  const observer = new MutationObserver(flushCallbacks);  const textNode = document.createTextNode(String(counter));  observer.observe(textNode, {    characterData: true,  });  timerFunc = () => {    counter = (counter + 1) % 2;    textNode.data = String(counter);  };} else if (typeof setImmediate !== "undefined") {  // 如果后面都不反对 判断setImmediate  timerFunc = () => {    setImmediate(flushCallbacks);  };} else {  // 最初降级采纳setTimeout  timerFunc = () => {    setTimeout(flushCallbacks, 0);  };}export function nextTick(cb) {  // 除了渲染watcher  还有用户本人手动调用的nextTick 一起被收集到数组  callbacks.push(cb);  if (!pending) {    // 如果屡次调用nextTick  只会执行一次异步 等异步队列清空之后再把标记变为false    pending = true;    timerFunc();  }}

数据更新的时候外部会调用nextTick

// src/core/observer/scheduler.jsexport function queueWatcher (watcher: Watcher) {  const id = watcher.id  if (has[id] == null) {    has[id] = true    if (!flushing) {      queue.push(watcher)    } else {      // if already flushing, splice the watcher based on its id      // if already past its id, it will be run next immediately.      let i = queue.length - 1      while (i > index && queue[i].id > watcher.id) {        i--      }      queue.splice(i + 1, 0, watcher)    }    // queue the flush    if (!waiting) {      waiting = true      if (process.env.NODE_ENV !== 'production' && !config.async) {        flushSchedulerQueue()        return      }      // 把更新办法放到数组中保护[nextTick回调函数,更新函数flushSchedulerQueue]      /**       * vm.a = 'test'; // 批改了数据后并不会马上更新视图        vm.$nextTick(() => {// [fn,更新]            console.log(vm.$el.innerHTML)        })       */      nextTick(flushSchedulerQueue)    }  }}

那vue中是如何检测数组变动的呢?

数组就是应用 object.defineProperty 从新定义数组的每一项,那能引起数组变动的办法咱们都是晓得的, pop push shift unshift splice sort reverse 这七种,只有这些办法执行改了数组内容,我就更新内容就好了,是不是很好了解。

  1. 是用来函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新。
  2. 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)

vue3:改用 proxy ,可间接监听对象数组的变动。

如何从实在DOM到虚构DOM

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

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

虚构DOM实现原理?

  • 虚构DOM实质上是JavaScript对象,是对实在DOM的形象
  • 状态变更时,记录新树和旧树的差别
  • 最初把差别更新到真正的dom中

参考 前端进阶面试题具体解答

为什么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, // 当批改属性时调用此办法};

Vue 的生命周期办法有哪些 个别在哪一步发申请

beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访

created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom

beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。

mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点

beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。

beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。

destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

activated keep-alive 专属,组件被激活时调用

deactivated keep-alive 专属,组件被销毁时调用

异步申请在哪一步发动?

能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。

如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:

  • 能更快获取到服务端数据,缩小页面 loading 工夫;
  • ssr 不反对 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

Vue-router 路由有哪些模式?

个别有两种模式:

 (1)**hash 模式**:前面的 hash 值的变动,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变动会触发 hashchange 事件。 (2)**history 模式**:利用了 HTML5 中新增的 pushState() 和 replaceState() 办法。这两个办法利用于浏览器的历史记录栈,在以后已有的 back、forward、go 的根底之上,它们提供了对历史记录进行批改的性能。只是当它们执行批改时,尽管扭转了以后的 URL,但浏览器不会立刻向后端发送申请。

虚构 DOM 的优缺点?

长处:

  • 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
  • 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 跨平台: 虚构 DOM 实质上是 JavaScript 对象,而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。

毛病:

  • 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。

vue和react的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件2. 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents标准3. 数据流动单向,都反对服务器的渲染SSR4. 都有反对native的办法,react有React native, vue有wexx

=> 不同点:

 1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的 2.数据渲染:大规模的数据渲染,react更快 3.应用场景:React配合Redux架构适宜大规模多人合作简单我的项目,Vue适宜小快的我的项目 4.开发格调:react举荐做法jsx + inline style把html和css都写在js了      vue是采纳webpack + vue-loader单文件组件格局,html, js, css同一个文件

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', '小红')  },},

什么是 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"    }}

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 代码尚未被解析之前,尚无法控制页面中 DOM 的显示,所以会看见模板字符串等代码。
解决方案是,在 css 代码中增加 v-cloak 规定,同时在待编译的标签上增加 v-cloak 属性:

[v-cloak] { display: none; }<div v-cloak>  {{ message }}</div>

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模版编译原理晓得吗,能简略说一下吗?

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

  • 生成AST树
  • 优化
  • codegen

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

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

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

vue 中应用了哪些设计模式

1.工厂模式 - 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2.单例模式 - 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3.公布-订阅模式 (vue 事件机制)

4.观察者模式 (响应式数据原理)

5.装璜模式: (@装璜器的用法)

6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略

...其余模式欢送补充

父组件能够监听到子组件的生命周期吗

比方有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑解决,能够通过以下写法实现:

// Parent.vue<Child @mounted="doSomething"/>// Child.vuemounted() {this.$emit("mounted");}

以上须要手动通过 $emit 触发父组件的事件,更简略的形式能够在父组件援用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue<Child @hook:mounted="doSomething" ></Child>doSomething() {   console.log('父组件监听到 mounted 钩子函数 ...');},//  Child.vuemounted(){   console.log('子组件触发 mounted 钩子函数 ...');},    // 以上输入程序为:// 子组件触发 mounted 钩子函数 ...// 父组件监听到 mounted 钩子函数 ...     

当然 @hook 办法不仅仅是能够监听 mounted,其它的生命周期事件,例如:createdupdated 等都能够监听