Observer原始碼解析

laihuamin發表於2019-06-07

前言

在Observer原始碼部分主要有三個角色:Observer、Dep、Watcher。

Observer

看這一部分,可以帶著以下幾個問題來看:

1、滿足什麼條件可以將其變成響應式的

2、Observer是如何去分別處理傳入的陣列或者物件的?

3、有兩處new Dep,作用分別是什麼?

4、核心程式碼defineReactive幹了些什麼?

5、Dep.target是什麼?defineReactive中get時,為什麼要判斷Dep.target?

滿足什麼條件可以將其變成響應式的

function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
複製程式碼

其實上述程式碼中有很多判斷,我們可以得出以下結論:

(1)必須是一個物件,且不能是vnode的型別的。

Observer是如何去分別處理傳入的陣列或者物件的

if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  this.observeArray(value)
} else {
  this.walk(value)
}
複製程式碼

這段程式碼就是判斷傳入的值是不是陣列,如果是陣列,走observeArray方法,如果不是陣列,那麼走walk方法。

walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
}
observeArray (items) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
}
複製程式碼

walk這個方法就是遍歷物件,然後給物件中的屬性值變成響應式的,遍歷完之後,整個物件就是響應式物件了。 observeArray這個方法是遍歷陣列,然後對陣列中每一個元素在走一遍響應式流程。

有兩處new Dep,作用分別是什麼

第一處:

class Observer {
  constructor (value) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
複製程式碼

第二處:

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  const dep = new Dep()
  // 省略中間程式碼
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        // 省略中間程式碼
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 省略中間程式碼
      dep.notify()
    }
  })
}
複製程式碼

第二處dep我們很好理解,是給get和set服務的,進行依賴收集和派發更新。

第一處dep,我們可以考慮一下,他是整個物件的一個屬性,那麼他何時進行依賴收集和派發更新。

我們可以全域性搜一下dep.depend。發現會有三處。有兩處是對ob屬性進行操作的,也就是對整個物件進行依賴收集。

在全域性搜一下dep.notify。發現有四處。有三處是對ob屬性進行操作的。分別是set和del,陣列的一個方法。

核心程式碼defineReactive幹了些什麼

這一部分程式碼可以分為三部分看:定義一些變數、Object.defineProperty、對childOb操作。

  // 生成一個新的dep。
  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) && arguments.length === 2) {
    val = obj[key]
  }
複製程式碼
get: function reactiveGetter () {
  // 獲取值
  const value = getter ? getter.call(obj) : val
  // 當存在Dep.target的時候進行依賴收集
  if (Dep.target) {
    dep.depend()
    // 省略中間程式碼
  }
  // 返回獲取到的值
  return value
},
set: function reactiveSetter (newVal) {
  // 獲取原來的值
  const value = getter ? getter.call(obj) : val
  // 將新的值和原值進行對比,如果沒有發生改變,就直接返回
  if (newVal === value || (newVal !== newVal && value !== value)) {
    return
  }
  // 該屬性不能set的情況也直接返回
  if (getter && !setter) return
  // 給該屬性賦值
  if (setter) {
    setter.call(obj, newVal)
  } else {
    val = newVal
  }
  // 重新對這個值進行監聽
  childOb = !shallow && observe(newVal)
  // 更新dep中的watcher
  dep.notify()
}
複製程式碼
// 嘗試將值轉化成響應式物件
let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        // 如果值能唄轉化成響應式物件,那麼對整個物件進行依賴收集
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 省略中間程式碼
      //因為值發生了變動,所以再一次嘗試將其變成一個響應式物件
      childOb = !shallow && observe(newVal)
    }
  })
複製程式碼

Dep.target是什麼

其實Dep.target是一個全域性變數,更是一個wathcer。在dep檔案中的原始碼如下:

Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
複製程式碼

defineReactive中get時,為什麼要判斷Dep.target

因為所有定義在data中的值,都會被變成響應式物件,但是每一個不一定有watcher。watcher分為三種:render中生成的watcher、使用者自定義的watcher、computed。

Observer原始碼解析

上述流程中,當生成一個新的renderWatcher的時候,便會走get流程,然後進行依賴收集,如果沒有Dep.target,說明這個值並沒有對應的watcher,所以不需要進行依賴收集。 當更新的是時候,又回進行一次依賴收集。

相關文章