1.watch与computed的区别
watch(侦听器),当需要在数据变化时执行异步或开销较大的操作时需要用的watch。无缓存。
computed(计算属性),在模板中如果有复杂的表达式而且多次用的,可以使用computed。计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。
2.vue生命周期及对应的行为
vue生命周期有beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed
- beforeCreate: 在Vue实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
- created: 在实例创建完成后被立即调用,此时数据观测 (data observer),属性和方法的运算,watch/event 事件回调已经完成。然后挂载阶段还没开始
- beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
- mounted:实例被挂载后调用,这时内部子组件不一定一起被挂载,可以使用==$nextTick==方法,执行要在所有视图渲染完毕后的操作。该钩子在服务器端渲染期间不被调用。
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
- updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。此时组件 DOM 已经更新。该钩子不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 ==vm.$nextTick==
- activated:被 keep-alive 缓存的组件激活时调用
- deactivated:被 keep-alive缓存的组件停用时调用。
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
- errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
3.vue父子组件生命周期执行顺序
加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
数据更新:父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
4.组件间通讯方法
- props父组件向子组件传递参数,自定义事件子组件向父组件传递参数($emit)
- 注册eventBus(就是一个vue实例),在实例上注册自定义事件,进行组件间的通讯(
on)
- Vuex(State、Getter、Mutation、Action、Module)
listeners
attrs" 传入内部组件——在创建高级别的组件时非常有用
listeners" 传入内部组件——在创建更高层次的组件时非常有用。
- provide 和 inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象(使用Vue.observable),那么其对象的属性还是可响应的。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
- ref、
children
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
children:访问父 / 子实例
这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
5.如何实现一个指令
可以使用Vue.directive注册全局自定义指令,也可以在组件中使用directives选项注册局部自定义指令。
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
//组件
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
指令对象的钩子函数bind、inserted、update、componentUpdated、unbind
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
- componentUpdated:指令所在组件的 VNode 及其子 VNode全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数el、binding、vnode、oldVnode
- el:指令所绑定的元素,可以用来直接操作 DOM 。
- binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
利用event loop的原理,当数据发生改变,触发DOM修改,到UI render是一个事件循环。Vue.nextTick方法的参数回调函数,会被放入一个队列中,等待下次事件循环执行flushCallbacks函数遍历执行队列中的回调函数(希望在DOM渲染后执行的操作)。Vue.nextTick方法实现模块中还声明了microTimerFunc 和 macroTimerFunc两个方法,对应Macrotask和Microtask
对于 macro task 的实现,优先检测是否支持原生setImmediate,这是一个高版本 IE 和 Edge 才支持的特性,不支持的话再去检测是否支持原生的MessageChannel,如果也不支持的话就会降级为 setTimeout 0;而对于 micro task的实现,则检测浏览器是否原生支持 Promise,不支持的话直接指向 macro task 的实现。
8.双向绑定
Vue数据双向绑定实际上就是利用Object.defineProperty和存取描述符,对属性的存取进行监听,从而进行相应的操作,示例:
// Vue老版本实现
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
})
// Vue-next版本
Proxy(data, {
get(target, key) {
return target[key];
},
set(target, key, value) {
let val = Reflect.set(target, key, value);
_that.$dep[key].forEach(item => item.update());
return val;
}
})
然后通过发布订阅者模式,当数据发生改变,触发视图上的渲染变化
9.Proxy与Object.defineProperty的优劣对比
Object.defineProperty
(1). 无法监听数组变化,arr[indexOfItem] = newValue这种是无法检测的,push、pop、shift、unshift、splice、sort、reverse方法可以监听到,是因为Vue内部对数组的原型对象上的这些方法进行了处理修改。
(2). 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历
Proxy
(1). Proxy可以直接监听对象而非属性
(2). Proxy可以直接监听数组的变化
(3). Proxy返回的是一个新对象,我们可以只操作新的对象达到目的
(4). Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
(5).Proxy的劣势就是兼容性问题,而且无法用polyfill磨平