21 Vue complier 实现
-
模板解析这种事,本质是将数据转化为一段 html
,最开始出现在后端,经过各种处理吐给前端。随着各种mv*
的兴起,模板解析交由前端处理。 -
总的来说, Vue complier
是将template
转化成一个render
字符串。
可以简单理解成以下步骤:
-
parse
过程,将template
利用正则转化成AST
抽象语法树。 -
optimize
过程,标记静态节点,后diff
过程跳过静态节点,提升性能。 -
generate
过程,生成render
字符串
22 怎么快速定位哪个组件出现性能问题
用
timeline
工具。 大意是通过timeline
来查看每个函数的调用时常,定位出哪个函数的问题,从而能判断哪个组件出了问题
23 开发中常用的指令有哪些
-
v-model
:一般用在表达输入,很轻松的实现表单控件和数据的双向绑定 -
v-html
: 更新元素的innerHTML
-
v-show
与v-if
: 条件渲染, 注意二者区别
使用了v-if的时候,如果值为false,那么页面将不会有这个html标签生成。 v-show则是不管值为true还是false,html元素都会存在,只是CSS中的display显示或隐藏
-
v-on
:click
: 可以简写为@click
,@
绑定一个事件。如果事件触发了,就可以指定事件的处理函数 -
v-for
:基于源数据多次渲染元素或模板块 -
v-bind
: 当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM
语法:
v-bind:title="msg"
简写::title="msg"
24 Proxy 相比于 defineProperty 的优势
Object.defineProperty() 的问题主要有三个:
-
不能监听数组的变化 -
必须遍历对象的每个属性 -
必须深层遍历嵌套的对象
Proxy 在 ES2015 规范中被正式加入,它有以下几个特点
-
针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 第二个问题 -
支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。
除了上述两点之外,Proxy 还拥有以下优势:
-
Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富 -
Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。
25 vue-router 有哪几种导航守卫?
-
全局守卫 -
路由独享守卫 -
路由组件内的守卫
全局守卫
vue-router全局有三个守卫
-
router.beforeEach
全局前置守卫 进入路由之前 -
router.beforeResolve
全局解析守卫(2.5.0+) 在beforeRouteEnter
调用之后调用 -
router.afterEach
全局后置钩子 进入路由之后
// 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 全局后置钩子');
});
路由独享守卫
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
})
路由组件内的守卫
-
beforeRouteEnter 进入路由前, 在路由独享守卫后调用 不能 获取组件实例 this,组件实例还没被创建 -
beforeRouteUpdate (2.2) 路由复用同一个组件时, 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 this -
beforeRouteLeave 离开当前路由时, 导航离开该组件的对应路由时调用,可以访问组件实例 this
26 组件之间的传值通信
组件之间通讯分为三种: 父传子、子传父、兄弟组件之间的通讯
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>
2. 子组件向父组件通信
父组件向子组件传递事件方法,子组件通过
$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>
3. 非父子, 兄弟组件之间通信
vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样:
import Vue from 'vue'
export default new Vue()
在需要通信的组件都引入Bus.js:
<template>
<button @click="toBus">子组件传给兄弟组件</button>
</template>
<script>
import Bus from '../common/js/bus.js'
export default{
methods: {
toBus () {
Bus.$emit('on', '来自兄弟组件')
}
}
}
</script>
另一个组件也import Bus.js 在钩子函数中监听on事件
import Bus from '../common/js/bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
Bus.$on('on', (msg) => {
this.message = msg
})
}
}
27 Vue与Angular以及React的区别?
Vue与AngularJS的区别
-
Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript -
AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。 -
AngularJS社区完善, Vue的学习成本较小
Vue与React的区别
-
vue组件分为全局注册和局部注册,在react中都是通过import相应组件,然后模版中引用; -
props是可以动态变化的,子组件也实时更新,在react中官方建议props要像纯函数那样,输入输出一致对应,而且不太建议通过props来更改视图; -
子组件一般要显示地调用props选项来声明它期待获得的数据。而在react中不必需,另两者都有props校验机制; -
每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现; -
使用插槽分发内容,使得可以混合父组件的内容与子组件自己的模板; -
多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法; -
Vue增加的语法糖computed和watch,而在React中需要自己写一套逻辑来实现; -
react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等;而 vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。 -
react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来确实方便一些, 比如 redux的combineReducer就对应vuex的modules, 比如reselect就对应vuex的getter和vue组件的computed, vuex的mutation是直接改变的原始数据,而redux的reducer是返回一个全新的state,所以redux结合immutable来优化性能,vue不需要。 -
react是整体的思路的就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,组件的横向拆分一般是通过高阶组件。而vue是数据可变的,双向绑定,声明式的写法,vue组件的横向拆分很多情况下用mixin
28 vuex是什么?怎么使用?哪种功能场景使用它?
-
vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 data -
state 里面存放的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新 -
它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性
vuex的使用借助官方提供的一张图来说明:
Vuex有5种属性: 分别是 state、getter、mutation、action、module;
state
Vuex
使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据
mutations
mutations
定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据
action
-
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。 -
vuex 一般用于中大型 web 单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用 vuex 的必要性不是很大,因为完全可以用组件 prop 属性或者事件来完成父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据。 -
使用Vuex解决非父子组件之间通信问题 vuex 是通过将 state 作为数据中心、各个组件共享 state 实现跨组件通信的,此时的数据完全独立于组件,因此将组件间共享的数据置于 State 中能有效解决多层级组件嵌套的跨组件通信问题 -
vuex 作为数据存储中心 vuex 的 State 在单页应用的开发中本身具有一个“数据库”的作用,可以将组件中用到的数据存储在 State 中,并在 Action 中封装数据读写的逻辑。这时候存在一个问题,一般什么样的数据会放在 State 中呢? 目前主要有两种数据会使用 vuex 进行管理: 1、组件之间全局共享的数据 2、通过后端异步请求的数据 比如做加入购物车、登录状态等都可以使用Vuex来管理数据状态
一般面试官问到这里vue基本知识就差不多了, 如果更深入的研究就是和你探讨关于vue的底层源码;或者是具体在项目中遇到的问题,下面列举几个项目中可能遇到的问题:
-
开发时,改变数组或者对象的数据,但是页面没有更新如何解决? -
vue弹窗后如何禁止滚动条滚动? -
如何在 vue 项目里正确地引用 jquery 和 jquery-ui的插件
28 watch与computed的区别
computed:
-
computed是计算属性,也就是计算值,它更多用于计算值的场景 -
computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算 computed适用于计算比较消耗性能的计算场景
watch:
-
更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作 -
无缓存性,页面重新渲染时值不变化也会执行
小结:
-
当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed -
如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
29、Vue是如何实现双向绑定的?
利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,然后根据变化进行后续响应,在vue3.0中通过Proxy代理对象进行类似的操作。
// 这是将要被劫持的对象
const data = {
name: '',
};
function say(name) {
if (name === '古天乐') {
console.log('给大家推荐一款超好玩的游戏');
} else if (name === '渣渣辉') {
console.log('戏我演过很多,可游戏我只玩贪玩懒月');
} else {
console.log('来做我的兄弟');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
},
});
});
data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月
29 Vue2.x 响应式原理
Vue 采用数据劫持结合发布—订阅模式的方法,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
-
Observer
遍历数据对象,给所有属性加上setter
和getter
,监听数据的变化 -
compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情
-
在自身实例化时往属性订阅器 ( dep
) 里面添加自己 -
待属性变动 dep.notice()
通知时,调用自身的update()
方法,并触发Compile
中绑定的回调
Vue3.x响应式数据原理
Vue3.x
改用Proxy
替代Object.defineProperty
。因为Proxy
可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy
只会代理对象的第一层,那么Vue3
又是怎样处理这个问题的呢?
判断当前
Reflect.get的
返回值是否为Object
,如果是则再通过reactive
方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断
key
是否为当前被代理对象target
自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger
30 v-model双向绑定原理
v-model
本质上是语法糖,v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件
-
text
和textarea
元素使用 value 属性和 input 事件 -
checkbox
和radio
使用 checked 属性和 change 事件 -
select
字段将 value 作为 prop 并将 change 作为事件
所以我们可以v-model进行如下改写:
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
-
这个语法糖必须是固定的,也就是说属性必须为 value
,方法名必须为:input
。 -
知道了 v-model
的原理,我们可以在自定义组件上实现v-model
//Parent
<template>
{{num}}
<Child v-model="num">
</template>
export default {
data(){
return {
num: 0
}
}
}
//Child
<template>
<div @click="add">Add</div>
</template>
export default {
props: ['value'],
methods:{
add(){
this.$emit('input', this.value + 1)
}
}
}
原文始发于微信公众号(消失的程序员):Vue.js面试题精选–3
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/250918.html