Vue核心 — Vue2响应式原理和核心源码解析(核心中的核心)

news2024/9/26 22:46:51

一、前置知识

1、Vue 核心概念

Vue 是什么?

        Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。

Vue 核心特点是什么?

响应式数据绑定:

        Vue.js 实现了数据的双向绑定,即当数据发生变化时,视图会自动更新,反之亦然。这使得开发者可以更轻松地管理和更新数据,同时保持视图与数据的同步。

组件化开发:

       Vue.js  将页面拆分为多个独立的组件,每个组件负责自己的视图和逻辑。这种组件化的开发方式使得代码更加模块化可维护性更高,也有利于团队协作。

虚拟 Dom:

        Vue.js 使用虚拟 DOM 技术,将页面的 DOM 结构表示为 JavaScript 对象,通过比较新旧虚拟 DOM 树的差异,最小化 DOM 操作,从而提高页面的性能和效率。

指令:

        Vue.js 提供了丰富的指令、用于在模板中添加特定的功能或行为。指令使得开发者可以更便捷地操作 DOM 元素,实现动态数据绑定、条件渲染等功能。

插件系统:

        Vue.js 提供了灵活的插件系统,允许开发者根据项目需求扩展 Vue 的功能

二、Vue2 的响应式原理

1、理解什么是响应式数据?

什么不是响应式数据?

数据和视图(dom属性值)相互独立、互相并不影响、即数据发生变化视图并不发生变化、视图发送变化数据并不发生变化、想要实现双向绑定、得在一方的值发生变化时去修改另一方的值。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>测试响应式</title>
</head>
<body>
  <input type="text">
  <button>value++</button>
  <script>
    // 设置 value 初始值
    let value = 1
    // 读取输入框 dom 元素
    const ipt = document.querySelector('input')
    // 按钮 dom 元素
    const btn = document.querySelector('button')

    // 设置 ipt 的 value 值为 value
    ipt.value = value

    // 为输入框绑定输入事件
    ipt.addEventListener('input', (event) => {
      console.log('iptValue: ', event.target.value);
      console.log('value ', value);;
      // 重新为 value 赋值
      // value = event.target.value
    })
    
    // 为按钮绑定点击事件
    btn.addEventListener('click', (event) => {
      value++
      // 重新为 ipt.value 赋值
      // ipt.value = ipt.value
      console.log('iptValue: ',ipt.value)
      console.log('value ', value);
    })
  </script>
</body>

</html>

什么是响应式数据?

数据发生变化、视图绑定该数据会自动更新、反之亦然。详细说明、例如页面上的表单元素通过v-model: value 绑定 data 方法里返回的对象属性值 value、当 value 值发送变化时视图会自动更新、在页面上修改表单元素时(修改value值)、data 方法里返回的对象属性值 value 也会同步变化。

为什么数据发送变化视图也会更新呢、底层源码是如何实现的?

如上图所示、这就是响应式对象发送变化时视图发送变化的机制。让我们一步一步的来剖析。

2、Vue 初始化做了什么?(重点关注状态初始化)


new Vue({
  render: h => h(App)
}).$mount('#app')

        上面代码大家都很熟悉、简单来讲就是通过new Vue({render: h => h(App)}) 创建一个 Vue 实例、render: h => h(App) 就是一个指令,告诉 Vue 使用 createElement 函数来创建 App 组件的虚拟 DOM 对象,然后将实例通过其内置的 $mount 方法挂载到 id 为 app 根节点上。

        从 new 操作符、咱们可以看出 Vue 其实就是一个构造函数、没啥特别的、传入的参数就是一个对象、源码中我们叫做 options(选项)。

让我们来看看构造函数做了什么?

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
import type { GlobalAPI } from 'types/global-api'

function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue)

export default Vue as unknown as GlobalAPI

很明显、核心在于调用了方法 this._init(options) 这里开始进行 Vue 实例的初始化工作

options 就是传入的虚拟 dom 对象。

那么 _init() 方法是从哪里来的呢? _init() 方法内部干了什么?

核心关注 initMixin(Vue)

_init()这个方法就是 initMixin(Vue) 在 vue 实例的原型上挂载的。

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    // #13134 edge case where a child component is manually created during the
    // render of a parent component
    vm._scope.parent = undefined
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

让我们来关注几个核心点

(1) Vue 组件实例的选项 (vm.$options)初始化。

如果是内部组件:通过 initInternalComponent 函数 优化初始化

如果是非内部组件:通过 mergeOptions 函数 合并传入的 options 对象和 当前 Vue 实例成为最终的 $options 对象

if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }

(2) 生命周期、事件、渲染初始化:

// 生命周期初始化
initLifecycle(vm)
// 事件初始化
initEvents(vm)
// 渲染初始化
initRender(vm)

(3) 状态初始化(核心重点):

initState(vm)
initState 干了什么?
export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)

  // Composition API
  initSetup(vm)

  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    const ob = observe((vm._data = {}))
    ob && ob.vmCount++
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

可以看到依次初始化了

props > setup > methods > data > computed > watch

想要弄清楚 Vue2 的响应式原理重点得关注初始化 data 上

if (opts.data) {
  initData(vm)
} else {
  const ob = observe((vm._data = {}))
  ob && ob.vmCount++
}

如果组件定义了 data,则直接调用 initData(vm) 来初始化;如果没有定义 data,则创建一个空对象,并将其转换为响应式对象

function initData(vm: Component) {
  let data: any = vm.$options.data;
  // 获取组件配置中的 data 选项
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
  // 如果 data 是一个函数,则通过 getData 函数获取其返回值,否则直接使用 data 或者默认为空对象 {}

  if (!isPlainObject(data)) {
    // 如果 data 不是一个纯对象,给出开发者警告(开发环境下)
    data = {};
    __DEV__ &&
      warn(
        'data functions should return an object:\n' +
          'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
  }

  // proxy data on instance
  const keys = Object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;
  let i = keys.length;
  // 遍历 data 的键,将每个键设置为 vm 实例的代理属性(如果不是保留键)

  while (i--) {
    const key = keys[i];
    if (__DEV__) {
      // 在开发环境下,检查是否有同名方法已经定义为数据属性
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`, vm);
      }
    }
    if (props && hasOwn(props, key)) {
      // 如果 data 的属性已经被声明为 prop,给出警告
      __DEV__ &&
        warn(
          `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
          vm
        );
    } else if (!isReserved(key)) {
      // 如果属性不是保留属性,则将其代理到 vm 实例上
      proxy(vm, `_data`, key);
    }
  }

  // observe data
  // 观察数据,确保数据变化时可以通知相关的依赖
  const ob = observe(data);
  if (ob) {
    // 如果成功创建了观察对象,则增加其引用计数
    ob.vmCount++;
  }

可以看到无论如何都会执行 observe(data) 、就是其让data数据变成响应式数据的。

observe 干了什么?
/**
 * 尝试为一个值创建观察者实例,
 * 如果成功观察,则返回新的观察者实例,
 * 如果值已经有观察者实例,则返回现有的观察者。
 *
 * @param value 需要观察的值。
 * @param shallow 是否执行浅层观察。
 * @param ssrMockReactivity 是否在服务端渲染时模拟响应性。
 * @returns 如果成功观察到,则返回 Observer 实例,否则返回 void。
 */
export function observe(
  value: any,
  shallow?: boolean,
  ssrMockReactivity?: boolean
): Observer | void {
  // 检查值是否已经有 __ob__ 属性,并且该属性是 Observer 的实例
  if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    return value.__ob__; // 返回现有的观察者实例
  }

  // 创建新的观察者实例的条件判断
  if (
    shouldObserve && // 全局标志,确定是否进行观察
    (ssrMockReactivity || !isServerRendering()) && // 确保在非服务端渲染时或者模拟响应性时启用响应性
    (isArray(value) || isPlainObject(value)) && // 值必须是数组或普通对象
    Object.isExtensible(value) && // 值必须是可扩展的(即未被密封)
    !value.__v_skip /* ReactiveFlags.SKIP */ && // 值不能被标记为跳过观察
    !isRef(value) && // 值不能是 ref 对象
    !(value instanceof VNode) // 值不能是 Vue 虚拟节点
  ) {
    // 创建一个新的 Observer 实例来观察该值
    return new Observer(value, shallow, ssrMockReactivity);
  }

  // 如果不满足观察条件,则返回 void
}

初始化传进来的 vm._data  满足上述条件、所以会执行

return new Observer(value, shallow, ssrMockReactivity);

Observer 里干了什么?

核心: 是将传入的 data 对象(或数组)转换为响应式对象(或响应式数组)

/**
 * Observer 类被附加到每个被观察的对象上。
 * 一旦附加,Observer 将目标对象的属性键转换为 getter 和 setter,
 * 用于收集依赖和分发更新。
 */
export class Observer {
  dep: Dep; // 依赖管理对象
  vmCount: number; // 使用该对象作为根 $data 的 vm 数量

  constructor(public value: any, public shallow = false, public mock = false) {
    // 初始化 Observer 实例
    this.dep = mock ? mockDep : new Dep(); // 创建依赖管理对象 Dep
    this.vmCount = 0; // 记录有多少个 vm 实例使用该对象作为根 $data

    // 在值 value 上定义 __ob__ 属性,指向当前 Observer 实例
    def(value, '__ob__', this);

    // 如果值是数组
    if (isArray(value)) {
      if (!mock) {
        if (hasProto) {
          /* eslint-disable no-proto */
          // 如果支持原型链修改,则将数组的原型指向 arrayMethods
          ;(value as any).__proto__ = arrayMethods;
          /* eslint-enable no-proto */
        } else {
          // 否则,逐个定义数组的方法
          for (let i = 0, l = arrayKeys.length; i < l; i++) {
            const key = arrayKeys[i];
            def(value, key, arrayMethods[key]);
          }
        }
      }

      // 如果不是浅层观察,则递归观察数组中的每一项
      if (!shallow) {
        this.observeArray(value);
      }
    } else { // 如果值是普通对象
      // 遍历对象的所有属性,转换为 getter/setter
      const keys = Object.keys(value);
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock);
      }
    }
  }

  /**
   * 观察数组中的每一项。
   */
  observeArray(value: any[]) {
    for (let i = 0, l = value.length; i < l; i++) {
      observe(value[i], false, this.mock);
    }
  }
}

def(value, key, arrayMethods[key]):将数组数据变成响应式数据核心方法。

defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock):将对象数据转化成响应式数据核心方法。

defineReactive 干了什么?

        将一个对象的属性变成响应式,即当属性被访问或修改时能触发相应的依赖收集和通知更新操作。核心方法就是 Object.defineProperty()

/**
 * 在对象上定义一个响应式属性。
 * @param obj 要定义属性的对象。
 * @param key 要定义的属性的名称。
 * @param val 可选,属性的初始值。
 * @param customSetter 可选,自定义的 setter 函数。
 * @param shallow 可选,是否进行浅层观察。
 * @param mock 可选,是否模拟对象。
 * @param observeEvenIfShallow 默认为 false,即使是浅层观察也进行观察。
 */
export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean,
  observeEvenIfShallow = false
) {
  const dep = new Dep(); // 创建一个依赖对象

  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return; // 如果属性已存在且不可配置,直接返回
  }

  // 处理预定义的 getter 和 setter
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && (val === NO_INITIAL_VALUE || arguments.length === 2)) {
    val = obj[key]; // 获取属性的初始值
  }

  // 观察子对象,决定是否进行深层观察
  let childOb = shallow ? val && val.__ob__ : observe(val, false, mock);
  
  // 定义属性的 getter 和 setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val; // 获取属性值
      if (Dep.target) {
        if (__DEV__) {
          dep.depend({
            target: obj,
            type: TrackOpTypes.GET,
            key
          }); // 进行依赖收集
        } else {
          dep.depend();
        }
        if (childOb) {
          childOb.dep.depend(); // 子对象依赖收集
          if (isArray(value)) {
            dependArray(value); // 数组依赖收集
          }
        }
      }
      return isRef(value) && !shallow ? value.value : value; // 如果是 ref 类型且不是浅层观察,返回其值
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val; // 获取属性当前值
      if (!hasChanged(value, newVal)) {
        return; // 新旧值相同则直接返回
      }
      if (__DEV__ && customSetter) {
        customSetter(); // 在开发环境下调用自定义的 setter 函数
      }
      if (setter) {
        setter.call(obj, newVal); // 使用属性的原始 setter 函数
      } else if (getter) {
        // 如果属性是 accessor 类型但没有 setter
        return;
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        value.value = newVal; // 处理 ref 类型属性的赋值
        return;
      } else {
        val = newVal; // 更新属性值
      }
      childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock); // 更新子对象的观察状态
      if (__DEV__) {
        dep.notify({
          type: TriggerOpTypes.SET,
          target: obj,
          key,
          newValue: newVal,
          oldValue: value
        }); // 发送属性变更通知
      } else {
        dep.notify();
      }
    }
  });

  return dep; // 返回依赖对象
}
Object.defineProperty() 干了什么
首先得知道 Object.defineProperty() 是什么?
Obeject.defineProperty() 概念

        Obeject.defineProperty 是一个用于定义或修改对象属性的方法。它允许你精确地控制属性的特性,包括可写性、可枚举性、可配置性以及访问器方法(getter 和 setter)。

使用语法
Object.defineProperty(obj, 'age',descriptor);
  • obj: 要在其上定义属性的对象。
  • prop: 要定义或修改的属性的名称或 Symbol。
  • descriptor: 描述符对象,定义了要定义或修改的属性的特性。
描述符对象(descriptor)
  1. value: 设置属性的值(仅适用于数据属性)。默认为 undefined不能与 getset 同时使用。

  2. writable: 值是否可写。默认为 false。如果为 true,则属性的值可以被赋值运算符改变。

  3. enumerable: 属性是否可以被枚举。默认为 false。如果为 true,则属性可以在 for...in 循环和 Object.keys 方法中被枚举。

  4. configurable: 属性是否可以被删除或修改特性。默认为 false。如果为 true,则可以使用 Object.defineProperty 修改该属性的特性,或者使用 delete 删除该属性。

  5. get: 属性的 getter 函数,当访问该属性时调用。不能与 value writable 同时使用。

  6. set: 属性的 setter 函数,当属性被赋值时调用。不能与 value writable 同时使用。

注意事项
  • 在非严格模式下,如果尝试写入一个不可写属性,赋值操作将会静默失败。
  • 不能同时在同一个属性描述符对象中使用 value 和 get 或 set
  • 一旦将属性设置为不可配置 (configurable: false),则不能再修改其特性,也不能删除该属性。
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>测试代码执行顺序</title>
</head>
<body>
  <script>
    // 定义数据属性
    let objOne = {};
    Object.defineProperty(objOne, 'myProperty', {
      value: 42,
      writable: true,
      enumerable: true,
      configurable: true
    });
    console.log(objOne.myProperty); // 输出: 42
    objOne.myProperty = 50;
    console.log(objOne.myProperty); // 输出: 50

    // 定义访问器属性 
    let objTwo = {
      _myProperty: 0
    };
    Object.defineProperty(objTwo, 'myProperty', {
      get: function() {
        console.log('获取值');
        return this._myProperty;
      },
      set: function(value) {
        if(value ===  this._myProperty) {
          return
        }
        console.log('设置值');
        this._myProperty = value;
      },
      enumerable: true,
      configurable: true
    });
    // 设置属性值 myProperty 就是在调用 set 方法
    objTwo.myProperty = 10;
    // 访问属性值 myProperty 就是在调用 get 方法
    console.log(objTwo.myProperty); // 输出: 10
  </script>
</body>
</html>

源码中的 Object.defineProperty()干了什么?

javascript
Object.defineProperty(obj, key, {
  enumerable: true,        // 可枚举
  configurable: true,      // 可配置
  get: function reactiveGetter() {
    const value = getter ? getter.call(obj) : val;  // 获取属性值
    if (Dep.target) {  // 如果存在正在依赖此属性的 Watcher
      if (__DEV__) {
        // 在开发模式下进行依赖收集
        dep.depend({
          target: obj,
          type: TrackOpTypes.GET,
          key
        });
      } else {
        dep.depend();  // 正常情况下进行依赖收集
      }
      if (childOb) {
        childOb.dep.depend();  // 如果存在子响应式对象,也进行依赖收集
        if (isArray(value)) {
          dependArray(value);  // 如果值是数组,还需依赖收集数组的元素
        }
      }
    }
    // 如果值是 Ref 对象且不是浅层响应式,则返回其实际值;否则返回原始值
    return isRef(value) && !shallow ? value.value : value;
  },
  set: function reactiveSetter(newVal) {
    const value = getter ? getter.call(obj) : val;  // 获取当前属性值
    if (!hasChanged(value, newVal)) {
      return;  // 如果新旧值相同,则不进行更新
    }
    if (__DEV__ && customSetter) {
      customSetter();  // 在开发模式下,如果定义了自定义 setter,则调用它
    }
    if (setter) {
      setter.call(obj, newVal);  // 如果定义了 setter 函数,则调用它设置新值
    } else if (getter) {
      // 如果只定义了 getter 函数但未定义 setter,不执行任何操作(适用于只读属性)
      return;
    } else if (!shallow && isRef(value) && !isRef(newVal)) {
      value.value = newVal;  // 如果属性值是 Ref 对象且不是浅层响应式,则设置其值
      return;
    } else {
      val = newVal;  // 否则直接更新属性的值
    }
    // 更新子响应式对象的依赖关系
    childOb = shallow ? newVal && newVal.__ob__ : observe(newVal, false, mock);
    if (__DEV__) {
      // 在开发模式下,通知依赖此属性的 Watcher 进行更新
      dep.notify({
        type: TriggerOpTypes.SET,
        target: obj,
        key,
        newValue: newVal,
        oldValue: value
      });
    } else {
      dep.notify();  // 正常情况下通知依赖进行更新
    }
  }
});
get和set核心就是调用dep的两个方法depend()和notify()

dep核心就是调用Watcher的两个方法get()和update()

// 定义一个 Dep 类,用于管理依赖和通知更新(演示非源码)

export default class Dep {
  constructor() {
    this.subs = []; // subs 数组用来存储 Watcher 对象
  }

  // 添加 Watcher 对象到 subs 数组
  addSub(sub) {
    this.subs.push(sub);
  }

  // 从 subs 数组移除指定的 Watcher 对象
  removeSub(sub) {
    remove(this.subs, sub); // 使用 remove 函数移除
  }

  // 当前 Dep 对象收集依赖
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this); // 将当前 Dep 对象添加到 Watcher 的依赖列表中
    }
  }

  // 通知所有依赖于该 Dep 对象的 Watcher 执行更新操作
  notify() {
    // 遍历 subs 数组,调用每个 Watcher 的 update 方法
    const subs = this.subs.slice(); // 使用 slice() 创建副本,避免在遍历过程中被修改
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update(); // 调用 Watcher 的 update 方法
    }
  }
}

Dep.target = null; // 静态属性,用来存储当前正在执行的 Watcher 对象
const targetStack = []; // Watcher 栈,用于处理嵌套依赖

// 将指定的 Watcher 对象推入 Watcher 栈中
export function pushTarget(target) {
  if (Dep.target) targetStack.push(Dep.target);
  Dep.target = target; // 将当前 Watcher 对象赋值给 Dep.target
}

// 从 Watcher 栈中弹出最后一个 Watcher 对象
export function popTarget() {
  Dep.target = targetStack.pop(); // 恢复 Watcher 栈的上一个 Watcher 对象
}
export default class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm; // Vue 实例
    this.getter = expOrFn; // 数据获取函数
    this.cb = cb; // 回调函数,数据变化时触发
    this.value = this.get(); // 初始化时执行 getter 函数,获取当前数据的值
  }

  // 更新 Watcher 的回调函数
  update() {
    const oldValue = this.value;
    this.value = this.get(); // 重新获取数据的值
    this.cb.call(this.vm, this.value, oldValue); // 执行回调函数,通知数据变化
  }

  // 评估 getter 函数,建立依赖关系
  get() {
    pushTarget(this); // 将当前 Watcher 对象推入 Watcher 栈中
    const vm = this.vm;
    let value;
    try {
      value = this.getter.call(vm, vm); // 执行 getter 函数,获取当前数据的值
    } catch (e) {
      throw e;
    } finally {
      popTarget(); // 从 Watcher 栈中弹出最后一个 Watcher 对象
    }
    return value;
  }
}
get方法核心 - 收集依赖 dep.depend()
  • 当访问(get)响应式对象的属性时,Vue.js 会收集当前正在执行的 Watcher 对象作为依赖。这个过程是通过 dep.depend() 实现的,其中 dep 是依赖对象(Dep 实例)。
  • Watcher 对象可以理解为观察者,它负责响应式数据与视图之间的绑定关系。当数据变化时,与之相关的 Watcher 将被通知,从而更新视图。
set方法核心 - 通知依赖更新 dep.notify()
  • 当设置(set)响应式对象的属性时,Vue.js 会调用 dep.notify() 来通知所有依赖于该属性的 Watcher 进行更新。
  • 这意味着所有观察此数据的视图组件将会重新渲染以反映数据变化。

总结

模板中绑定data数据发送变化时为什么视图会同步更新呢?

这时候这张图就更高理解了。

  1. 响应式对象(响应式原理)

    • 当你将一个普通的 JavaScript 对象传给 Vue 实例的 data 选项时,Vue 会遍历这个对象的属性,并使用 Object.defineProperty 或 Proxy 将每个属性转换为 getter 和 setter、引用data数据实际上是访问数据对象属性的 get 方法、修改数据实际上是在调用数据对象属性的set方法。
    • 这样一来,当属性被访问或修改时,Vue 能够捕捉到这些操作,并触发相应的依赖更新。
  2. 依赖追踪与 Watcher

    • Vue 内部维护了一个依赖收集的系统。每个响应式属性都会有一个关联的 Watcher 对象。
    • 当属性被访问时,Watcher 会将当前组件实例与这个属性建立关联(依赖追踪),确保在属性变化时能够通知相关的 Watcher 执行更新操作。
  3. 虚拟 DOM 及更新优化(虚拟dom和diff算法)

    • Vue 使用虚拟 DOM 来提高渲染效率。当数据发生变化时,Vue 会生成新的虚拟 DOM,并通过比对算法找出变化的部分,然后更新到实际 DOM 中,而不是直接操作实际 DOM。
  4. 异步更新队列

    • Vue 在更新数据时是异步执行的,多个数据的变化会被合并成一个更新操作,以提高性能并避免不必要的计算和 DOM 操作。
  5. 渲染 Watcher

    • 每个组件实例都有一个渲染 Watcher,它是 Vue 在实例化过程中自动创建的。这个 Watcher 负责将组件的 render 函数渲染成虚拟 DOM,并在数据变化时重新渲染组件。

(4) hook 的调用(从源码上理解 beforeCreate 和 created 调用时机)

callHook(vm, 'beforeCreate', undefined, false /* setContext */)
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

这里也很好理解了生命周期hook 中的 beforeCreate 和 created 的调用时机。

beforeCreate 在状态初始化前、这时状态数据的肯定是不能用的

created 在状态初始化完成后调用。

3、总结响应式原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1914385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot助农农产品销售系统-计算机毕业设计源码16718

摘要 SpringBoot助农农产品销售系统旨在通过利用SpringBoot框架开发一个便捷高效的农产品销售平台。该系统包括用户注册登录、商品浏览、购物车管理、订单生成、支付功能等模块。通过整合支付接口、地图定位、推荐系统等技术&#xff0c;提供给用户更好的购物体验。本文介绍了…

考完软考之后,如何评职称?是否有有效期?

一、软考和职称之间的关系 软考和职称之间的关系可以这样理解&#xff1a;拿到软考证书并不意味着就能获得职称。软考证书是技术等级证书&#xff0c;而职称则是一种资格。如果单位聘用你做工程师&#xff0c;那么你的软考证书就可以发挥作用&#xff0c;相当于获得了职称证。…

私域运营从0到1冷启动

私域社群的冷启动是一个从无到有的过程&#xff0c;需要策略和耐心来吸引并维护用户。以下是一些步骤和策略&#xff0c;可以帮助你的私域社群实现从0到1的冷启动&#xff1a; 1. **明确目标和定位**&#xff1a; - 确定社群的目标用户和他们的需求。 - 明确社群的主题和…

【全面的LangChain入门指南】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

简过网:工程专业最吃香的6个证书,你考了几个了?

工程专业最吃香的6个证书&#xff0c;你考了几个了&#xff1f;我们一起来看看吧&#xff01; 1、二级建造师 报考条件&#xff1a;工程类大专及以上学历/从事相关职业 考试时间&#xff1a;3月报名、6月考试 就业前景&#xff1a;建筑设计院、房产开发公司、施工单位 2、一…

叠纸游戏被“偷跑”的一生

已经数不清叠纸是第几次被偷跑了。 刚刚经历了一次大规模拆包偷跑的叠纸&#xff0c;在7月4日&#xff0c;又遭遇了如出一辙的恶性事件&#xff0c;叠纸旗下的乙女游戏《恋与深空》新男主秦彻再次被偷跑&#xff0c;#秦彻偷跑#、#秦彻建模#等多个话题登上热搜。 同时被偷跑的…

交流调压电路和交流调功电路的区别

交流调压电路和交流调功电路的区别 一、指代不同 1、交来流调压&#xff1a;对单相交流电的电压进行调节的电路。 2、交流调功&#xff1a;是一种以晶闸管&#xff08;电力电子功率器件&#xff09;为基础&#xff0c;以智能数字控制电路为核心的电源功率控制电路。 二、原…

优柏云创网关

iphone手机以及ipad平板电脑寻找ip的步骤 步骤1&#xff1a;在桌面上找到设置 步骤2&#xff1a;之后找到wifi /无线局域网 步骤3&#xff1a;选取已经连接上的无线网络名称旁边的蓝色字母i&#xff08;蓝色i圈出&#xff09;&#xff0c; 步骤4:最后可以看到wifi网络窗口&a…

消息称台积电下周开始试产 2nm 芯片,有望率先用于苹果 iPhone 17

消息称台积电下周开始试产 2nm 芯片&#xff0c;有望率先用于苹果 iPhone 17 &#x1f4a1;&#x1f4f1; 大家好&#xff0c;我是猫头虎&#xff0c;科技自媒体博主 &#x1f431;&#x1f42f;&#xff0c;带你洞察科技世界的每一个细节&#xff01;&#x1f525; 关于猫头…

ARM_Linux驱动开发——字符设备驱动开发(上)

目录 一、Linux驱动开发思维 二、Linux驱动开发分类 三、“ ARM_Linux驱动开发——字符设备驱动开发 ” 字符设备驱动简介 前言 在分享Linux驱动开发之前&#xff0c;我想带大家首先回顾一下裸机驱动开发和Linux驱动开发的区别。 1、运行环境和操作系统&#xff1a; 裸机驱…

淘宝扭蛋机小程序:互联网下的潮玩新增速

近些年&#xff0c;潮玩成为了年轻一代的新宠&#xff0c;吸引了无数的年轻消费者&#xff0c;尤其是扭蛋机行业&#xff01;扭蛋机具有价格低、商品丰富等特点&#xff0c;性价比非常高&#xff0c;受众群体遍布到了各个年龄层。扭蛋机商品具有非常高的观赏性和收藏性&#xf…

C语言学习笔记[23]:循环语句while①

C语言除了顺序结构和选择结构还有循环结构 whilefordo...while while循环 //while 语法结构 while(表达式)循环语句; 表达式的结果为真&#xff0c;则执行循环语句&#xff0c;否则循环停止 例如&#xff1a;打印1~10 #include <stdio.h>int main() {int i 1;whil…

浅析文件如何生成二维码,以及文件二维码的广泛应用

随着智能手机的普及和移动互联网的飞速发展&#xff0c;二维码已经深入到人们生活的各个领域&#xff0c;为人们的生活带来了极大的便利。文件二维码具有信息容量大、编码范围广、容错能力强、译码可靠性高、可引入加密措施、成本低、易制作、持久耐用等优点。因此&#xff0c;…

L2 LangGraph_Components

参考自https://www.deeplearning.ai/short-courses/ai-agents-in-langgraph&#xff0c;以下为代码的实现。 这里用LangGraph把L1的ReAct_Agent实现&#xff0c;可以看出用LangGraph流程化了很多。 LangGraph Components import os from dotenv import load_dotenv, find_do…

uniapp微信小程序分享教程

文章目录 导文当前页面未设置分享&#xff1f;配置在代码中开启分享转发按钮 点击按钮转发怎么写微信小程序转发官网文档地址&#xff1a;uniapp转发官网文档地址&#xff1a; 导文 uniapp 微信小程序 当前页面未设置分享&#xff1f; uniapp 微信小程序分享到好友怎么写&#…

力扣-排序算法

排序算法&#xff0c;一般都可以使用std&#xff1a;&#xff1a;sort&#xff08;&#xff09;来快速排序。 这里介绍一些相关的算法&#xff0c;巩固记忆。 快速排序 跟二分查找有一丢丢像。 首先选择一个基准元素&#xff0c;一般就直接选择第一个。然后两个指针&#xff0c…

绝区玖--人工智能物料清单 (AI BOM)

前言 AI BOM 涵盖了从输入模型的数据到为模型提供支持的基础设施以及将 AI 从概念转化为生产的过程的一切。 但为什么我们需要人工智能物料清单&#xff1f;答案在于当今世界人工智能/Gen AI系统的复杂性和关键性&#xff1a; 透明度和可重复性&#xff1a;AI BOM 提供所有组件…

MES系统在装备制造行业核心应用场景介绍

MES软件在企业中有着广泛的应用场景&#xff0c;主要包括生产计划排程、生产过程监控、质量管理、设备管理、库存管理、数据分析等领域。 通过实时监控生产过程、收集数据、进行分析&#xff0c;MES软件可以帮助企业实现生产过程可视化、透明化&#xff0c;提高生产效率&#…

开源项目有哪些机遇与挑战

目录 1.概述 2.开源项目的发展趋势 2.1. 开源项目的发展现状 2.2. 开源社区的活跃度 2.3. 开源项目在技术创新中的作用 3.参与开源的经验分享 3.1. 选择开源项目 3.2. 理解项目结构和文档 3.3. 贡献代码 3.4. 与开源社区的合作 3.5. 学习和成长 4.开源项目的挑战 …

CSS【详解】文本相关样式(含 font 系列样式,文本颜色 color,三种颜色表示法,文本排版-含最佳实战范例,文本装饰,分散对齐,渐变色文本等)

文本风格 font-style font-style:italic 值描述normal默认值。浏览器显示一个标准的字体样式。italic加载对应字体的斜体字体文件&#xff0c;若找不到斜体字体文件&#xff0c;则进行物理上的倾斜。 标签默认font-style:italicoblique浏览器会显示一个倾斜的字体样式。 文本粗…