生命周期钩子函数
在 beforCreate 钩子函数调用的时候,是获取不到props或者data中的数据的,因为这些数据的初始化都在initState中。
然后执行created钩子函数,在这个钩子函数中就可以访问props和datas了,但是这时组件还没有挂载。
然后执行beforeMount钩子函数,开始创建VDOM,最后执行mounted钩子,并把VDON渲染成真实DOM并渲染数据。组建中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会还行根组件的挂载钩子。
数据更新时会调用的钩子函数beforeUpdate和Updated,这两个钩子函数分别在数据更新前和更新后会调用。
keep-alive独有的生命周期,分别为actived和deactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存在内存中并执行deavtived钩子函数,命中缓存渲染后会执行actived钩子函数。
最后是销毁组件的钩子函数beforeDestroy 和destroyed 。在beforeDestroy 中适合移除事件、定时器等等。否则可能引起内存泄露。然后进行一系列的销毁操作,如果有子组件,也会递归销毁子组件,所有子组件销毁完毕后会执行根组件的destroyed钩子函数。
组件通信
组件通信一般分为一下几种情况:
父子组件通信
父组件通过props传递数据给子组件,子组件通过emit发送事件传递数据给父组件。
这种通信方式是单向数据流,父组件通过props传递数据,子组件不能直接修改props,而是必须通过发送事件的方式告知父组件修改数据。
还可以通过$parent或者$children来访问组件实例中的方法和数据。
兄弟组件通信
可以通过查找父组件中的子组件实现,也就是this.$parent.$children ,在$children中可以通过组件 name 查询到需要的组件实例,然后通信。
任意组件通信
可以通过Vuex和Event Bus解决。
mixin和mixins区别
mixin 用于全局混入,会影响到每个组件实例。
Vue.mixin({
beforeCreat(){
// 这种方式会影响每个组件的beforeCreate钩子函数
}
})
mixins是最常用的扩展组件的方式。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins混入代码。
mixins混入的钩子函数会先于组件内的钩子函数执行,并且遇到同名选项的时候会有选择性的进行合并。
computed和watch区别
computed是计算属性,依赖其他属性计算值,并且computed的值有缓存,只有当计算只变化才会返回内容。
watch监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用watch。
组件复用时所有组件实例都会共享data,如果data是对象的话,就会造成一个组件修改data以后影响到其他所有组件,所以需要将data写成函数,每次用到就调用一次函数获得新的数据。
当使用new Vue()方式的时候,无论我们将data设置为对象还是函数都可以,因为new Vue()方式生成一个根组件,该组件不会复用,也就不存在共享data的情况了。
vue内部使用了Object.defineProperty()来实现数据响应式,通过这个函数可以监听到set和get的事件。
var data = {name : 'yck'}
observe(data);
let name = data.name; // get value
data.name = 'yyy' // set value
function observe(obj){
// 判断类型
if(!obj || typeof obj !== 'object'){
return;
}
Object.keys(obj).forEach(key=>{
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, val){
observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter(){
return val
},
set : funtcion reacticeSetter(newVal){
console.log("change value")
val = newVal
}
})
}
只有执行了依赖收集,才能在属性更新的时候派发更新。
<div>{{name}}</div>
在解析如上模板代码中,会遇到{{name}}就会进行依赖收集
接下来我们实现一个Dep类,用来解耦属性的依赖收集和派发更新操作。
class Dep{
constructor(){
this.subs =[]
}
addSub(sub){ // 添加依赖
this.subs.push(sub)
}
notify(){ //更新
this.subs.forEach(sub=>{
sub.update()
})
}
}
Dep.target = null // 全局属性,通过该属性配置Watcher
当需要依赖收集的时候调用addSub,当需要派发更新的时候调用notify。
Vue组件挂载时添加响应式的过程:
触发依赖收集:
class Watcher{
constructor(obj, key, cb){
Dep.target = this;
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update(){
this.value = this.obj[this.key]
this.cb(this.value)
}
}
此时需要对defineReactive函数进行改造
function defineReactive(obj, key, val){
observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter(){
if(Dep.target){ //将Watcher添加到订阅
dp.addSub(Dep.target)
}
return val
},
set : funtcion reacticeSetter(newVal){
console.log("change value")
val = newVal
dp.notify() // 执行watcher的update方法
}
})
}
下面测试一下代码:
var data ={name: 'yck'}
observe(data)
function update(value){
document.querySelector('div').innerText = value;
}
new Watcher(data,'name',update);
data.name = 'xxx'
Vue会通过编译器将模板通过几个阶段最终编译成render函数,然后通过执行render函数生成Virtual Dom 最终映射为真是的DOM。
编译过程可分为三个阶段:
第一阶段,最主要的事情还是通过各种各样的正则表达式匹配模板中的内容,然后将内容提取出来做各种逻辑操作,接下来生成一个最基本的AST对象
{
type :1, // 类型
tag, // 标签属性
attrsList: attrs, //属性列表
attrsMap : makeAttrsMap(attrs), //属性映射
parent, //父节点
children:[] //子节点
}
在这一阶段,还有一起其他的判断逻辑,比如对比前后开闭标签是否一致……
第二阶段,优化AST阶段,对阶段进行了静态内容提取,即将永远不会变动的节点提取出来,实现复用Virtual DOM。
最后阶段,主要是遍历整个AST,根据不同的条件生成不同的代码。
每张故作坚强的笑脸背后,是怎样风雨漂泊的一生---Lin