Vue深入学习4—指令和生命周期

1.Vue是怎么识别 v- 指令的?

首先将HTML结构解析成属性列表,存入到数组中,接着遍历数组中的每一个节点,获取到不同指令对应的方法,来看一下源码。

// 将HTML看作真正的属性列表
var ndoeAttrs = node.attributes;
var self = this;
// 类数组对象变为数组,一层一层的遍历节点
[].slice.call(nodeAttes).forEach(attr => {
    // 这里开始分析指令
    var attrName = attr.name;
    var value = attr.value;
    // 指令都是 v- 开头的
    var dir = attrName.substring(2);
    if(attrName.indexOf('v-') == 0){
        // v-下不同的指令
        if(dir == 'model'){
            // console.log('发现了model指令',value);
            // 添加Watcher
            new Watcher(self.$vue, value, value => {
                node.value = value;
            });
            // 得到 v 的值
            var v = self.getVueVal(self.$vue, value);
            // 显示 v 的值
            node.value = v;
            // 添加监听事件,基本实现双向绑定
            node.addEventListener('input', e => {
                var newVal = e.target.value;
                self.setVueVal(self.$vue, value, newVal);
                v = newVal;
            });
        }else if(dir == 'if'){
            // console.log('发现了if指令',value);
        }
    } 
})

2.v-model底层是怎么实现的?

v-model 会把关联的相应式数据(info.message),动态的绑定到表单元素的value属性上,让后监听 input 事件;

v-model 绑定的相应数据发生变化时,表单元素的value值也会随之变化。

<template>
  <div>
    <div class="message">{{ info.message }}</div>
    <div><input v-model="info.message" type="text"></div>
    <button @click="change">click</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        info: {}
      }
    },
    methods: {
      change () {
        this.info.message = 'hello world'
      }
    }
  }
</script>

面试题:v-for 和 v-if为什么不能一起用?

涉及到一个优先级的问题,v-for 比 v-if优先执行,如果一起使用,循环出来的每一项都会去判断一下v-if是否为true或者false,这样就会照成资源的浪费!

3.生命周期

Vue3生命周期.png
image-20210725222206025.png
  • beforeCreate & created

    属于实例化阶段,在_init方法内,DOM 被挂载时执行,两个函数都不能获取到 prop、data 中定义的值,也不能调用 methods中定义的函数。

Vue.prototype._init = function (options?: Object) {
  // ...
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // 在prop、data 之前解决注入
  initState(vm)
  initProvide(vm) // 解决初始化之后的prop、data
  callHook(vm, 'created')
  // ...
}
  • beforeMount &mounted

    属于挂载阶段,在mountComponent方法内,响应数据被修改时执行,对于同步渲染的子组件而言,mounted 钩子函数的执行顺序是先子后父

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  callHook(vm, 'beforeMount')
  
  let updateComponent
 
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      // 执行vm._update 把 VNode 渲染到 真实 DOM 
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }
  // 把它设为vm._watcher 在watcher的构造函数中.
  // 因为观察者的初始补丁可能会调用$forceUpdate(例如:inside child . exe)
  // 组件的挂载钩子),依赖于vm._watcher已经定义.
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false
  // 手动挂载实例,调用挂载在self上
  // 挂载在其插入的钩子中为渲染创建的子组件调用
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  • beforeUpdate & updated

    属于更新阶段,在渲染 Watcher 的 before 函数内,元素被销毁之前执行,在 callUpdatedHooks 函数中,等 vm._watcher 的回调执行完毕后,才能执行 update 函数。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // ...

  // 把它设为vm._watcher 在watcher的构造函数中.
  // 因为观察者的初始补丁可能会调用$forceUpdate(例如:inside child . exe)
  // 组件的挂载钩子),依赖于vm。_watcher已经定义.
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  // ...
}

  • beforeDestory & destroyed (3.x中更名为 beforeUnmount & unmounted

    属于销毁阶段,在 $destroy 函数前执行,从 parent$children 中删掉自身,删除 watcher

Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // 将self从父节点移除
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // 卸载 watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // 从数据ob中移除引用
    // frozen object 没有观察者。
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // 调用最后一个钩子
    vm._isDestroyed = true
    // 在当前redered 树上调用销毁钩子
    vm.__patch__(vm._vnode, null)
    // 销毁钩子函数
    callHook(vm, 'destroyed')
    // 关闭所有实例侦听器。
    vm.$off()
    // 删除vue reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // 发布循环引用
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }

renderTracked & renderTriggered(3.x新增)

都是跟踪虚拟DOM 重新渲染时调用,接收 debugger event 参数;

renderTracked() : 此事件告诉你哪个操作跟踪组件,以及该操作的目标对象和键。

renderTriggered() : 此事件告诉你哪个操作触发重新渲染,以及该操作的目标对象和键。

<div id="app">
  <button v-on:click="addToCart">Add to cart</button>
  <p>Cart({{ cart }})</p>
</div>
const app = Vue.vreateApp({
    data(){
        return{
            cart: 0
        }
    },
    // cart 操作*跟踪*了 组件
    renderTracked({ key, target, type }){
        console.log({ key, target, type })
        /*{ key: "cart", target:{cart: 0}, type: "get" }*/
    },
    // cart 操作*触发*了 重新渲染
    renderTriggered({ key, target, type }) {
        console.log({ key, target, type })
    },
    methods: {
        addToCart(){
            this.cart += 1
            /*{ key: "cart", target:{cart:1}, type: "set" }*/
        }
    }
})
app.mount('#app')

总结: Vue生命周期函数就是在初始化,及数据更新过程各个阶段执行不同的钩子函数;在created钩子函数中可以访问到数据,在mounted钩子函数中可以访问到DOM,在destroyed 钩子函数中可以做一些定时器销毁工作!

参考:v-model源码解析

剖析Vue原理&实现双向绑定MVVM

技术胖Vue3知识地图

Vue2与Vue3的生命周期

Vue2和Vue3的区别

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容