Vue源码~初始化过程及响应式原理 | 八月更文挑战

294 阅读1分钟

gw8I2D.png

  • 阅读源码因为flow会报红
    • 配置 setting.json中的 "javascript.validate.enable": false
  • 解析到泛型时以下代码没有语法高亮
    • 下载 babel-javascript 插件

首次渲染过程

  • Vue 初始化,实例成员,静态成员
  • new Vue()
  • this._init()
  • vm.$mount()
    • src/platforms/web/entry-runtime-with-compiler.js
    • 如果没有传递 render,把模板编译成 render 函数
    • compileToFunctions() 生成 render() 渲染函数
    • options.render = render
  • vm.$mount()
    • src/platforms/web/runtime/index.js
    • mountComponent()
  • mountComponent(this.el)
    • src/core/instance/lifecycle.js
    • 判断是否有 render 选项,如果没有但是传入了模板,并且当前是开发环境的话会发送警告
    • 触发 beforeMount
    • 定义 updateComponent
    • 创建 Watcher 实例
    • 触发 mounted
    • return vm
  • watcher.get()
    • 创建完 watcher 会调用一次 get
    • 调用 updateComponent()
    • 调用 vm._render() 创建 VNode
      • 调用 render.call(vm._renderProxy, vm.$createElement)
      • 调用实例化时 Vue 传入的 render()
      • 或者编译 template 生成的 render()
      • 返回 VNode
    • 调用 vm._update(vnode, ...)
      • 调用 vm.__patch__(vm.$el, vnode) 挂载真实 DOM

      • 记录 vm.$el

数据响应式原理

通过查看源码解决下面问题

vm.msg = { count: 0 } // 重新给属性赋值,是否是响应式的?

vm.arr[0] = 4// 给数组元素赋值,视图是否会更新?

vm.arr.length = 0, // 修改数组的 length,视图是否会更新?

vm.arr.push(4), // 视图是否会更新

响应式处理的入口

整个响应式处理的过程是比较复杂的

  • src/core/instance/init.js
    • initState(vm) vm 状态的初始化
    • 初始化了 _data_propsmethods
  • src/core/instance/state.js
// 数据初始化
if (opts.data) {
    initData(vm)
} else {
    observe(vm._data = {}, true /* asRootData */)
}

针对数组的特殊处理

动态添加一个响应式属性以及相关类似方法

  • Vue.set | vm.$set
  • Vue.delete | vm.$delete

watch

vm.$watch

vm.$watch(expOrFn, callback, [options])

  • 功能
    • 观察 Vue 实例变化 的一个表达式或计算属性函数,回调函数得到的参数为新值和旧值,表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
  • 参数
    • expOrFn:要监视的 $data 中的属性,可以是表达式或函数
    • callback:数据变化后执行的函数
      • 函数:回调函数
      • 对象:具有 handler 属性(字符串或者函数,如果该属性为字符串则 methods 中相应的定义
    • options:可选的选项
      • deep: 布尔类型,深度监听
      • immediate:布尔类型,是否立即执行一次回调函数
// https://cn.vuejs.org/v2/api/#vm-watch

var vm = new Vue({
  el: '#app',
  data: {
    a: '1',
    b: '2',
    msg: 'Hello Vue',
    user: {
      firstName: '诸葛',
      lastName: '亮'
    }
  }
})
// expOrFn 是表达式
vm.$watch('msg', function (newVal, oldVal) {
  // ...
})

三种类型的 Watcher

  • 没有静态方法,因为 $watch 方法中要使用 Vue 的实例
  • Watcher 分三种:计算属性 Watcher用户Watcher(侦听器)渲染Watcher
    • 创建顺序:计算属性 Watcher用户Watcher(侦听器)渲染Watcher
  • vm.$watch()
    • src/core/instance/state.js

nextTick

异步更新队列 - nextTick()

  • Vue 更新 DOM 是异步执行的,指的
    • 在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即 使用这个方法,获取更新后的DOM
  • vm.$nextTicik(function() {// 操作 dom}) 或者 Vue.nexttick(function() {})
  • vm.$nextTick() 代码演示
<div id="app">
    <p ref="p1">{{ msg }}</p>
</div>

var vm = new Vue({
  el: "#app",
  data: {
    msg: "Hello nextTick",
    name: "vue.js",
    title: 'title'
  },
  mounted() {
    this.msg = "hello world";
    this.name = "Hello snabbdom"
    this.title = "Vue.js"
    
    this.$nextTick(() => {
        console.log(this.$refs.p1.textContent)
    })
      
      
  }
})

源码

  • src/core/instance/render.js
Vue.prototype.$nextTick = function(fn: Function) {
  return nextTick(fn, this)
}
  • 手动调用 vm.$nextTick()
  • WatcherqueueWatcher 中执行 nextTick()
  • src/core/util/next-tick.js
// 需要注意看看 timerFunc 的实现
function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把 cb 加上异常处理存入 callbacks 数组中
  callbacks.push(() => {
    if (cb) {
      try {
        // 调用 cb()
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 调用
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    // 返回 promise 对象
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

响应式原理总结

总结

Watcher

vm.$watch

vm.$watch(expOrFn, callback, [options])

  • 功能
    • 观察 Vue 实例变化 的一个表达式或计算属性函数,回调函数得到的参数为新值和旧值,表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
  • 参数
    • expOrFn:要监视的 $data 中的属性,可以是表达式或函数
    • callback:数据变化后执行的函数
      • 函数:回调函数
      • 对象:具有 handler 属性(字符串或者函数,如果该属性为字符串则 methods 中相应的定义
    • options:可选的选项
      • deep: 布尔类型,深度监听
      • immediate:布尔类型,是否立即执行一次回调函数
// https://cn.vuejs.org/v2/api/#vm-watch

var vm = new Vue({
  el: '#app',
  data: {
    a: '1',
    b: '2',
    msg: 'Hello Vue',
    user: {
      firstName: '诸葛',
      lastName: '亮'
    }
  }
})
// expOrFn 是表达式
vm.$watch('msg', function (newVal, oldVal) {
  // ...
})