DEMO地址,先下載DEMO,打開index.html體驗一下吧html
先上一張靈魂圖,可能看完圖就要跑了。。。爲了講清楚畫的比較亂vue
從上圖中能夠大體看出,分爲兩條線🧵node
new Vue -> reactive[new Proxy -> return proxy] -> baseHandler(set, get) -> effect文件中的依賴收集(track,trigger)-> trigger觸發任務調度 -> 完了react
先保留這兩個問題,繼續看第二條線git
從根路徑app開始解析子節點 -> 解析出當前node,node裏面的指令{{name}} -> 實例化effect(傳入回調函數:回調裏面去proxy上面獲取name) -> 調用effectgithub
調用effect主要作兩件事:api
import { mutableHandlers } from './baseHandler'
// 這個map存儲key: target, value:proxy
// 做用:
// 1.避免重複proxy
const rawToReactive = new WeakMap()
// 這個map存儲key:proxy, value:target
// 做用:
// 1.避免proxy對象再次被proxy
const reactiveToRaw = new WeakMap()
export const targetMap = new WeakMap()
export function reactive(target){
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers
)
}
// 建立響應式對象
function createReactiveObject(target, toProxy, toRaw, handlers){
// 若是當前對象已被proxy,那麼直接返回
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// 檢測被proxy的對象,即這裏的target,自身是不是個proxy,若是是的話,直接返回
if (toRaw.has(target)) {
return target
}
// 當前的target既沒有被proxy,也不是個proxy對象,那麼對它proxy
observed = new Proxy(target, handlers)
// 實例化以後把它維護到兩個map
toProxy.set(target, observed)
toRaw.set(observed, target)
// 把當前的target維護到targetMap,targetMap的做用 -> 【繼續往下看,先無論】
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
// toRaw函數,傳入proxy對象,獲取target
//
export function toRaw(observed) {
return reactiveToRaw.get(observed) || observed
}
複製代碼
能夠看出reactive的做用主要是:bash
ok,這兩個疑問🤔️先保留,繼續看baseHandler的源碼app
import { toRaw } from './reactive'
import { track, trigger } from './effect'
// 爲了便於理解,這裏只作了get和set的proxy
// 其餘的代碼都是通常的代理,不講,講一下track和trigger
// 從vue 2.0的源碼其實能夠知道:
// 1.get的時候會作依賴收集:即這裏的track
// 2.set的時候會作更新廣播:即這裏的trigger
export const mutableHandlers = {
get(target, key, receiver){
const res = Reflect.get(target, key, receiver)
track(target, 'get', key)
return res
},
set(target, key, value, receiver){
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// 這裏檢測key是不是target的自有屬性
const hadKey = target.hasOwnProperty(key)
// 在reactive維護了一個reactiveToRaw隊列,存儲了[proxy]:[target]這樣的隊列,這裏檢測下是不是使用createReactiveObject新建的proxy
if (target === toRaw(receiver)) {
// 判斷是否值改變,才觸發更新
if (hadKey && value !== oldValue) {
trigger(target, 'set', key)
}
}
return result
}
}
複製代碼
重點來了,effect就是vue3裏面用於依賴管理的,主要是管理三個東西:dom
import { targetMap } from './reactive'
const activeReactiveEffectStack = []
// 下面這兩個api是初始化effect,就不過於糾結了
export function effect(fn, options){
const effect = createReactiveEffect(fn, options)
return effect
}
function createReactiveEffect(fn, options){
const effect = function(){
if (!activeReactiveEffectStack.includes(effect)) {
try {
activeReactiveEffectStack.push(effect)
return fn()
} finally {
activeReactiveEffectStack.pop()
}
}
}
effect.scheduler = options.scheduler
return effect
}
// 做用:
// 1.收集依賴
export function track(target, type, key){
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
// proxy初始化的時候,這個depsMap爲new Map
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
// 若是是第一次這個dep是沒有的,由於depsMap是new Map
let dep = depsMap.get(key)
if (dep === void 0) {
// 這裏把依賴放進去。依賴是個Set
depsMap.set(key, (dep = new Set()))
}
// 這裏的effect就是依賴。
// 依賴是啥?能夠理解爲依賴保存了data <-> dom的關係
dep.add(effect)
// effect.deps.push(dep)
}
// 做用:
// 1.觸發了數據更新,這時候得更新dom了
export function trigger(target, type, key){
const depsMap = targetMap.get(target)
const effects = new Set()
const run = effect => {
scheduleRun(effect, target, type, key)
}
// 解析出依賴中要更新的effect
addRunners(effects, depsMap.get(key))
// 任務調度執行
effects.forEach(run)
}
function addRunners(effects, effectsToAdd){
effectsToAdd.forEach(effect => {
effects.add(effect)
})
}
// 任務調度,就理解爲data更新以後,調用effect.scheduler去更新dom
function scheduleRun(effect, target, type, key){
if (effect.scheduler !== void 0) {
effect.scheduler(effect)
} else {
effect()
}
}
複製代碼
能夠看出:1.targetMap是用來存儲依賴的
繼續看下scheduler的queueJob任務調度
import { callWithErrorHandling } from './errorHandling'
const queue = []
const p = Promise.resolve()
let isFlushing = false
export function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
if (!isFlushing) {
nextTick(flushJobs)
}
}
}
export function nextTick(fn) {
return fn ? p.then(fn) : p
}
function flushJobs(seenJobs) {
isFlushing = true
let job
while ((job = queue.shift())) {
job()
}
isFlushing = false
}
複製代碼
queueJob主要是利用了Promise來進行一個微任務隊列的依賴更新:其實執行effect實例函數
到這裏第一線的內容就完了,遺留一個問題:
這說明啥?
說明在調用了effect -> 把effect push進activeReactiveEffectStack 以後,須要調用proxy[name]來觸發get
明白了這一點以後,繼續來看第二條線,第二條線的compile內容屬於自研,跟源碼差距比較大
import { effect } from './effect'
import { queueJob } from './scheduler'
export function compile(el, vm){
let fragment = document.createDocumentFragment();
let node;
while(node = el.firstChild){
compileNode(vm, node)
fragment.append(node)
}
return fragment
}
const reg = /\{\{(.*)\}\}/;
function compileNode(vm, node){
let { nodeType, nodeValue, nodeName } = node;
node.update = (type, bindName) => {
return effect(() => {
node[type] = vm[bindName]
}, { scheduler: queueJob })
}
let bindName;
switch(nodeType){
case 1:
if(nodeName == 'INPUT'){
let { attributes } = node;
for(let attr of attributes){
if(attr.name === 'v-model'){
bindName = attr.value;
}
}
if(bindName){
node.addEventListener('input', e => {
vm[bindName] = e.target.value;
})
}
node.update('value', bindName)()
}
break;
case 3:
let isModal = reg.test(nodeValue)
if(isModal){
bindName = RegExp.$1 && RegExp.$1.trim();
node.update('nodeValue', bindName)()
}
break;
}
}
複製代碼
重點在於:當解析出node和bingName以後,其實這時候能夠調用proxy[name]來直接獲取值了
-> 但是這裏建立了個effect來創建key和當前node之間的關係
由於時間比較緊,因此寫得很倉促,本身也感受文章寫的比較亂,單獨看文章的話可能會看不懂。須要先把DEMO過一遍,而後帶着問題來看這篇文章