子组件能够间接扭转父组件的数据吗?
子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 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的⽣命周期。
- beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到data、computed、watch、methods上的办法和数据。
- created(创立后) :实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到
$el
属性。 - beforeMount(挂载前):在挂载开始之前被调用,相干的render函数首次被调用。实例已实现以下的配置:编译模板,把data外面的数据和模板生成html。此时还没有挂载html到页面上。
- mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的html内容替换el属性指向的DOM对象。实现模板中的html渲染到html 页面中。此过程中进行ajax交互。
- beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
- updated(更新后) :在因为数据更改导致的虚构DOM从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM曾经更新,所以能够执行依赖于DOM的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,
this
仍能获取到实例。 - destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive
独有的生命周期,别离为 activated
和 deactivated
。用 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中应用插件的步骤
- 采纳
ES6
的import ... from ...
语法或CommonJS
的require()
办法引入插件 - 应用全局办法
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 路由钩子函数是什么 执行程序是什么
路由钩子的执行流程, 钩子函数品种有:全局守卫
、路由守卫
、组件守卫
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+
)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+
)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发
DOM
更新。 - 调用
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
对象,指定path
、name
、params
等信息 - 如果页面中简略示意跳转链接,应用
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 组件通信有哪几种形式
- props 和$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
- $parent,$children 获取以后组件的父组件和以后组件的子组件
- $attrs 和$listeners A->B->C。Vue 2.4 开始提供了$attrs 和$listeners 来解决这个问题
- 父组件中通过 provide 来提供变量,而后在子组件中通过 inject 来注入变量。(官网不举荐在理论业务中应用,然而写组件库时很罕用)
- $refs 获取组件实例
- envetBus 兄弟组件数据传递 这种状况下能够应用事件总线的形式
- vuex 状态治理