一、前置知識
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)
-
value: 設置屬性的值(僅適用于數據屬性)。默認為
undefined
。不能與get
或set
同時使用。 -
writable: 值是否可寫。默認為
false
。如果為true
,則屬性的值可以被賦值運算符改變。 -
enumerable: 屬性是否可以被枚舉。默認為
false
。如果為true
,則屬性可以在for...in
循環和Object.keys
方法中被枚舉。 -
configurable: 屬性是否可以被刪除或修改特性。默認為
false
。如果為true
,則可以使用Object.defineProperty
修改該屬性的特性,或者使用delete
刪除該屬性。 -
get: 屬性的 getter 函數,當訪問該屬性時調用。不能與
value
或writable
同時使用。 -
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數據發送變化時為什么視圖會同步更新呢?
這時候這張圖就更高理解了。
-
響應式對象(響應式原理):
- 當你將一個普通的 JavaScript 對象傳給 Vue 實例的?
data
?選項時,Vue 會遍歷這個對象的屬性,并使用?Object.defineProperty
?或?Proxy
?將每個屬性轉換為 getter 和 setter、引用data數據實際上是訪問數據對象屬性的 get 方法、修改數據實際上是在調用數據對象屬性的set方法。 - 這樣一來,當屬性被訪問或修改時,Vue 能夠捕捉到這些操作,并觸發相應的依賴更新。
- 當你將一個普通的 JavaScript 對象傳給 Vue 實例的?
-
依賴追蹤與 Watcher:
- Vue 內部維護了一個依賴收集的系統。每個響應式屬性都會有一個關聯的 Watcher 對象。
- 當屬性被訪問時,Watcher 會將當前組件實例與這個屬性建立關聯(依賴追蹤),確保在屬性變化時能夠通知相關的 Watcher 執行更新操作。
-
虛擬 DOM 及更新優化(虛擬dom和diff算法):
- Vue 使用虛擬 DOM 來提高渲染效率。當數據發生變化時,Vue 會生成新的虛擬 DOM,并通過比對算法找出變化的部分,然后更新到實際 DOM 中,而不是直接操作實際 DOM。
-
異步更新隊列:
- Vue 在更新數據時是異步執行的,多個數據的變化會被合并成一個更新操作,以提高性能并避免不必要的計算和 DOM 操作。
-
渲染 Watcher:
- 每個組件實例都有一個渲染 Watcher,它是 Vue 在實例化過程中自動創建的。這個 Watcher 負責將組件的?
render
?函數渲染成虛擬 DOM,并在數據變化時重新渲染組件。
- 每個組件實例都有一個渲染 Watcher,它是 Vue 在實例化過程中自動創建的。這個 Watcher 負責將組件的?
(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、總結響應式原理