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

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

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

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

你有对 Vue 我的项目进行哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 辨别应用场景
  • computed 和 watch 辨别应用场景
  • v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化有限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 缩小 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建后果输入剖析
  • Vue 我的项目的编译优化

(3)根底的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的应用
  • 应用 Chrome Performance 查找性能瓶颈

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

对虚构DOM的了解?

从实质上来说,Virtual Dom是一个JavaScript对象,通过对象的形式来示意DOM构造。将页面的状态形象为JS对象的模式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将屡次DOM批改的后果一次性的更新到页面上,从而无效的缩小页面渲染的次数,缩小批改DOM的重绘重排次数,进步渲染性能。

虚构DOM是对DOM的形象,这个对象是更加轻量级的对 DOM的形容。它设计的最后目标,就是更好的跨平台,比方Node.js就没有DOM,如果想实现SSR,那么一个形式就是借助虚构DOM,因为虚构DOM自身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚构 DOM)。以对象的模式来形容实在DOM构造,最终渲染到页面。在每次数据发生变化前,虚构DOM都会缓存一份,变动之时,当初的虚构DOM会与缓存的虚构DOM进行比拟。在vue外部封装了diff算法,通过这个算法来进行比拟,渲染时批改扭转的变动,原先没有产生扭转的通过原先的数据进行渲染。

另外古代前端框架的一个根本要求就是毋庸手动操作DOM,一方面是因为手动操作DOM无奈保障程序性能,多人合作的我的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作能够大大提高开发效率。

Vue template 到 render 的过程

vue的模版编译过程次要如下:template -> ast -> render函数

vue 在模版编译版本的码中会执行 compileToFunctions 将template转化为render函数:

// 将模板编译为render函数const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)

CompileToFunctions中的次要逻辑如下∶ (1)调用parse办法将template转化为ast(形象语法树)

constast = parse(template.trim(), options)
  • parse的指标:把tamplate转换为AST树,它是一种用 JavaScript对象的模式来形容整个模板。
  • 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构AST树的目标。

AST元素节点总共三种类型:type为1示意一般元素、2为表达式、3为纯文本

(2)对动态节点做优化

optimize(ast,options)

这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化

深度遍历AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的DOM永远不会扭转,这对运行时模板更新起到了极大的优化作用。

(3)生成代码

const code = generate(ast, options)

generate将ast形象语法树编译成 render字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(` render`) 生成render函数。

vue-router中如何爱护路由

剖析

路由爱护在利用开发过程中十分重要,简直每个利用都要做各种路由权限治理,因而相当考查使用者基本功。

体验

全局守卫:

const router = createRouter({ ... })router.beforeEach((to, from) => {  // ...  // 返回 false 以勾销导航  return false})

路由独享守卫:

const routes = [  {    path: '/users/:id',    component: UserDetails,    beforeEnter: (to, from) => {      // reject the navigation      return false    },  },]

组件内的守卫:

const UserDetails = {  template: `...`,  beforeRouteEnter(to, from) {    // 在渲染该组件的对应路由被验证前调用  },  beforeRouteUpdate(to, from) {    // 在以后路由扭转,然而该组件被复用时调用  },  beforeRouteLeave(to, from) {    // 在导航来到渲染该组件的对应路由时调用  },}

答复

  • vue-router中爱护路由的办法叫做路由守卫,次要用来通过跳转或勾销的形式守卫导航。
  • 路由守卫有三个级别:全局路由独享组件级。影响范畴由大到小,例如全局的router.beforeEach(),能够注册一个全局前置守卫,每次路由导航都会通过这个守卫,因而在其外部能够退出管制逻辑决定用户是否能够导航到指标路由;在路由注册的时候能够退出单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因而只会影响这个路由,管制更准确;咱们还能够为路由组件增加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,管制的范畴更准确了。
  • 用户的任何导航行为都会走navigate办法,外部有个guards队列按程序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会勾销原有的导航。

原理

runGuardQueue(guards)链式的执行用户在各级别注册的守卫钩子函数,通过则持续下一个级别的守卫,不通过进入catch流程勾销本来导航

// 源码runGuardQueue(guards)  .then(() => {    // check global guards beforeEach    guards = []    for (const guard of beforeGuards.list()) {      guards.push(guardToPromiseFn(guard, to, from))    }    guards.push(canceledNavigationCheck)    return runGuardQueue(guards)  })  .then(() => {    // check in components beforeRouteUpdate    guards = extractComponentsGuards(      updatingRecords,      'beforeRouteUpdate',      to,      from    )    for (const record of updatingRecords) {      record.updateGuards.forEach(guard => {        guards.push(guardToPromiseFn(guard, to, from))      })    }    guards.push(canceledNavigationCheck)    // run the queue of per route beforeEnter guards    return runGuardQueue(guards)  })  .then(() => {    // check the route beforeEnter    guards = []    for (const record of to.matched) {      // do not trigger beforeEnter on reused views      if (record.beforeEnter && !from.matched.includes(record)) {        if (isArray(record.beforeEnter)) {          for (const beforeEnter of record.beforeEnter)            guards.push(guardToPromiseFn(beforeEnter, to, from))        } else {          guards.push(guardToPromiseFn(record.beforeEnter, to, from))        }      }    }    guards.push(canceledNavigationCheck)    // run the queue of per route beforeEnter guards    return runGuardQueue(guards)  })  .then(() => {    // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>    // clear existing enterCallbacks, these are added by extractComponentsGuards    to.matched.forEach(record => (record.enterCallbacks = {}))    // check in-component beforeRouteEnter    guards = extractComponentsGuards(      enteringRecords,      'beforeRouteEnter',      to,      from    )    guards.push(canceledNavigationCheck)    // run the queue of per route beforeEnter guards    return runGuardQueue(guards)  })  .then(() => {    // check global guards beforeResolve    guards = []    for (const guard of beforeResolveGuards.list()) {      guards.push(guardToPromiseFn(guard, to, from))    }    guards.push(canceledNavigationCheck)    return runGuardQueue(guards)  })  // catch any navigation canceled  .catch(err =>    isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)      ? err      : Promise.reject(err)  )

源码地位(opens new window)

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

说一下Vue的生命周期

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

  1. beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到data、computed、watch、methods上的办法和数据。
  2. created(创立后) :实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 $el 属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相干的render函数首次被调用。实例已实现以下的配置:编译模板,把data外面的数据和模板生成html。此时还没有挂载html到页面上。
  4. mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的html内容替换el属性指向的DOM对象。实现模板中的html渲染到html 页面中。此过程中进行ajax交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
  6. updated(更新后) :在因为数据更改导致的虚构DOM从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM曾经更新,所以能够执行依赖于DOM的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,this 仍能获取到实例。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

另外还有 keep-alive 独有的生命周期,别离为 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

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)

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

谈谈对keep-alive的理解

keep-alive 能够实现组件的缓存,当组件切换时不会对以后组件进行卸载。罕用的2个属性 include/exclude ,2个生命周期 activated deactivated

vue 中应用了哪些设计模式

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

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

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

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

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

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

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

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

...其余模式欢送补充

在Vue中应用插件的步骤

  • 采纳ES6import ... from ...语法或CommonJSrequire()办法引入插件
  • 应用全局办法Vue.use( plugin )应用插件,能够传入一个选项对象Vue.use(MyPlugin, { someOption: true })

vue-router守卫

导航守卫 router.beforeEach 全局前置守卫
  • to: Route: 行将要进入的指标(路由对象)
  • from: Route: 以后导航正要来到的路由
  • next: Function: 肯定要调用该办法来 resolve 这个钩子。(肯定要用这个函数能力去到下一个路由,如果不必就拦挡)
  • 执行成果依赖 next 办法的调用参数。
  • next(): 进行管道中的下一个钩子。如果全副钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false):勾销进入路由,url地址重置为from路由地址(也就是将要来到的路由地址)
// main.js 入口文件import router from './router'; // 引入路由router.beforeEach((to, from, next) => {   next();});router.beforeResolve((to, from, next) => {  next();});router.afterEach((to, from) => {  console.log('afterEach 全局后置钩子');});
路由独享的守卫 你能够在路由配置上间接定义 beforeEnter 守卫
const router = new VueRouter({  routes: [    {      path: '/foo',      component: Foo,      beforeEnter: (to, from, next) => {        // ...      }    }  ]})
组件内的守卫你能够在路由组件内间接定义以下路由导航守卫
const Foo = {  template: `...`,  beforeRouteEnter (to, from, next) {    // 在渲染该组件的对应路由被 confirm 前调用    // 不!能!获取组件实例 `this`    // 因为当守卫执行前,组件实例还没被创立  },  beforeRouteUpdate (to, from, next) {    // 在以后路由扭转,然而该组件被复用时调用    // 举例来说,对于一个带有动静参数的门路 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,    // 因为会渲染同样的 Foo 组件,因而组件实例会被复用。而这个钩子就会在这个状况下被调用。    // 能够拜访组件实例 `this`  },  beforeRouteLeave (to, from, next) {    // 导航来到该组件的对应路由时调用,咱们用它来禁止用户来到    // 能够拜访组件实例 `this`    // 比方还未保留草稿,或者在用户来到前,    将setInterval销毁,避免来到之后,定时器还在调用。  }}

vue-router 路由钩子函数是什么 执行程序是什么

路由钩子的执行流程, 钩子函数品种有:全局守卫路由守卫组件守卫
  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入

Vue 模板编译原理

Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步

第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构DOM的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)

Vue-router 除了 router-link 怎么实现跳转

申明式导航

<router-link to="/about">Go to About</router-link>

编程式导航

// literal string pathrouter.push('/users/1')// object with pathrouter.push({ path: '/users/1' })// named route with params to let the router build the urlrouter.push({ name: 'user', params: { username: 'test' } })

答复范例

  • vue-router导航有两种形式:申明式导航和编程形式导航
  • 申明式导航形式应用router-link组件,增加to属性导航;编程形式导航更加灵便,可传递调用router.push(),并传递path字符串或者RouteLocationRaw对象,指定pathnameparams等信息
  • 如果页面中简略示意跳转链接,应用router-link最快捷,会渲染一个a标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航
  • 实际上外部两者调用的导航函数是一样的

Vue模版编译原理晓得吗,能简略说一下吗?

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

  • 生成AST树
  • 优化
  • codegen

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

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

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

vue如何监听对象或者数组某个属性的变动

当在我的项目中间接设置数组的某一项的值,或者间接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为Object.defineProperty()限度,监听不到变动。

解决形式:

  • this.$set(你要扭转的数组/对象,你要扭转的地位/key,你要改成什么value)
this.$set(this.arr, 0, "OBKoro1"); // 扭转数组this.$set(this.obj, "c", "OBKoro1"); // 扭转对象
  • 调用以下几个数组的办法
splice()、 push()、pop()、shift()、unshift()、sort()、reverse()

vue源码里缓存了array的原型链,而后重写了这几个办法,触发这几个办法的时候会observer数据,意思是应用这些办法不必再进行额定的操作,视图主动进行更新。 举荐应用splice办法会比拟好自定义,因为splice能够在数组的任何地位进行删除/增加操作

vm.$set 的实现原理是:

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

理解history有哪些办法吗?说下它们的区别

history 这个对象在html5的时候新退出两个api history.pushState()history.repalceState() 这两个API能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录。

从参数上来说:

window.history.pushState(state,title,url)//state:须要保留的数据,这个数据在触发popstate事件时,能够在event.state里获取//title:题目,根本没用,个别传null//url:设定新的历史纪录的url。新的url与以后url的origin必须是一样的,否则会抛出谬误。url能够时绝对路径,也能够是相对路径。//如 以后url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/window.history.replaceState(state,title,url)//与pushState 基本相同,但她是批改以后历史纪录,而 pushState 是创立新的历史纪录

另外还有:

  • window.history.back() 后退
  • window.history.forward()后退
  • window.history.go(1) 后退或者后退几步

从触发事件的监听上来说:

  • pushState()replaceState()不能被popstate事件所监听
  • 而前面三者能够,且用户点击浏览器后退后退键时也能够

Vue 组件通信有哪几种形式

  1. props 和$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  2. $parent,$children 获取以后组件的父组件和以后组件的子组件
  3. $attrs 和$listeners A->B->C。Vue 2.4 开始提供了$attrs 和$listeners 来解决这个问题
  4. 父组件中通过 provide 来提供变量,而后在子组件中通过 inject 来注入变量。(官网不举荐在理论业务中应用,然而写组件库时很罕用)
  5. $refs 获取组件实例
  6. envetBus 兄弟组件数据传递 这种状况下能够应用事件总线的形式
  7. vuex 状态治理