vue[原始碼]你不知道的observe!

小為子發表於2019-04-04

observe工廠函式

在之前的原始碼initdata函式中最後一句

observe(data, true /* asRootData */)複製程式碼

呼叫了observe函式觀察資料

export function observe (value: any, asRootData: ?boolean): Observer | void { 
 if (!isObject(value) || value instanceof VNode) { 
    return //判斷是否是一個物件或者VNode  
}
  let ob: Observer | void  //定義變數ob 儲存observe例項  
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 
   ob = value.__ob__ //檢測目標物件是否含有_ob_,並且 __ob__ 屬性應該是Observer的例項            作用是為了避免重複觀察同一個資料物件
  } 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
}複製程式碼

看下else..if..第一個條件shouldObserve 必須為true

export let shouldObserve: boolean = true //初始化預設設定為true
export function toggleObserving (value: boolean) {
 // 接收一個布林值 改變true false  shouldObserve = value}複製程式碼

第二個條件!isServerRendering() 必須ture

let _isServer //定義一個變數
export const isServerRendering = () => {
  if (_isServer === undefined) {
    /* istanbul ignore if */    
if (!inBrowser && !inWeex && typeof global !== 'undefined') { 
     // detect presence of vue-server-renderer and avoid     
 // Webpack shimming the process      
_isServer = global['process'] && global['process'].env.VUE_ENV === 'server'  
  } else { 
     _isServer = false    
}  
}  return _isServer}複製程式碼

這個函式作用是為了判斷是否為服務端渲染

第三個條件判斷是否為陣列和純物件

第四個判斷物件是可擴充性的...接下來就不說了 相信應該能看懂

ob = new Observer(value)複製程式碼

最後建立一個observer例項


observe建構函式

export class Observer 
{  
value: any;  
dep: Dep; 
 vmCount: number; // number of vms that have this object as root $data
  constructor (value: any) {
    this.value = value //屬性引用了資料物件   
    this.dep = new Dep() //儲存了一個新建立的 Dep 例項物件    
    this.vmCount = 0 //例項屬性設定為0    
    def(value, '__ob__', this) //為資料物件定義了一個 __ob__ 屬性,這個屬性的值就是當前 Observer 例項物件,
    其中 def 函式其實就是 Object.defineProperty 函式的簡單封裝,
之所以這裡使用 def 函式定義 __ob__ 屬性是因為這樣可以定義不可列舉的屬性,
這樣後面遍歷資料物件的時候就能夠防止遍歷到 __ob__ 屬性    
if (Array.isArray(value)) { 
//判斷是否為陣列      
if (hasProto) {
        protoAugment(value, arrayMethods)      
} else {
        copyAugment(value, arrayMethods, arrayKeys)      
}      this.observeArray(value)    
} else { 
//純物件的情況      this.walk(value)    
}  
}
  /**   * Walk through all properties and convert them into  
 * getter/setters. This method should only be called when   * value type is Object.   */ 
 walk (obj: Object) {
    const keys = Object.keys(obj)    
for (let i = 0; i < keys.length; i++) { 
     defineReactive(obj, keys[i])    
}  
}
  /**   * Observe a list of Array items.   */  
observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])    
}  
}
}
複製程式碼

walk 例項物件方法遍歷了變數獲得了可列舉的屬性,

每個屬性呼叫了defineReactive函式:

export function defineReactive ( 
 obj: Object,
  key: string,
  val: any,  
 customSetter?: ?Function,  shallow?: boolean) {
  const dep = new Dep() //定義一個dep常量接收Dep例項物件 
//getOwnPropertyDescriptor方法返回指定物件上一個自有屬性對應的屬性描述
  const property = Object.getOwnPropertyDescriptor(obj, key)  
if (property && property.configurable === false) {
     return //判斷是否可配置  
}
  // cater for pre-defined getter/setters  
const getter = property && property.get 
//儲存 property 物件的 get 和 set函式  
const setter = property && property.set  
if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]//當只傳遞兩個引數時,說明沒有傳遞第三個引數 val,
那麼此時需要根據 key 主動去物件上獲取相應的值,即執行 if 語句塊內的程式碼:val = obj[key]  
}
  let childOb = !shallow && observe(val)  
Object.defineProperty(obj, key, {
//函式重新定義屬性的 setter/getter,這會導致屬性原有的 set 和 get 方法被覆蓋,
所以要將屬性原有的 setter/getter 快取,並在重新定義的 set 和 get 方法中呼叫快取的函式,
從而做到不影響屬性的原有讀寫操作。    
enumerable: true,    
configurable: true,    
get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val 
//判斷是否存在getter?直接呼叫該函式:使用val      
if (Dep.target) {
//Dep.target 中儲存的值就是要被收集的依賴(觀察者)
        dep.depend() 
//行 dep 物件的 depend 方法將依賴收集到 dep中        
if (childOb) {
//大概意思是收集的依賴的觸發時機是在使用 $set 或 Vue.set 給資料物件新增新屬性時觸發          
childOb.dep.depend() 
if (Array.isArray(value)) {
 //對陣列依賴收集的處理            
dependArray(value)          
}        
}      
}
      return value    
},    
set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val //如上一樣     
 /* eslint-disable no-self-compare */      
if (newVal === value || (newVal !== newVal && value !== value)) { 
       return //新舊值判斷處理 後面判斷應該是為了處理NaN      
}      /* eslint-enable no-self-compare */     
 if (process.env.NODE_ENV !== 'production' && customSetter) { 
       customSetter()
       //環境判斷和customSetter函式判斷(作用:用來列印輔助屬性)      
}     
 // #7981: for accessor properties without setter     
 if (getter && !setter) return     
 if (setter) {
  //正確設定屬性值        setter.call(obj, newVal)      
} else { 
       val = newVal      
}     
 childOb = !shallow && observe(newVal)     
 dep.notify()//深度觀測 依賴收集    
}  
})
}複製程式碼

defineReactive 函式的核心就是 將資料物件的資料屬性轉換為訪問器屬性,即為資料物件的屬性設定一對 getter/setter,但其中做了很多處理邊界條件的工作defineReactive 接收五個引數,但是在 walk 方法中呼叫 defineReactive 函式時只傳遞了前兩個引數,即資料物件和屬性的鍵名





相關文章