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

一、前置知识

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 值为 valueipt.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.valueconsole.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 uidvm._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// checkvm._isVue = true// avoid instances from being observedvm.__v_skip = true// effect scopevm._scope = new EffectScope(true /* detached */)// #13134 edge case where a child component is manually created during the// render of a parent componentvm._scope.parent = undefinedvm._scope._vm = true// merge optionsif (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 selfvm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate', undefined, false /* setContext */)initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(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.$optionsif (opts.props) initProps(vm, opts.props)// Composition APIinitSetup(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 instanceconst 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(); // 创建依赖管理对象 Depthis.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/setterconst 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 和 setterconst 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 和 setterObject.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 类型但没有 setterreturn;} 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); // 输出: 42objOne.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) {  // 如果存在正在依赖此属性的 Watcherif (__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.mzph.cn/diannao/43601.shtml

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

相关文章

绝区捌--将GPT幻觉的发生率从20%以上降低到2%以下

总结&#xff1a;我们没有使用微调&#xff0c;而是结合使用提示链和预处理/后处理来将幻觉发生率降低一个数量级&#xff0c;但这确实需要对 OpenAI 进行 3-4 倍的调用。还有很大的改进空间&#xff01; 使用 GPT 等大型语言模型面临的最大挑战之一是它们倾向于捏造信息。 这…

使用Python绘制QQ图并分析数据

使用Python绘制QQ图并分析数据 在这篇博客中&#xff0c;我们将探讨如何使用Python中的pandas库和matplotlib库来绘制QQ图&#xff08;Quantile-Quantile Plot&#xff09;&#xff0c;并分析数据文件中的内容。QQ图是一种常用的统计图表&#xff0c;用于检查一组数据是否服从…

C# 下sendmessage和postmessage的区别详解与示例

文章目录 1、SendMessage2、PostMessage3、两者的区别&#xff1a; 总结 在C#中&#xff0c;SendMessage和PostMessage是两个用于Windows编程的API&#xff0c;它们用于向窗口发送消息。这两个方法都位于System.Windows.Forms命名空间中&#xff0c;通常用于自动化Windows应用程…

科普文:分布式系统的架构设计模式

一、分布式架构基本概念 分布式架构是一种计算机系统设计方法&#xff0c;它将一个复杂的系统划分为多个自治的组件或节点&#xff0c;并通过网络进行通信和协作。每个组件或节点在功能上可以相互独立&#xff0c;但又能够通过消息传递或共享数据来实现协同工作。分布式架构主要…

值传递与引用传递:深入理解Java中的变量赋值和参数传递机制

在Java中&#xff0c;理解值传递&#xff08;值拷贝&#xff09;与引用传递&#xff08;地址拷贝&#xff09;之间的区别对于正确处理数据结构和对象至关重要。本文将通过示例代码深入探讨这两种机制&#xff0c;并解释它们如何影响程序的行为。 值传递&#xff08;值拷贝&…

虚拟机因断电进入./#状态解决办法

现象&#xff1a; 解决&#xff1a;先查看错误日志&#xff1a;journalctl -p err -b查看自己虚拟机中标黄部分的名字 之后运行&#xff1a;xfs_repair -v -L /dev/sda #这里sda用你自己标黄的 最后重启 reboot 即可。

在Linux上运行macOS:深度解析OSX-KVM项目

在Linux上运行macOS&#xff1a;深度解析OSX-KVM项目 在现代开发和测试环境中&#xff0c;能够在不同操作系统之间无缝切换是至关重要的。对于开发者而言&#xff0c;如何在Linux系统上运行macOS一直是一个挑战。然而&#xff0c;OSX-KVM项目为我们提供了一种高效的解决方案&a…

R包:ggsci期刊配色

介绍 不同期刊配色大多数时候不一样&#xff0c;为了更好符合期刊图片颜色的配色&#xff0c;有人开发了ggsci这个R包。它提供以下函数&#xff1a; scale_color_palname() scale_fill_palname() 对应不同期刊的color和fill函数。 导入数据R包 library("ggsci")…

2024年全面导入APS系统:提升工厂生产效率的策略

在快速变化的市场环境中&#xff0c;急单、插单、订单设计变更、订单交期变更、订单取消、供应链移动等问题已经是制造业时时刻刻都在面对的问题&#xff0c;在订单量下降的市场环境下&#xff0c;企业本身的业务工作反而越来越忙碌。在此背景下&#xff0c;当今制造业企业亟需…

【pytorch24】Visdom可视化

TensorboardX pytorch有一个工具借鉴了tensorboard pip install tensorboardX 有查看变量的数值、监听曲线等功能 如何使用 新建SummaryWriter()实例 要把监听的数据&#xff0c;比如说要监听dummy_s1[0]&#xff08;y 坐标&#xff09;存放到data/scalar1中&#xff0c;…

【React】React18 Hooks 之 useContext

目录 useContext1、Provider和 useContext2、Provider 和Consumer3、Provider 嵌套4、React.createContext提供的Provider和class的contextType属性5、读、写Context&#xff08;1&#xff09;父组件修改Context&#xff08;2&#xff09;子组件修改Context 好书推荐 useContex…

NPDP有什么价值?究竟值不值得去考?

NPDP其实就是产品经理国际资格认证&#xff0c;是美国产品开发管理协会发起的&#xff0c;集理论、方法和实践一体&#xff0c;在新产品开发方面有一个很全面的知识体系。是国际公认的新产品开发专业认证&#xff0c;具有权威性。 NPDP能够很好地帮你在做新产品的道路上少走弯…

【已解决】腾讯云安装了redis,但是本地访问不到,连接不上

汇总了我踩过的所有问题。 查看配置文件redis.conf 1、把bind 127.0.0.1给注释掉&#xff08;前面加个#就是&#xff09;或者改成bind 0.0.0.0&#xff0c;因为刚下载时它是默认只让本地访问。&#xff08;linux查找文档里的内容可以输入/后面加需要匹配的内容&#xff0c;然后…

clickhouse-jdbc-bridge rce

clickhouse-jdbc-bridge 是什么 JDBC bridge for ClickHouse. It acts as a stateless proxy passing queries from ClickHouse to external datasources. With this extension, you can run distributed query on ClickHouse across multiple datasources in real time, whic…

Java基础-组件及事件处理(上)

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 Swing 概述 MVC 架构 Swing 特点 控件 SWING UI 元素 JFrame SWING 容器 说明 常用方法 示例&a…

服务器信息获取工具

功能介绍 SSH连接到远程服务器&#xff1a; 用户可以输入目标服务器的IP地址、用户名、密码以及SSH端口&#xff08;默认22&#xff09;。 工具会尝试连接到远程服务器&#xff0c;并在连接失败时显示错误信息。 运行命令并返回输出&#xff1a; 工具可以在远程服务器上运…

python (必看)10个提升接口自动化编写效率的脚本!

亲爱的开发者们&#xff0c;&#x1f44b; 在快速迭代的软件开发周期中&#xff0c;接口自动化测试扮演着至关重要的角色。今天&#xff0c;我们将分享10个实用的Python小脚本&#xff0c;它们能够显著提升你编写接口自动化测试的效率。无论是初学者还是资深工程师&#xff0c;…

算法体系-26 第二十六节:第26节:单调栈结构 (5节)

一 单调栈知识讲解 1.1描述 一个数组里面想的到每个位置与他最近的左边和右边比他小的最近的信息 1.2 分析 通过单调栈的特点&#xff0c;for遍历数组中的每个数&#xff0c;当前数来的时候对比单调栈中的数进行每个数的左右判断完满足条件的进行更新到当前i种的 int[][] re…

采用3种稀疏降噪模型对心电信号进行降噪(Matlab R2021B)

心电信号采集自病人体表&#xff0c;是一种无创性的检测手段。因此&#xff0c;心电信号采集过程中&#xff0c;本身也已经包含了机体内部其他生命活动带来的噪声。同时&#xff0c;由于采集设备和环境中存在电流的变化&#xff0c;产生电磁发射等物理现象&#xff0c;会对心电…

学习测试7-ADB的使用

ADB是什么&#xff1f; ADB&#xff0c;即 Android Debug Bridge&#xff08;安卓调试桥&#xff09; 是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具&#xff0c;它可为各种设备操作提供便利&#xff0c;如安装和调试应用&#xff0c;并提供对 Unix shell&…