你爲何看不懂源碼之Vue 3.0【1】

更新了第二篇,你爲何看不懂源碼之Vue 3.0 面面俱到【2】javascript

先嘮會兒嗑

記得很早以前,有我的說過,看源碼就像武俠小說裏的 學習武林祕籍。會耍刀耍劍那是外力,學習武功祕籍纔是內力,才能和別人在對波或戰前拼氣時更升一籌。
在實際開發中,想象這樣一個場景:html

  • 小A:我這兒遇到一個bug,不知道是否是瀏覽器兼容?也多是網絡問題?會不會是框架問題!
  • 你:(輕蔑的一瞥),這塊兒呀,我以前擼過它的源碼,是XX致使了XX,讓你這塊兒代碼產生了XX的影響。(講完後背着手頭也不回的去接杯咖啡。)

這多裝逼呀!vue

現實總不如意

讀框架的源碼的確能夠裝以上的逼,但......java

每當你看源碼時,就像初中時聽數學課,幾分鐘前興致盎然,低頭撿根筆,再擡頭時已 「物是人非」,源碼彷彿變成質能方程,數學老師彷彿變成沒有鬍子的 愛因斯坦 ??!react

你看到一個個大V發出來的源碼解讀文章,爲何別人能看懂?git

你可能會想:是否是我太笨?是否是源碼太難懂?是否是我不適合這一行......github

別擔憂,99%的人和你同樣。typescript

那些大V只告訴你讀源碼時的過程,卻沒有告訴你讀源碼前的準備。json

而我會從讀源碼的準備階段講起。api

磨刀不誤砍柴工

讀源碼前,你的基礎功必定要紮實。
好比如下兩個方法:

export const isObject = (val: any) =>
  val !== null && typeof val === 'object'
複製代碼
export const isObject = (val: any) =>
  val !== null && Object.prototype.toString.call(val) === '[object Object]'
複製代碼

一樣的函數名,但功能不一樣,若是你瞭解 typeof 而且瞭解原型方法 toString 以及 call 的做用,那你很容易分辨出來這二者的區別和做用。

當你看源碼時,掃一眼函數體,便知道框架做者要作什麼。

你可能會問我,基礎功不行怎麼看源碼?

emmmm 個人建議是別看了。先磨刀吧。由於你當前階段提高的最快方法是學習基礎知識。

預知此事先躬行

小A:我基礎功練好了,能夠看源碼了吧?

我:能夠看,也不能看。

小A:你在逗我?

不是的,能夠看的意思是,咱們先大致掃一邊源碼,這個過程小於 5分鐘。看看源碼做者用了什麼新鮮的api 或老的你沒見過的api。

如今咱們須要把 vue 3.0的源碼clone下來,享受這短暫的五分鐘。

能夠看到,vue 3.0 用了大量的

Reflect\Proxy\Symbol\Map\Set

你得確保這些api你都瞭如指掌,若是你作不到了如指掌,也得知道它們分別是幹什麼的。
在這篇文章裏我不會給你一一列舉它們的功能,我只告訴你讀源碼前的準備。但願你能本身理解,而不是拾人牙慧。

文檔

要拾人牙慧

小A:你上文剛說不要拾人牙慧,這下又要拾人牙慧,你到底要我幹什麼?

我:上文講,學習新方法時,要本身探索,照着官方標準本身理解,而這裏,要進入源碼的閱讀了,咱們能夠站在巨人們的肩膀上,對源碼結構稍微有些瞭解。後面本身閱讀時會更容易些.

小A:我信你了。

本文將以Vue數據綁定的核心實現做爲例子,因此這一步,須要你在社區找找大V們寫好的文章。好比我以爲下面這篇寫的不錯。

Vue3 中的數據偵測

第一步先給文章點個👍,支持下原做者。看文章時,忽略掉你已經知道的知識,好比 proxy 的用法,Reflect 的用法。而後試着手敲一遍文章中的代碼demo。
在這篇文章裏你最好本身先實現一遍 Proxy 代理深層對象。
如下是我寫的demo

<html>
  <div id='array'></div>
  <script> const array= document.querySelector('#array') const isObject = (val) => val !== null && typeof val === 'object' function proxyMethod(obj) { let p2 = new Proxy(obj, { get(target, key, val) { const res = Reflect.get(target, key, val) if(isObject(res)) { return proxyMethod(res) } return res }, set(target, key, val, receiver) { const res = Reflect.set(target, key, val) array.innerHTML = receiver console.log('set', receiver) return true } }); return p2 } let p = { arr: [1,2,3], c: 2 } let p2 = proxyMethod(p) p2.arr.push(8) setTimeout(() => { p2.arr.push(6) }, 1000) </script>
</html>
複製代碼

將社區的文章看的大概,瞭解源碼的核心功能,則能夠進行下一步了。

庖丁解牛

到目前爲止,你知道了源碼實現的核心,而其餘的就是源碼包裝核心以後實現功能的代碼。舉個例子:你如今知道了發動機怎麼作,接下來要作的是,裝上傳動、裝上輪胎、裝上車架......
因此接下來你得對代碼的全局有個大概的瞭解,將代碼拆開來,你須要知道輪胎在哪兒?車門在哪兒?

這個過程,我稱之爲 「架構拆解」,庖丁解牛,你解架構。那如何解呢。先列舉下Vue的文件架構。

這裏推薦一個工具,能夠在命令行展現文件的樹狀結構。

brew install tree
tree -L 1

├── compiler-core
├── compiler-dom
├── global.d.ts
├── reactivity
├── runtime-core
├── runtime-dom
├── runtime-test
├── server-renderer
├── shared
├── template-explorer
└── vue
複製代碼

接下來逐條對文件夾進行分析。本篇只分析 reactivity文件夾。

.
├── README.md
├── __tests__
├── api-extractor.json
├── index.js
├── package.json
└── src
複製代碼

有 README 先看 README。

# @vue/reactivity

## Usage Note

This package is inlined into Global & Browser ESM builds of user-facing renderers (e.g. `@vue/runtime-dom`), but also published as a package that can be used standalone. The standalone build should not be used alongside a pre-bundled build of a user-facing renderer, as they will have different internal storage for reactivity connections. A user-facing renderer should re-export all APIs from this package.

For full exposed APIs, see `src/index.ts`. You can also run `yarn build reactivity --types` from repo root, which will generate an API report at `temp/reactivity.api.md`.

## Credits

The implementation of this module is inspired by the following prior art in the JavaScript ecosystem:

- [Meteor Tracker](https://docs.meteor.com/api/tracker.html)
- [nx-js/reactivity-util](https://github.com/nx-js/reactivity-util)
- [salesforce/observable-membrane](https://github.com/salesforce/observable-membrane)

## Caveats

- Built-in objects are not observed except for `Map`, `WeakMap`, `Set` and `WeakSet`.

複製代碼

從上段看出來,這個包能夠獨立使用。至於如何使用,能夠看 index.ts 文件,前提是,咱們瞭解它導出的每一對象。

咱們再看看 src 的結構

.
├── baseHandlers.ts
├── collectionHandlers.ts
├── computed.ts
├── effect.ts
├── index.ts
├── lock.ts
├── operations.ts
├── reactive.ts
└── ref.ts
複製代碼

接下來的工做是標註每個文件的做用。最好能作備註。從 index.ts 讀起,採用 深度優先 的方式。

接下來我會一行行讀,你能夠跟隨個人腳步......

祕徑追蹤

index.ts

export { ref, isRef, toRefs, Ref, UnwrapRef } from './ref'
複製代碼

先進入 ref 文件看看 ref 方法怎麼來的。

ref.ts

import { track, trigger } from './effect'
import { OperationTypes } from './operations'
import { isObject } from '@vue/shared'
import { reactive } from './reactive'

export const refSymbol = Symbol(__DEV__ ? 'refSymbol' : undefined)

export interface Ref<T> {
  [refSymbol]: true
  value: UnwrapNestedRefs<T>
}

export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>

const convert = (val: any): any => (isObject(val) ? reactive(val) : val)

export function ref<T>(raw: T): Ref<T> {
  // 若是是對象,則用 reactive 方法 包裝 raw
  raw = convert(raw)
  // 返回一個 v 對象,在 取value 值時,調用 track 方法,在存 value 值時,調用 trigger方法
  const v = {
    [refSymbol]: true,
    get value() {
      track(v, OperationTypes.GET, '')
      return raw
    },
    set value(newVal) {
      raw = convert(newVal)
      trigger(v, OperationTypes.SET, '')
    }
  }
  return v as Ref<T>
}
複製代碼

能夠看到,咱們遇到了三個未知函數

  • convert 方法裏調用了 reactive 方法,對ref 的參數進行了包裝。
  • 在 v 對象中, 取value 值時,調用 track 方法,在存 value 值時,調用 trigger方法

繼續往下走,看看 reactive 方法的內容,我將我讀代碼的思路寫進了備註裏,你能夠檢索 MARK1 - MARK10 來逐行看思路。 reactive.ts

import { isObject, toTypeString } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'

import {
  mutableCollectionHandlers,
  readonlyCollectionHandlers
} from './collectionHandlers'

import { UnwrapNestedRefs } from './ref'
import { ReactiveEffect } from './effect'
// WeakMap 主要是用來儲存 {target -> key -> dep} 的連接,它更像是 依賴 Dep 的類,它包含了一組 Set,但咱們只是簡單的儲存它們,以減小內存消耗
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()

// WeakMaps that store {raw <-> observed} pairs.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
const readonlyToRaw = new WeakMap<any, any>()

// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()

const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
// MARK9: ->進去看
const canObserve = (value: any): boolean => {
  return (
    // 不是vue 對象
    !value._isVue &&
    // 不是 vNode
    !value._isVNode &&
    // 白名單: Object|Array|Map|Set|WeakMap|WeakSet
    observableValueRE.test(toTypeString(value)) &&
    // 沒有代理過的
    !nonReactiveValues.has(value)
  )
}

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // MARK1: 若是target 只讀,則返回它
  if (readonlyToRaw.has(target)) {
    return target
  }
  // MARK2: 若是target被用戶設置爲只讀,則讓它只讀,並返回
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  // MARK3: 貌似到重點了
  return createReactiveObject(
    target,
    // MARK3: 底下這一堆是 weakMap,先無論它們
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

export function readonly<T extends object>(
  target: T
): Readonly<UnwrapNestedRefs<T>>
export function readonly(target: object) {
  // value is a mutable observable, retrieve its original and return
  // a readonly version.
  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

function createReactiveObject(
  target: any,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // MARK4: 很明顯,這裏只 typeof判斷,Array Date之類的也能經過
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // MARK5: 已經代理過的對象,不須要在代理
  let observed = toProxy.get(target)
  // MARK6: 這裏用 void 0代替了undfined,防止 undfined 被重寫。get 到新姿式了。
  if (observed !== void 0) {
    return observed
  }
  // MARK7: 目標自己就是一個 proxy 對象
  if (toRaw.has(target)) {
    return target
  }
  // MARK8: 只有加入白名單的對象才能被代理
  if (!canObserve(target)) {
    return target
  }
  // MARK10: collectionTypes 能夠看到是一個Set結構,放了幾個構造函數:[Set, Map, WeakMap, WeakSet],這個三目表達式就是區分了以上四種對象和其餘對象的 handlers,咱們先從 baseHandlers 看起
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  observed = new Proxy(target, handlers)
  // 如下則是存儲下已經代理過的對象,以做優化。
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  // 返回代理後的對象
  return observed
}

export function isReactive(value: any): boolean {
  return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

export function isReadonly(value: any): boolean {
  return readonlyToRaw.has(value)
}

export function toRaw<T>(observed: T): T {
  return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T>(value: T): T {
  readonlyValues.add(value)
  return value
}

export function markNonReactive<T>(value: T): T {
  nonReactiveValues.add(value)
  return value
}

複製代碼

reactive.ts 終於完了,接下來咱們從 上文的 MARK10 進入 到 handler.ts 中,老規矩,這裏的代碼思路從 MARK11 開始。

import { reactive, readonly, toRaw } from './reactive'
import { OperationTypes } from './operations'
import { track, trigger } from './effect'
import { LOCKED } from './lock'
import { isObject, hasOwn } from '@vue/shared'
import { isRef } from './ref'

const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key => (Symbol as any)[key])
    .filter(value => typeof value === 'symbol')
)

function createGetter(isReadonly: boolean) {
  return function get(target: any, key: string | symbol, receiver: any) {
    const res = Reflect.get(target, key, receiver)
    //MARK13: 防止key爲Symbol的內置對象,好比 Symbol.iterator
    if (typeof key === 'symbol' && builtInSymbols.has(key)) {
      return res
    }
    //MARK14: 從上文知道,被ref 包裝過,則返回
    if (isRef(res)) {
      return res.value
    }
    // 這裏貌似是依賴收集的,暫且不深刻
    track(target, OperationTypes.GET, key)
    // MARK15 對深層對象再次包裝
    // res 是深層對象,若是它不是隻讀對象,則調用 reactive 繼續代理,上文本身實現過,這裏很好理解
    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
        : reactive(res)
      : res
  }
}

function set(
  target: any,
  key: string | symbol,
  value: any,
  receiver: any
): boolean {
  // MARK16 優化:以前有存過,直接拿就好了
  value = toRaw(value)
  // MARK17 key 是否爲 taget 的屬性,
  /**
   * 
   * export const hasOwn = (
        val: object,
        key: string | symbol
      ): key is keyof typeof val => hasOwnProperty.call(val, key)

      這個方法是解決 數組push時,會調用兩次 set 的狀況,好比 arr.push(1)
      第一次set,在數組尾部添加1
      第二次set,給數組添加length屬性
      hasOwnProperty 方法用來判斷目標對象是否含有指定屬性。數組自己就有length的屬性,因此這裏是 true
   */
  const hadKey = hasOwn(target, key)
  const oldValue = target[key]
  /**
   * MARK18 若是 value 不是響應式數據,則須要將其賦值給 oldValue
   */
  if (isRef(oldValue) && !isRef(value)) {
    //MARK19 這將觸發 oldValue 的 set value 方法,若是 isObject(value) ,則會通過 reactive 再包裝一次,將其變成響應式數據
    oldValue.value = value
    return true
  }
  const result = Reflect.set(target, key, value, receiver)
  // MARK20 target 若是隻讀 或者 存在於 reactiveToRaw 則不進入條件,reactiveToRaw 儲存着代理後的對象
  if (target === toRaw(receiver)) {
    /* istanbul ignore else */
    if (__DEV__) {
      const extraInfo = { oldValue, newValue: value }
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key, extraInfo)
      } else if (value !== oldValue) {
        trigger(target, OperationTypes.SET, key, extraInfo)
      }
    // MARK21 只看生產環境
    } else {
      //MARK22 屬性新增,觸發 ADD 枚舉
      if (!hadKey) {
        trigger(target, OperationTypes.ADD, key)
      } else if (value !== oldValue) {
        //MARK23 屬性修改,觸發 SET 枚舉
        trigger(target, OperationTypes.SET, key)
      }
    } 
    /** MARK24 對應 MARK17
     * else {}
     */
  }
  return result
}

function deleteProperty(target: any, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = target[key]
  const result = Reflect.deleteProperty(target, key)
  if (hadKey) {
    /* istanbul ignore else */
    if (__DEV__) {
      trigger(target, OperationTypes.DELETE, key, { oldValue })
    } else {
      trigger(target, OperationTypes.DELETE, key)
    }
  }
  return result
}

function has(target: any, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  track(target, OperationTypes.HAS, key)
  return result
}

function ownKeys(target: any): (string | number | symbol)[] {
  track(target, OperationTypes.ITERATE)
  return Reflect.ownKeys(target)
}

export const mutableHandlers: ProxyHandler<any> = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
}
// MARK11 入口函數
export const readonlyHandlers: ProxyHandler<any> = {
  // MARK12: 建立 getter
  get: createGetter(true),
// MARK12: 建立 setter
  set(target: any, key: string | symbol, value: any, receiver: any): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Set operation on key "${String(key)}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return set(target, key, value, receiver)
    }
  },

  deleteProperty(target: any, key: string | symbol): boolean {
    if (LOCKED) {
      if (__DEV__) {
        console.warn(
          `Delete operation on key "${String(
            key
          )}" failed: target is readonly.`,
          target
        )
      }
      return true
    } else {
      return deleteProperty(target, key)
    }
  },

  has,
  ownKeys
}


複製代碼

未完待續

到此爲止,你對代碼總體有了模糊的理解,但還有些棱角不清晰,好比:

  • MARK4 那一連串 WeakMap 是幹啥的?
  • MARK10 下面爲何 set 了屢次? ...... 在後面的文章中,我會逐條分析。讀懂源碼是個漫長的過程,未完待續......
相關文章
相關標籤/搜索