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) 返回值為