5

介绍

关于 Vue.js 的原理一直以来都是一个话题。经过几天的源码学习和资料介绍,我将一些个人理解的经验给写下来,希望能够与大家共勉。

附上GITHUB源码地址, 如果有任何不解 可以在 文章下面提出或者写下issue, 方便大家回答和学习, 有兴趣可以Star.
最后附上 LIVE DEMO

简单图解 Vue.js 内置对象

vue双向绑定原理

构造实例对象

应用创建时需要使用的构造函数对象, data 为我们的数据模型

const vm = new Vue({
  data: {
      foo: 'hello world'
  }
});

数据双向绑定

被观察者 observe

被观察对象,data属性里的值的变化,get时检查是否有新的观察员需要加入观察员集合, set时通知观察员集合里的观察员更新视图,被观察者有一个观察员的集合对象。

观察员集合对象 Dep

一个观察员的收集器, depend()负责将当前的 Dep.target 观察员加入观察员集合, data 中的每一项数据都会有对应的闭包dep对象, 数据对象会有一个内置的dep对象,用来通知嵌套的数据对象变化的情况。

观察员 watcher

由模板解析指令创建的观察员, 负责模板中的更新视图操作。保留旧的数据,以及设置钩子函数 update(), 等待被观察者数据通知更新,对比新的value与旧数据, 从而更新视图。

数据代理 proxyData

我们的关注点在与创建后的vm, 其 options.data, 被挂载至vm._data, 同时被代理至 vm 上, 以至于 vm._data.foo 等价于 vm.foo, 代理函数代码如下:

const noop = function () {}
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

// initState 时执行 initData
function initData () {
    
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
      const key = keys[i]
      // key不以 $ 或 _开头
      if (!isReserved(key)) {
        proxy(vm, `_data`, key)
      }
    }
    // do something
}

数据劫持 defineProperty


/**
 * Define a reactive property on an Object.
 */
function defineReactive(obj, key, val) {

  // 观察员集合
  const dep = new Dep();
  
  // data 内属性描述 
  const property = Object.getOwnPropertyDescriptor(obj, key);

  // 属性不可再次修改
  if (property && property.configurable === false) {
    return;
  }

  // 属性预定义 getter/setters
  const getter = property && property.get;
  const setter = property && property.set;

  // 如果val为对象, 获取val的 被观察数据对象 __ob__
  let childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 被观察数据被使用时, 获取被观察员最新的数据
      const value = getter ? getter.call(obj) : val
      
      // 观察员在new时或使用 get()时, 注入给被观察员对象集合
      if (Dep.target) {
        // 将当前的 watcher 传递给 观察员
        dep.depend();
        if (childOb) {
          // 将当前的 watcher 传递给子对象的 观察员
          childOb.dep.depend();
        }
      }
      return val;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 新value设置被观察者对象 __ob__
      childOb = observe(newVal);
      
      // 通知数据对象依赖的观察员, 更新 update()
      dep.notify();
    }
  });
}

计算属性介绍

初始化执行过程

计算属性图形介绍

计算属性的定义和使用

var vm = new Vue({
    data: {
        firstname: 'li',
        lastname: 'yanlong'
    },
    computed: {
        fullname () {
            return this.firstname + this.lastname;
        }
    }
});
console.log(vm.fullname);

核心代码解读

const computedWatcherOptions = {lazy: true};
function initComputed (vm, computedOptions) {
    // 创建计算属性对应的观察员对象
    // 获取计算属性时收集 内置数据对象的 dep
    const watchers = vm._computedWatchers = Object.create(null)
    
    for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        );
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        } 
    }
}

function defineComputed (target, key, userDef) {
    // 如果不为服务端渲染,则使用缓存value
    const shouldCache = !isServerRendering()
    
    // sharedPropertyDefinition 共享属性配置
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : userDef
      sharedPropertyDefinition.set = noop
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : userDef.get
        : noop
      sharedPropertyDefinition.set = userDef.set
        ? userDef.set
        : noop
    }
    // 给 vm对象定义计算属性 
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

// 创建
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 计算属性的 watcher 有数据更新过, 重新计算
      if (watcher.dirty) {
        watcher.evaluate()
      }
      
      // 视图指令 使用了计算属性
      // 将计算属性的watcher依赖传递给视图指令的 watcher
      if (Dep.target) {
        // 源码地址
        // https://github.com/vuejs/vue/blob/master/src/core/observer/watcher.js#L210
        watcher.depend()
      }
      return watcher.value
    }
  }
}

计算属性知识点介绍

1. 计算属性的 watcher 对象
计算属性函数在读取它本身的value时, 使用一个watcher观察员进行代理. 通过对原始数据的劫持, 将watcher 观察员添加到原始数据的dep依赖集合中.

2. deps的数据对象发生更新
举例,如果firstname 或者 lastname 任意一个更新,那么就会设置计算属性的watcher.dirty = true, 而当其它指令或者函数使用,计算属性会重新计算值,如果是视图指令,还会重新该指令的watcher的数据对象依赖。

Watcher 观察员种类

目前了解情况来看, 主要分三类

  1. 视图指令的 watcher
  2. 计算属性的 watcher
  3. 用户自定义的 watcher

wayneli
1.4k 声望828 粉丝

2017-2018年目标