[vue面試專問]Vue.set 和 Vue.delete 的實現

Vue.set($set) 

Vue.delete($delete)


咱們發現 $set$delete 定義在 stateMixin 函數中,以下代碼:
react

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object  
  // when using Object.defineProperty, so we have to procedurally build up  
  // the object here.  
  const dataDef = {}  
  dataDef.get = function () { return this._data }  
  const propsDef = {}  
  propsDef.get = function () { return this._props }  
if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',        this      )    
}    
  propsDef.set = function () {
        warn(`$props is readonly.`, this)    
  }  
}
  Object.defineProperty(Vue.prototype, '$data', dataDef)  
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  Vue.prototype.$set = set  
  Vue.prototype.$delete = del   
  Vue.prototype.$watch = function (
       expOrFn: string | Function,    
       cb: any,    
       options?: Object  ): Function {
   const vm: Component = this    
   if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)    
   }    
   options = options || {}   
   options.user = true    
   const watcher = new Watcher(vm, expOrFn, cb, options)
   if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }}複製代碼

是否是太長,太複雜?看不懂?express

不急咱們慢慢往下看,逐步介紹:數組

上面定義常量和環境判斷就不說了直接看核心:bash

Vue.prototype.$set = set  
Vue.prototype.$delete = del複製代碼

能夠看到 $set$delete 的值分別是是 setdel函數

其實咱們發現initGlobalAPI 函數中定義了:ui

Vue.set = set 
 Vue.delete = del複製代碼

不難看出其實 Vue.set == $set ,Vue.delete == $deletethis

 接下來看看Vue.set代碼:

export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&    (isUndef(target) || isPrimitive(target))  ) {
    warn(`Cannot set reactive property on undefined,
    null, or primitive value: ${(target: any)}`)
  }
if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)    return val  
}  
if (key in target && !(key in Object.prototype)) {
    target[key] = val    return val  
}
  const ob = (target: any).__ob__  
if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'    )
    return val  }
if (!ob) {
    target[key] = val
    return val  
}  
defineReactive(ob.value, key, val)  
ob.dep.notify()  
return val
}複製代碼

set 函數接收三個參數:第一個參數 target 是將要被添加屬性的對象,第二個參數 key 以及第三個參數 val分別是要添加屬性的鍵名和值。
spa

if判斷中isUndef函數用來判斷一個值是不是 undefinednullprototype

isPrimitive 函數用來判斷一個值是不是原始類型值
code

ECMAScript 有 5 種原始類型(primitive type),即 Undefined、Null、Boolean、Number 和 String

if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)   
    target.splice(key, 1, val)
    return val  
}複製代碼

這個判斷主要是對target與key作了校驗判斷是不是個數組和key是否爲有效的數組索引

target.length = Math.max(target.length, key)  
target.splice(key, 1, val)複製代碼

這就涉及到上篇博客講的(數組變異處理)

target.length = Math.max(target.length, key)複製代碼

將數組的長度修改成 target.lengthkey 中的較大者,不然若是當要設置的元素的索引大於數組長度時 splice 無效。

target.splice(key, 1, val)複製代碼

數組的 splice 變異方法可以完成數組元素的刪除、添加、替換等操做。而 target.splice(key, 1, val) 就利用了替換元素的能力,將指定位置元素的值替換爲新值,同時因爲 splice 方法自己是可以觸發響應的

而後接下來一個if:

if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
}複製代碼

若是 target 不是一個數組,那麼必然就是純對象了,當給一個純對象設置屬性的時候,假設該屬性已經在對象上有定義了,那麼只須要直接設置該屬性的值便可,這將自動觸發響應,由於已存在的屬性是響應式的

key in target複製代碼

判斷keytarget 對象上,或在 target 的原型鏈上

!(key in Object.prototype)複製代碼

同時必須不能在 Object.prototype

const ob = (target: any).__ob__
複製代碼

定義了 ob 常量,它是數據對象 __ob__ 屬性的引用

defineReactive(ob.value, key, val)  ob.dep.notify()複製代碼

defineReactive 函數設置屬性值,這是爲了保證新添加的屬性是響應式的。

 __ob__.dep.notify() 從而觸發響應。這就是添加全新屬性觸發響應的原理

if (!ob) {    target[key] = val    return val  }複製代碼

target 也許本來就是非響應的,這個時候 target.__ob__是不存在的,因此當發現 target.__ob__ 不存在時,就簡單的賦值便可

if (target._isVue || (ob && ob.vmCount)) {
複製代碼

Vue 實例對象擁有 _isVue 屬性,因此當第一個條件成立時,那麼說明你正在使用 Vue.set/$set 函數爲 Vue 實例對象添加屬性,爲了不屬性覆蓋的狀況出現,Vue.set/$set 函數不容許這麼作,在非生產環境下會打印警告信息

(ob && ob.vmCount)複製代碼

這個就涉及比較深:主要是觀測一個數據對象是否爲根數據對象,因此所謂的根數據對象就是 data 對象

當使用 Vue.set/$set 函數爲根數據對象添加屬性時,是不被容許的

由於這樣作是永遠觸發不了依賴的。緣由就是根數據對象的 Observer 實例收集不到依賴(觀察者)

set講完了 講講delete


 Vue.delete/$delete

仍是同樣先看源碼:

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))  ) {
    warn(`Cannot delete reactive property on undefined, 
      null, or primitive value: ${(target: any)}`)  
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)    return  
  }  
   const ob = (target: any).__ob__  
   if (target._isVue || (ob && ob.vmCount)) {
      process.env.NODE_ENV !== 'production' && warn( 
     'Avoid deleting properties on a Vue instance or its root $data ' +   
    '- just set it to null.'    )
     return  
    }  
   if (!hasOwn(target, key)) {
    return  
   }
   delete target[key]  
  if (!ob) {
    return  
  }
  ob.dep.notify()
}
複製代碼

del 函數接收兩個參數,分別是將要被刪除屬性的目標對象 target 以及要刪除屬性的鍵名 key

第一個if判斷和set同樣 就不講了

if (Array.isArray(target) && isValidArrayIndex(key)) {
       target.splice(key, 1)    
    return  
}複製代碼

第二個判斷其實和set也差很少。。。刪除數組索引(一樣是變異數組方法,觸發響應)

if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +  
    '- just set it to null.'    )
    return  
}複製代碼

其實也不用說了 判斷都同樣(不能刪除Vue 實例對象或根數據的屬性)

if (!hasOwn(target, key)) {    return  }複製代碼

檢測key 是不是 target 對象自身擁有的屬性

if (!ob) {    return  }複製代碼

判斷ob對象是否存在若是不存在說明 target 對象本來就不是響應的,因此直接返回(return)便可

若是 ob 對象存在,說明 target 對象是響應的,須要觸發響應才行,即執行 ob.dep.notify()

進行觀測和依賴收集。

相關文章
相關標籤/搜索