1. 程式人生 > >vue資料的雙向繫結原始碼分析

vue資料的雙向繫結原始碼分析

VUE中的資料雙向繫結是通過資料劫持的方式實現的,核心的便是object.defineProperty(),它內部分為三個部分:

observer 可以遞迴地監聽物件上的所有屬性,當屬性改變時觸發相應的watcher。

watcher 觀察者,當監聽的資料值修改時,執行相應的回撥函式,更新模板內容。

dep 連線observer watcher,每一個observer對應一個dep,內部維護一個數組,儲存與該observer相關的watcher。

由初始化資料進入到observe(value)方法,為給定的資料繫結observer例項,在observe方法中的核心就是

ob = new Observer(value)

在getter方法中,把watcher新增到dep中,在setter方法中,觸發watcher執行回撥(生成render函式,生成虛擬dom,對映在頁面上)。

1.observer class中屬性dep定義為new Dep(),對陣列和物件型別分別處理。對陣列呼叫observeArray方法,對物件呼叫walk方法。

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

1.1walk方法:遍歷所有例項屬性,將之呼叫defineReactive方法。

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

1.2observeArray方法,對陣列中的每個元素呼叫observe方法。

 observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }

1.11 深入檢視defineReactive方法。

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  if (!getter && arguments.length === 2) {
    val = obj[key]
  }
  const setter = property && property.set

  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) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

傳入方法中的引數為 obj key ,分別表示物件本身和 例項屬性名。

(1)在此方法中首先建立dep例項,之後獲取對於例項屬性的描述,通過Object.getOwnPropertyDescriptor,獲取例項屬性是否可以修改,若configurable為false,那麼從方法中返回。

(2)獲取屬性的get方法,若屬性為資料屬性而不為訪問器屬性,則get值為undefined,並將結果賦值為getter,如果此屬性為資料屬性,並且傳入了兩個引數,則把物件對應的例項屬性值賦值為val。

(3)獲取屬性的set方法值。若為資料屬性則為undefined,並將其賦值為setter。

(4)若引數中未包含true false,則呼叫observe(val)方法,並將其賦值為childOb  這裡涉及到observe方法。會建立observer例項,並返回。

(5)利用Object defineProperty定義obj的key例項屬性描述為可遍歷,可修改,設定其get,set方法。

get方法:a.若此屬性已包含get方法,則在obj作用域下呼叫get方法。若無get方法,則獲取其例項屬性值val。將get方法返回值或者obj[key]賦值為value。b.如果Dep.target有值時,也就是目前存在唯一的watcher正在起作用時,通過dep例項呼叫depend方法,來為watcher和此dep例項新增依賴。此處的depend方法接下來細談。

    b.1 depend方法:若當前存在watcher,則呼叫Watcher 型別的addDep方法。

export default class Dep {
  static target: ?Watcher;

depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
       b.11 深入addDep方法:若Watcher的新dep中未包含此dep例項,也就是此屬性頭一次被監聽,那麼將此dep例項新增進newDeps中,若果depId中未包含此dep例項,則呼叫dep例項的addSub方法
/**
 * Add a dependency to this directive.
 */
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

      b.111呼叫addSub方法。為dep例項的subs陣列中新增watcher。

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};
if (childOb) {
  childOb.dep.depend()
  if (Array.isArray(value)) {
    dependArray(value)
  }

此處在研究observe後進行補充。

set方法

(1)首先判斷此屬性是否包含get方法,如果包含則獲取get方法在obj物件的作用域下呼叫的返回值,如果沒有get方法,則獲取例項屬性的值,並將其賦值為value。

(2)判斷為例項屬性新賦的值是否全等於value值,也就是例項屬性已有的值和新賦值是否全等,或者value自身與自身就不全等或者新值與自身不全等的情況下,從set方法中返回。

(3)如果已有set方法,則在obj作用域下呼叫set方法,否則將val定義為新賦的值

(4)根據是否傳入true false 呼叫observe(newval)方法,將返回值賦值為childOb

(5)呼叫dep例項的notify方法。對dep例項的subs陣列中的watcher呼叫update方法,此處延伸下去。

 notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

a.watcher的update方法會呼叫run方法。

 run () {
    if (this.active) {
      const value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`);
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
 get方法:先將目前正在執行工作的watcher新增到Dep的targetStack中,設定全域性變數Dep.target,之後touch Watcher初始化時傳入的引數expOrFn中
涉及到的每一項資料,然後觸發該資料項的getter函式;設定dep.target是依賴收集過程中的重要一步,getter函式中就是通過判斷Dep.target的
有無來判斷是Watcher初始化時呼叫的還是普通資料讀取,如果有則進行依賴收集。
get () {
  pushTarget(this);//設定全域性變數Dep.target,將watcher儲存在這個全域性變數中。
let value;
const vm = this.vm;
try {
    value = this.getter.call(vm, vm); 
} catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`);
} else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
if (this.deep) {
      traverse(value);
}
    popTarget();
this.cleanupDeps();
}
  return value
}
class Dep中
Dep.target = null;
const targetStack = [];

function pushTarget (_target) {
  if (Dep.target) targetStack.push(Dep.target);
  Dep.target = _target;
}

watcher物件的run方法中會呼叫cb方法,而該方法會進而呼叫patch方法,進行diff,比較新舊值差距,進而渲染頁面


相關推薦

vue資料雙向原始碼分析

VUE中的資料雙向繫結是通過資料劫持的方式實現的,核心的便是object.defineProperty(),它內部分為三個部分:observer 可以遞迴地監聽物件上的所有屬性,當屬性改變時觸發相應的watcher。watcher 觀察者,當監聽的資料值修改時,執行相應的回撥

vue開發:vue,angular,react資料雙向原理分析

傳統做法 前端維護狀態,手動操作DOM更新檢視。前端框架對伺服器資料通過模版進行渲染。當用戶產生了一個動作之後,我們通過document.getElementBy... 手動進行DOM更新。  框架幫忙分離資料和檢視,後續狀態更新需要手動操作DOM,因為框架只管首次渲染,不追蹤狀態監聽變化。 雙向資料繫結

淺談vue,angular,react資料雙向原理分析

傳統做法 前端維護狀態,手動操作DOM更新檢視。前端框架對伺服器資料通過模版進行渲染。當用戶產生了一個動作之後,我們通過document.getElementBy... 手動進行DOM更新。  框架幫忙分離資料和檢視,後續狀態更新需要手動操作DOM,因為框架只管首次渲染,不

Vue資料雙向探究

使用過vue的小夥伴都會感覺,哇,這個框架對開發者這麼友好,簡直都要笑出聲了。 確實,使用過vue的框架做開發的人都會感覺到,以前寫一大堆操作dom,bom的東西,現在用不著了,對開發者來說更容易去注重對操作邏輯的思考和實現,省了不少事兒呢!!! 我是直接從原生js,jq的開發用過度到使用v

Vue資料雙向的原理

vue資料雙向繫結是通過資料劫持結合釋出者-訂閱者模式的方式來實現的。 雙向繫結就是檢視上的變化能夠反映到資料上,資料上的變化也能反映到檢視上。如下圖所示: 關鍵點在於data如何更新view,因為view更新data其實可以通過事件監聽即可,比如input

vue資料雙向的原理和vue-router路由的實現原理

vue實現雙向資料繫結的原理就是利用了 Object.defineProperty() 這個方法重新定義了物件獲取屬性值(get)和設定屬性值(set)的操作來實現的。 在MDN上對該方法的說明是:Object.defineProperty() 方法會直接在一個物件上定義一

談談Vue資料雙向原理,看看你的回答能打幾分

面試官的這個問題也可以理解成為“你是怎麼理解Vue資料繫結,知道它背後實現的原理麼”。一般剛畢業的前端新人可能會說,用v-model。(當然,這可能是句廢話) 如果簡單說下v-model指令,是Vue的語法糖之類的,可能不會讓面試官滿意,也看不出你對Vue的熟練程度。只能說

Vue資料雙向原理

先看效果圖 //程式碼: <div id="app"> <input v-model="name" type="text"> <h1>{{name}}</h1> </div&g

vue原始碼學習——資料雙向的Object.defineProperty

情景:vue雙向繫結,這應該是多數講vue優勢脫口而出的名詞,然後你就會接觸到一個方法 Object.defineProperty(a,"b",{}) 這個方法該怎麼用 簡單例子敲一下 var a = {} Object.defineProperty(a,"b

React學習之旅----實現類似vue資料雙向

react沒有資料的雙向繫結,但可以用過一些方法實現: import React from 'react'; class TodoList extends React.Component { constructor(props) { super(props) this.sta

vue實現雙向資料之原理及實現篇 vue雙向原理及實現

轉自:canfoo#! vue的雙向繫結原理及實現 前言 先上個成果圖來吸引各位: 程式碼:                          &nb

Vue-事件資料雙向

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>事件處理, 雙向資料繫結</title> <script src="js/v

vue中v-model的資料雙向(重要)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body&

Vue 建構函式、生命週期與資料雙向

Vue2 建構函式、生命週期與資料雙向繫結 Vue是一個響應式的、漸進式的JavaScript框架,它在設計上採用MVVM模式,將檢視與資料兩部分分離。下面就是一個簡單的Vue例項: <!DOCTYPE html> <html lang="en"> <h

vue資料雙向

1.在data中設定的資料才會進行雙向繫結 2.陣列 2.1.data中的陣列,觸發陣列更新的方式有:pop、push、shift、unshift、splice、sort、reverse 2.2.而filter、slice、cancat等方式並不會改變原陣

Vue學習日誌第二天 v-model 資料雙向 eval函式用法

<div id="app"> <p> {{msg}}</p> <p><input type="text" v-b

vue之選單新增選擇,知識:資料雙向、迴圈渲染、事件點選以及按鍵的點選

要求: 1.可以增加菜名 2.可以刪除菜名 3.點選選擇菜名後自動增加到已選選單中 4.可以在已選選單中取消選擇   程式碼: <template> <div> <input type="text" ref="add"> &

怎麼理解vue資料雙向

單向資料繫結 指的是我們先把模板寫好,然後把模板和資料(資料可能來自後臺)整合到一起形成HTML程式碼,然後把這段HTML程式碼插入到文件流裡面。 單向資料繫結缺點:HTML程式碼一旦生成完以後,就沒有辦法再變了,如果有新的資料來了,那就必須把之前的HTML

vue 2.0 資料雙向

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <script src="js/vue.js"></script

面試總結:vue實現資料雙向的原理

vue實現資料雙向繫結的原理就是用Object.defineproperty()重新定義(set方法)物件設定屬性值和(get方法)獲取屬性值的操縱來實現的 Object.property()方法的解釋:Object.property(引數1,引數2,引數3)   返回值為