對於 Vue 的工做過程,咱們能夠從下面這張圖中獲得一點思路。javascript
咱們能夠從兩個方面來解析 Vue 的工做過程:初始化階段、數據修改階段。html
在 Vue 初始化階段,咱們建立了一個 Vue 實例並將其掛載在了頁面上:vue
init()
方法。它作了什麼事情呢?它將傳入的props、事件、data等都作了初始化。$mount()
方法,實現了 Vue 實例的掛載。這個$mount()
方法,最主要作的事情是什麼呢?它經過調用 render()
函數生成了 virtual DOM,即虛擬DOM樹。 render()
函數在執行的時候,會touch
一下 對應屬性的getter
,這一步即爲觸發getter
進行依賴收集的過程。patch()
方法生成真實DOM,掛載在頁面上。在數據修改階段:java
setter
。patch()
方法,對比新舊 virtual DOM,獲得頁面的最小修改,執行頁面刷新。我想要試試本身實現一個簡單的 Vue。它將會是怎樣的呢:node
{{}}
。文件會有五個:算法
首先,給出做爲測試用的 index.html:數組
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<div id="app">
{{test}}
<p k-text="test"></p>
<p k-html="html"></p>
<p>
<input type="text" k-model="test">
</p>
<p>
<button @click="onClick">按鈕</button>
</p>
</div>
<script src="fvue.js"></script>
<script src="fcompile.js"></script>
<script src="watcher.js"></script>
<script src="dep.js"></script>
<script> const fVue = new FVue({ el: "#app", data: { test: "hello, frank", foo: { bar: "bar" }, html: '<button>adfadsf</button>' }, methods: { onClick() { alert('blabla') } }, }); //模擬數據修改 setTimeout(function(){ fVue.$data.test = "hello,fVue!"; console.log("setTimeout : ",fVue.$data.test); }, 2000); </script>
</body>
</html>
複製代碼
爲了驗證想法,寫了這四個文件。代碼儘可能簡單。app
//fvue.js
class FVue {
constructor(options){
this.$data = options.data;
this.$options = options;
//數據響應化
this.observe(this.$data);
//解析頁面模板
new Compile(options.el, this);
}
observe(value){
if(!value || typeof value !== 'object'){
return;
}
Object.keys(value).forEach(key =>{
this.defineReactive(value, key, value[key]);
// 爲vue的data作屬性代理:this.xxx = this.$data.xxx
this.proxyData(key);
})
}
defineReactive(obj, key, val){
//遞歸
this.observe(val);
//每個 key 都有一個的Dep與之對應
const dep = new Dep();
Object.defineProperty(obj, key, {
get(){
//依賴收集
Dep.target && dep.addDep(Dep.target)
return val;
},
set(newVal){
if(newVal === val) return;
val = newVal;
//執行更新操做
dep.notify();
}
})
}
proxyData(key) {
Object.defineProperty(this, key, {
get(){
return this.$data[key];
},
set(newVal){
this.$data[key] = newVal;
},
});
}
}
複製代碼
fvue.js 核心文件實現了 observe 邏輯:即在初始化過程當中,將傳入的data
屬性作了初始化處理,經過 defineReactive()
方法將data
中每一個屬性都作了數據攔截,從新定義了每一個屬性的getter
與setter
。更詳細的:函數
每個屬性都有本身專有的調度模塊 Dep。測試
在getter
中,定義了依賴收集的方式(只要有對應的 Watcher 觸發了 getter 方法,那麼將其放入到 Dep 的數組裏)。
在setter
中,定義了響應數據變化的方法(只要對應的setter方法被觸發,那麼該 Dep 就會執行通知操做,讓對應的 Watcher 執行更新)。
再來看 dep.js 與 watcher.js。
//dep.js
class Dep {
constructor(){
this.deps = []
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach(dep => dep.update())
}
}
//watcher.js
class Watcher{
constructor(vm, key, cb){
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; //將當前Watcher實例附加到Dep的靜態屬性上
this.vm[this.key]; //主動觸發 getter 屬性,觸發依賴收集
Dep.target = null; //解除 Dep.target 這個靜態變量的鎖定
}
update(){
this.cb.call(this.vm, this.vm[this.key]);
}
}
複製代碼
咱們將 Dep 當作是一個調度模塊,它只負責管理更新。而 Watcher 至關因而一個執行人,它負責執行具體的更新過程。
咱們看到,在 Watcher 初始化的過程當中,咱們主動觸發了 getter
屬性,觸發了依賴收集的過程。可是,尚未看到 Watcher 在哪裏被初始化的。其實,在 解析 HTML 模板的過程當中,當咱們發現了自定義變量時,就會觸發 Watcher 的初始化。
爲了簡化,驗證可行性。此時咱們的 fcompile.js 會寫得很是簡單,只處理文本自定義變量的狀況(在例子中是{{test}}
)。
class Compile {
//el是宿主元素或者選擇器
//vm 是vue實例
constructor(el, vm){
this.$vm = vm;
this.$el = document.querySelector(el); // 簡化:經過選擇器來獲取到文檔元素
this.compile(this.$el);
}
compile(el){
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if(this.isTextParam(node)){
this.compileText(node);
}
//遞歸
this.compile(node);
})
}
isTextParam(node){
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileText(node){
let key = RegExp.$1;
let currentValue = this.$vm[key];
//解析後,須要將真實值掛載到真實頁面上
this.textUpdate(node, currentValue)
//建立新的 Watcher 實例
new Watcher(this.$vm, key, (newValue)=>{
this.textUpdate(node, newValue)
})
}
textUpdate(node, value){
node.textContent = value;
}
}
複製代碼
Compile 是在 FVue 中調用的。它的工做是最爲繁重的:
固然,爲了簡單起見,此處的 Compile 只處理了一個最簡單的狀況:文本自定義變量 ({{test}}) 的狀況。一個完善的 compile 函數會很是周密且複雜,可查看 Vue 源碼。
將代碼放在一塊兒,它們是能夠運轉的。頁面上的展現變量在定時器時間事後,會發生改變。
在文章最後,讓咱們來捋一捋整個 Vue 工做的過程:
整個過程能夠看作是 Vue 1.x 的工做方式極端簡易版本,雖然與 Vue 2.x 不一樣,但但願不會影響各位讀者對 Vue 的理解。