Vue.js 2.0 的基础

Vue.js 2.0 的基础可以概括为以下几点:

  1. 数据响应式。
  2. 组件化。
  3. 指令系统。

一个 Vue.js 应用程序由多个 Vue 实例组成,每个 Vue 实例都代表一个特定的区域内的视图,Vue 实例包含了一个数据模型和一个视图模板。Vue.js 的核心是数据响应式,也就是当我们改变数据时,视图会自动更新。

Vue.js 的源码结构

Vue.js 源码非常庞大,整个源码包括以下几个部分:

  1. Observer:用于实现数据响应式的核心组件。
  2. Watcher:用于处理视图中具体的更新操作。
  3. Compiler:用于将组件模板编译成渲染函数。
  4. Runtime:用于实现组件和指令等运行时的公共逻辑。
  5. Platform:用户平台特定的逻辑。

Vue.js 源码解析

在整个 Vue.js 源码中,Observer 和 Watcher 是比较核心和重要的两个部分。

  1. Observer

Observer 的主要作用是实现数据响应式机制,它通过递归的方式对数据对象进行劫持。当数据对象的某个属性发生变化时,会自动通知上层依赖进行更新。

Observer 的实现代码如下:

class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep()

    // 如果该对象不可扩展,则直接返回。
    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }

  /**
   * 循环遍历对象的每个属性,使得每个属性都成为响应式的。
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

/**
 * 对一个对象的属性进行劫持,即实现数据响应式机制。
 */
function defineReactive(obj, key, val) {
  const dep = new Dep()

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) {
        return
      }
      val = newVal
      dep.notify()
    }
  })
}

 2. Watcher

Watcher 的主要作用是收集依赖,并在数据变化时触发相应的更新操作。当一个组件初始化完毕时,会创建一个渲染 Watcher,它会在组件触发更新时执行渲染函数,并进行更新操作。

Watcher 的实现代码如下:

class Watcher {
  constructor(vm, expOrFn, callback) {
    this.vm = vm
    this.callback = callback
    this.depIds = {}

    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }

    // 在实例化 Watcher 对象时,将它自身赋值给 Dep.target,此时
    // Dep.target 维护了一个栈结构。
    Dep.target = this
    this.value = this.get()
    Dep.target = null
  }

  /**
   * 收集依赖。
   */
  addDep(dep) {
    if (!this.depIds.hasOwnProperty(dep.id)) {
      dep.addSub(this)
      this.depIds[dep.id] = dep
    }
  }

  /**
   * 计算表达式的值并返回。
   */
  get() {
    // 当计算表达式的值时,依赖会被自动收集。
    return this.getter.call(this.vm, this.vm)
  }

  /**
   * 当表达式的值发生变化时会被调用,调用一系列的回调函数进行更新操作。
   */
  update() {
    const oldValue = this.value
    this.value = this.get()
    this.callback.call(this.vm, this.value, oldValue)
  }
}

/**
 * 将字符串路径解析成函数。
 */ 
function parsePath(path) {
  const segments = path.split('.')
  return function(obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

代码展示

下面是一个简单的代码示例,它可以在控制台中输出 data 对象中 count 属性的值以及更新后的值:

<body>
  <div id="app">
    {{ count }}
  </div>
</body>

<script>
  const data = { count: 0 }

  new Vue({
    el: '#app',
    data: data
  })

  // 修改 count 属性的值,触发更新操作。
  data.count = 1
</script>