乐于分享
好东西不私藏

Vue v2.6.11 响应式实现源码解析

本文最后更新于2026-01-02,某些文章具有时效性,若有错误或已失效,请在下方留言或联系老夜

Vue v2.6.11 响应式实现源码解析

注:建议学习源码从易到难(学习react也可建议先学习react16),核心是感受框架设计的理念,学习vue2后,再去对照学习vue3,从中对比效果更好。

github源码地址

https://github.com/vuejs/vue/tree/v2.6.11

源码学习路线(红色是重点了解模块)

  1. 响应式实现篇

学习Vue中如何实现数据的响应式系统,从而达到数据驱动视图

  1. 虚拟 DOM 篇

学习什么是虚拟 DOM,以及Vue中的DOM-Diff原理

  1. 模板编译篇

学习Vue内部是怎么把template模板编译成虚拟DOM,从而渲染出真实DOM

  1. 实例方法篇

学习Vue中所有实例方法(即所有以$开头的方法)的实现原理

  1. 全局 API 篇

学习Vue中所有全局API的实现原理

  1. 生命周期篇

学习Vue中组件的生命周期实现原理

  1. 指令篇

学习Vue中所有指令的实现原理

  1. 过滤器篇

学习Vue中所有过滤器的实现原理

  1. 内置组件篇

学习Vue中内置组件的实现原理

项目目录:complier 和 core 是核心

├─dist                   # 项目构建后的文件├─scripts                # 与项目构建相关的脚本和配置文件├─flow                   # flow的类型声明文件├─src                    # 项目源代码│    ├─complier          # 与模板编译相关的代码│    ├─core              # 通用的、与运行平台无关的运行时代码│    │  ├─observe        # 实现变化侦测的代码│    │  ├─vdom           # 实现virtual dom的代码│    │  ├─instance       # Vue.js实例的构造函数和原型方法│    │  ├─global-api     # 全局api的代码│    │  └─components     # 内置组件的代码│    ├─server            # 与服务端渲染相关的代码│    ├─platforms         # 特定运行平台的代码,如weex│    ├─sfc               # 单文件组件的解析代码│    └─shared            # 项目公用的工具代码└─test                   # 项目测试代码

Vue的响应式实现

众所周知,Vue最大的特点之一就是数据驱动视图,那么什么是数据驱动视图呢?在这里,我们可以把数据理解为状态,而视图就是用户可直观看到页面。页面不可能是一成不变的,它应该是动态变化的,而它的变化也不应该是无迹可寻的,它或者是由用户操作引起的,亦或者是由后端数据变化引起的,不管它是因为什么引起的,我们统称为它的状态变了,它由前一个状态变到了后一个状态,页面也就应该随之而变化,所以我们就可以得到如下一个公式:

UI = render(state)

上述公式中:状态state是输入,页面UI输出,状态输入一旦变化了,页面输出也随之而变化。我们把这种特性称之为数据驱动视图。

Object的响应式实现

使对象变得可观测:this.walk(value);
let car = {};let val = 3000;Object.defineProperty(car, "price", {  configurabeltrue,  enumerabletrue,  get() {    console.log("price属性被读取了");    return val;  },  set(newVal) {    console.log("price属性被修改了");    val = newVal  }})

首先定义了一个observer类,将一个普通的object对象转换为可观测的object并且会给value新增一个__ob__的属性,相当于给value打了一个标记,表示已经被转化成响应式数据,避免重复操作。

判断数据的类型是否数组,只有object的数据才调用walk方法,把每一个属性转换成getter/setter的形式去监听数据变化。

if (Array.isArray(value)) {      if (hasProto) {        protoAugment(value, arrayMethods)      } else {        copyAugment(value, arrayMethods, arrayKeys)      }      this.observeArray(value)    } else {      this.walk(value)    }}

在defineReactive中

/** * Define a reactive property on an Object. */export function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: ?Function,  shallow?: boolean) {  // 实例化一个依赖管理器,生成一个依赖管理数组dep  const dep = new Dep()  const property = Object.getOwnPropertyDescriptor(obj, key)  if (property && property.configurable === false) {    return  }  // cater for pre-defined getter/setters  const getter = property && property.get  const setter = property && property.set  if ((!getter || setter) && arguments.length === 2) {    val = obj[key]  }  let childOb = !shallow && observe(val)  Object.defineProperty(obj, key, {    enumerabletrue,    configurabletrue,    getfunction reactiveGetter () {      const value = getter ? getter.call(obj) : val      if (Dep.target) {        // 在getter中收集依赖        dep.depend()        if (childOb) {          childOb.dep.depend()          if (Array.isArray(value)) {            dependArray(value)          }        }      }      return value    },    setfunction reactiveSetter (newVal) {      const value = getter ? getter.call(obj) : val      /* eslint-disable no-self-compare */      if (newVal === value || (newVal !== newVal && value !== value)) {        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter()      }      // #7981: for accessor properties without setter      if (getter && !setter) return      if (setter) {        setter.call(obj, newVal)      } else {        val = newVal      }      childOb = !shallow && observe(newVal)      // 在setter中通知依赖更新      dep.notify()    }  })}
依赖收集

我们可以监听到一个数据的变化,然后去更新视图,但是我们不可能数据一变化,就把全部视图都更新一遍,因此我们需要知道,谁依赖了这个数据。我们需要给每个数据建立一个依赖数组(一个数据可能被多处使用),谁依赖了这个数据(谁用到了这个数据),我们就把谁放入到依赖数组中,当这个数据发生变化,我们就去他对应的依赖数组中,通知每个依赖,告诉他们:你们依赖的数据变化了快去更新。

所谓的谁用到了这个数据,就是谁获取了这个数据。

在getter中收集依赖,在setter中去通知依赖更新。

我们应该给每一个数据都建立一个依赖管理器,把这个数据所有的依赖都管理起来,这里就我们的依赖管理器Dep。

/* @flow */import type Watcher from './watcher'import { remove } from '../util/index'import config from '../config'let uid = 0/** * A dep is an observable that can have multiple * directives subscribing to it. */export default class Dep {  static target: ?Watcher;  id: number;  subsArray<Watcher>;  constructor () {    this.id = uid++    this.subs = []  }  addSub (subWatcher) {    this.subs.push(sub)  }  removeSub (subWatcher) {    remove(this.subs, sub)  }  depend () {    if (Dep.target) {      Dep.target.addDep(this)    }  }  notify () {    // stabilize the subscriber list first    const subs = this.subs.slice()    if (process.env.NODE_ENV !== 'production' && !config.async) {      // subs aren't sorted in scheduler if not running async      // we need to sort them now to make sure they fire in correct      // order      subs.sort((a, b) => a.id - b.id)    }    for (let i = 0, l = subs.length; i < l; i++) {      subs[i].update()    }  }}// The current target watcher being evaluated.// This is globally unique because only one watcher// can be evaluated at a time.Dep.target = nullconst targetStack = []export function pushTarget (target: ?Watcher) {  targetStack.push(target)  Dep.target = target}export function popTarget () {  targetStack.pop()  Dep.target = targetStack[targetStack.length - 1]}
依赖到底是谁

在Vue中实现了一个叫Watcher的类,Watcher类的实例就是我们上边说的“谁”。谁用到了数据,谁就是依赖,我们就给谁创建一个Watcher实例。在之后数据变化时,我们不会直接去通知依赖更新,而是通知这个依赖对应的Watch实例,由Watcher实例去通知真正的视图。

export default class Watcher {  vm: Component;  expression: string;  cb: Function;  id: number;  deep: boolean;  user: boolean;  lazy: boolean;  sync: boolean;  dirty: boolean;  active: boolean;  deps: Array<Dep>;  newDeps: Array<Dep>;  depIds: SimpleSet;  newDepIds: SimpleSet;  before: ?Function;  getter: Function;  value: any;  constructor (    vm: Component,    expOrFn: string | Function,    cb: Function,    options?: ?Object,    isRenderWatcher?: boolean  ) {    this.vm = vm    if (isRenderWatcher) {      vm._watcher = this    }    vm._watchers.push(this)    // options    if (options) {      this.deep = !!options.deep      this.user = !!options.user      this.lazy = !!options.lazy      this.sync = !!options.sync      this.before = options.before    } else {      this.deep = this.user = this.lazy = this.sync = false    }    this.cb = cb    this.id = ++uid // uid for batching    this.active = true    this.dirty = this.lazy // for lazy watchers    this.deps = []    this.newDeps = []    this.depIds = new Set()    this.newDepIds = new Set()    this.expression = process.env.NODE_ENV !== 'production'      ? expOrFn.toString()      : ''    // parse expression for getter    if (typeof expOrFn === 'function') {      this.getter = expOrFn    } else {      this.getter = parsePath(expOrFn)      if (!this.getter) {        this.getter = noop        process.env.NODE_ENV !== 'production' && warn(          `Failed watching path: "${expOrFn}" ` +          'Watcher only accepts simple dot-delimited paths. ' +          'For full control, use a function instead.',          vm        )      }    }    this.value = this.lazy      ? undefined      : this.get()  }  /**   * Evaluate the getter, and re-collect dependencies.   */  get () {    pushTarget(this)    let value    const vm = this.vm    try {      value = this.getter.call(vm, vm)    } catch (e) {      if (this.user) {        handleError(e, vm, `getter for watcher "${this.expression}"`)      } else {        throw e      }    } finally {      // "touch" every property so they are all tracked as      // dependencies for deep watching      if (this.deep) {        traverse(value)      }      popTarget()      this.cleanupDeps()    }    return value  }  /**   * Add a dependency to this directive.   */  addDep (dep: Dep) {    const id = dep.id    if (!this.newDepIds.has(id)) {      this.newDepIds.add(id)      this.newDeps.push(dep)      if (!this.depIds.has(id)) {        dep.addSub(this)      }    }  }  /**   * Clean up for dependency collection.   */  cleanupDeps () {    let i = this.deps.length    while (i--) {      const dep = this.deps[i]      if (!this.newDepIds.has(dep.id)) {        dep.removeSub(this)      }    }    let tmp = this.depIds    this.depIds = this.newDepIds    this.newDepIds = tmp    this.newDepIds.clear()    tmp = this.deps    this.deps = this.newDeps    this.newDeps = tmp    this.newDeps.length = 0  }  /**   * Subscriber interface.   * Will be called when a dependency changes.   */  update () {    /* istanbul ignore else */    if (this.lazy) {      this.dirty = true    } else if (this.sync) {      this.run()    } else {      queueWatcher(this)    }  }  /**   * Scheduler job interface.   * Will be called by the scheduler.   */  run () {    if (this.active) {      const value = this.get()      if (        value !== this.value ||        // Deep watchers and watchers on Object/Arrays should fire even        // when the value is the same, because the value may        // have mutated.        isObject(value) ||        this.deep      ) {        // set new value        const oldValue = this.value        this.value = value        if (this.user) {          try {            this.cb.call(this.vm, value, oldValue)          } catch (e) {            handleError(e, this.vm, `callback for watcher "${this.expression}"`)          }        } else {          this.cb.call(this.vm, value, oldValue)        }      }    }  }  /**   * Evaluate the value of the watcher.   * This only gets called for lazy watchers.   */  evaluate () {    this.value = this.get()    this.dirty = false  }  /**   * Depend on all deps collected by this watcher.   */  depend () {    let i = this.deps.length    while (i--) {      this.deps[i].depend()    }  }  /**   * Remove self from all dependencies' subscriber list.   */  teardown () {    if (this.active) {      // remove self from vm's watcher list      // this is a somewhat expensive operation so we skip it      // if the vm is being destroyed.      if (!this.vm._isBeingDestroyed) {        remove(this.vm._watchers, this)      }      let i = this.deps.length      while (i--) {        this.deps[i].removeSub(this)      }      this.active = false    }  }}

通过全局API:Vue.use和Vue.delete触发对象的新增和删除

Object总结流程

  1. Object通过observer转换成getter/setter的形式去追踪数据变化。

  2. 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。

  3. 当数据发生了变化会触发setter,从而向Dep中的依赖发送通知。

  4. 当Watcher接收到通知后,会向外界发送通知,可能会触发视图的更新,也有可能触发某个回调函数。

Array的响应式实现

我们依然还是要在获取数据的时候收集依赖。在数据变化时通知依赖更新。

在哪里收集依赖
data() {  return {    arr: [1,2,3],    user: {},    isLogintrue  }}

Array型数据还是在getter中收集

let arr = [1,2,3]arr.push(4)Array.prototype.newPush = function(val) {  console.log("arr被修改了")  this.push(val)}arr.newPush(4)
创建拦截器
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */import { def } from '../util/index'const arrayProto = Array.prototype// 创建了一个对象作为拦截器export const arrayMethods = Object.create(arrayProto)const methodsToPatch = [  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse']/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function (method) {  // cache original method  const original = arrayProto[method]  def(arrayMethods, method, function mutator (...args) {    const result = original.apply(this, args)    const ob = this.__ob__    let inserted    switch (method) {      case 'push':      case 'unshift':        inserted = args        break      case 'splice':        inserted = args.slice(2)        break    }    if (inserted) ob.observeArray(inserted)    // notify change    ob.dep.notify()    return result  })})
使用拦截器
constructor (value: any) {    this.value = value    this.dep = new Dep()    this.vmCount = 0    def(value, '__ob__'this)    if (Array.isArray(value)) {      if (hasProto) {        // value.__proto__= arrayMethods        protoAugment(value, arrayMethods)      } else {        copyAugment(value, arrayMethods, arrayKeys)      }      this.observeArray(value)    } else {      this.walk(value)    }  }

如何收集依赖

observeArray (itemsArray<any>) {    for (let i = 0, l = items.length; i < l; i++) {        observe(items[i])    }}
如何通知依赖
methodsToPatch.forEach(function (method{  // cache original method  const original = arrayProto[method]  def(arrayMethodsmethodfunction mutator (...args) {    const result = original.apply(this, args)    const ob = this.__ob__    let inserted    switch (method{      case 'push':      case 'unshift':        inserted = args        break      case 'splice':        inserted = args.slice(2)        break    }    if (inserted) ob.observeArray(inserted)    // notify change    ob.dep.notify()    return result  })})
深度监听

在Vue中,不管是Object还是Array实现的响应式数据,都是深度监听,也就是不但要监听自身数据的变化,还要监听数据中所有的子数据的变化。

let arr = [{  name'test',  age,  info: {    address'xxx'  }}]

数据元素的新增

我们向数组中新增了一个元素,我们需要把新增的元素转换为响应式数据。

Vue2版本的响应式不足

日常开发中,我们可能这样写

let arr = [1,2,3]arr[0] = 5; // 通过数组下标去修改数组中的数据arr.length = 0; // 通过修改数组长度去清空数组

但这两种方法都无法监听到,也不会触发响应式更新。因此才有后续Vue3的Proxy响应式处理方案。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Vue v2.6.11 响应式实现源码解析

评论 抢沙发

7 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮