深入Vue實現原理,實現一個響應式框架
在前面的章節中我們已經學習了Vue.js
的基礎內容並且瞭解了Vue.js
的原始碼實現,包括:Vue的生命週期、Vue的資料響應、Vue的渲染流程等等,在這一章節我們會和大家一起去實現一個響應式的框架 – MVue
,MVue
會遵循Vue
的程式碼邏輯和實現思路,我們希望能夠藉助MVue
來讓大家更好的理解整個Vue
的核心思想:響應式資料渲染。
在開始我們的MVue
開發之前,我們需要先了解一些必備的知識。首先是Object.defineProperty(obj, prop, descriptor)
,這個方法可以用來定義物件的屬性描述符
get、set
描述符,我們可以使用get、set
來監聽物件的屬性setter、getter
的呼叫事件。我們看一下下面的這段程式碼:
<div>
<input type="text" id="input-msg">
<p id="output-msg"></p>
</div>
<script>
var obj = {
msg: 'hello'
};
var key = 'msg';
var val = obj[key];
Object .defineProperty(obj, key, {
enumerable: true,
configurable: true,
set: function (newValue) {
val = newValue;
console.log('setter');
},
get: function () {
console.log('getter');
return val;
}
})
</script >
在上面的程式碼中我們利用Object.defineProperty
監聽了obj.msg
的setter、getter
事件。所有當我們在控制檯去呼叫obj.msg
的時候就會呼叫console.log('getter');
,當我們呼叫obj.msg = '123'
的時候就會呼叫console.log('setter');
,由此我們就成功的監聽了obj.msg
的資料變化。那麼這有什麼意義呢?我們可以利用這個功能來做些什麼呢?我們看一下下面的程式碼:
<div>
<input type="text" id="input-msg">
<p id="output-msg"></p>
</div>
<script>
var inputMsg = document.getElementById('input-msg'),
outputMsg = document.getElementById('output-msg');
var obj = {
msg: 'hello'
};
var key = 'msg';
var val = obj[key];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
set: function (newValue) {
val = newValue;
outputMsg.innerText = obj[key];
},
get: function () {
console.log('getter');
return val;
}
});
inputMsg.addEventListener('input', function (event) {
var newVal = event.target.value;
obj[key] = newVal;
});
</script>
在上面的程式碼中,我們通過監聽input
的input事件
來去改變obj[key]
的值,使obj[key]
的值始終等於使用者輸入的值,當obj[key]
的值因為使用者的輸入而發生了改變的時候,會啟用Object.defineProperty
中的setter
事件,然後我們獲取到最新的obj[key]
的值並把它賦值給outputMsg
。這樣當我們在input
中進行輸入的時候,<p>
中的值也會跟隨我們的輸入變化。這種通過Object.defineProperty
來監聽資料變化的方式就是Vue
中資料響應的核心思想。
其次大家需要了解的就是觀察者模式,大家可以點選這裡來檢視觀察者模式的詳細解釋,相信這裡會比我解釋的更加清楚。
當大家瞭解完觀察者模式之後我們就可以正式開始我們的MVue
的開發工作。
思路整理
整個框架的思路被分成三大塊。
首先就是檢視渲染,我們在html
或者<template></template>
中進行html
內容編寫的時候,往往是這樣:
<div id="app">
<input type="text" v-model='msg'>
<div>
<p>{{msg}}</p>
</div>
</div>
其中的v-model='msg'
和 {{msg}}
瀏覽器是無法解析的,那麼我們就需要把 瀏覽器不認識的內容轉化為瀏覽器可以解析的內容,在Vue
中,Vue
通過虛擬DOM(VNode
)描述真實DOM
,然後通過_update
來進行具體渲染。我們這裡不去描述這個VNode
直接通過_update
方法來對DOM
進行渲染操作,這個動作是發生在Compile
中。Compile
會解析我們的具體指令,並重新渲染DOM
。
其次是監聽我們的資料變化,在最初的例子中我們已經知道我們可以通過Object.defineProperty(obj, prop, descriptor)
來實現資料的監聽,那麼就需要一個Observer
類來進行資料劫持的工作,這時Observer
承擔的就是釋出者的工作。當我們通過Observer
來監聽到資料變化之後,我們需要通知我們的觀察者,但是對於我們的釋出者來說,它並不知道誰是這個觀察者,這個觀察者是一個還是多個?所以這個時候,就需要有一個人來負責去收集這些依賴的工作,這個人就是Dep(Dependency)
,我們通過Dep
來去通知觀察者Watcher
,Watcher
訂閱Dep
,Dep
持有Watcher
,兩者互相依賴形成一個訊息中轉站。當Watcher
接收到訊息,需要更改檢視的時候,那麼就會發布具體的訊息根據具體指令的不同(Directive
)來執行具體的操作Patch
。這就是我們的整個從監聽到渲染的過程,如下圖:
最後我們需要把所有的東西整合起來形成一個入口函式,輸出給使用者方便使用者進行呼叫,就好像Vue
中的new Vue({})
操作,這裡我們叫它MVue
。
綜合以上的內容,我們需要完成的程式碼內容包括
├── compile.js 渲染DOM,解析指令
├── dep.js 收集依賴
├── directive.js 所有支援到的指令
├── mvue.js 入口函式
├── observer.js 資料劫持
├── patch.js 根據具體的指令來修改渲染的內容
└── watcher.js 觀察者。訂閱Dep,釋出訊息
我們預期的完成效果應該是這樣
<div id="app">
<input type="text" v-model='msg'>
<div>
<p>{{msg}}</p>
</div>
</div>
<script>
var vm = new MVue({
el: '#app',
data: {
msg: 'hello'
}
});
</script>
入口函式
首先我們需要先生成MVue
的入口函式,我們仿照Vue
的寫法,建立一個MVue
的類,並獲取傳入的options
。
function MVue (options) {
this.$options = options;
this._data = options.data || {};
}
MVue.prototype = {
_getVal: function (exp) {
return this._data[exp];
},
_setVal: function (exp, newVal) {
this._data[exp] = newVal;
}
}
首先我們實現一個MVue
的建構函式,併為它提供了兩個私有的原型方法_getVal
和_setVal
用於獲取和設定data
中對應key
的值。這時我們就可以通過下面的程式碼來建立對應的MVue
例項。
var vm = new MVue({
el: '#app',
data: {
msg: 'hello'
}
});
然後我們就可以在MVue
的建構函式之中去進行我們的 檢視渲染 和 資料監聽 的操作。
檢視渲染
然後我們進行我們的檢視渲染,我們再來回顧一下我們需要解析的檢視結構
<div id="app">
<input type="text" v-model='msg'>
<div>
<p>{{msg}}</p>
</div>
</div>
在這段html
之中v-model
和{{msg}}
是我們MVue
中的自定義指令,這些指令我們的瀏覽器是無法解析的,所以需要我們把這些指令解析為瀏覽器可以解析的html
程式碼。以<p>{{msg}}</p>
為例,當我們宣告data: {msg: 'hello'}
的時候,應解析為<p>hello</p>
。
我們的模板解析的操作是通過compile.js
來完成的。
function Compile (vm, el) {
this.$vm = vm;
el = this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (!el) {
return;
}
this._update(el);
};
Compile.prototype = {
/**
* Vue中使用vm._render先根據真實DOM建立了虛擬DOM,然後在vm._update把虛擬DOM轉化為真實DOM並渲染,
* 我們這裡沒有虛擬DOM,所以直接通過createElm方法建立一個fragment用以渲染
*/
_update: function (el) {
this.$fragment = document.createDocumentFragment();
// 複製el的內容到建立的fragment
this.createElm(el);
// 把解析之後的fragment放入el中,此時fragment中的所有指令已經被解析為具體資料
el.appendChild(this.$fragment);
},
/**
* 建立新的DOM 用來替換 原DOM
*/
createElm: function (node) {
var childNode = node.firstChild;
if (childNode) {
this.$fragment.appendChild(childNode);
this.createElm(node);
}
}
}
我們聲明瞭一個Compile
的構造方法,並呼叫了它的_update
原型函式,在_update
中我們聲明瞭一個fragment
用於承載解析之後的模板內容,通過createElm
的遞迴呼叫獲取el
中的元素,並把獲取出的元素放入fragment
中,最後把fragment
新增到el
裡面。至此我們已經成功的獲取到了el
中的元素,並把這些元素重新規制。
接下來我們就需要對獲取出來的元素進行解析操作,其實就是對v-model
和{{*}}
等指令進行解析,這個解析的時機應該在 遍歷出所有的元素之後,新增fragment
到el
之前。我們看一下解析DOM
的程式碼:
Compile.prototype = {
_update: function (el) {
...
// 解析被建立完成的fragment,此時fragment已經擁有了el內所有的元素
this.compileElm();
...
},
...
/**
* 對DOM進行解析
*/
compileElm: function (childNodes) {
var reg = /\{\{(.*)\}\}/;
if (!childNodes) {
childNodes = this.$fragment.childNodes;
}
[].slice.call(childNodes).forEach(node => {
if (node.childNodes.length > 0) {
// 迭代所有的節點
this.compileElm(node.childNodes);
}
// 獲取elementNode節點
if (this.isElementNode(node)) {
if (reg.test(node.textContent)) {
// 匹配 {{*}}
this.compileTextNode(node, RegExp.$1);
}
// 匹配elementNode
this.compileElmNode(node);
}
});
},
/**
* 解析elementNode,獲取elm的所有屬性然後便利,檢查屬性是否屬於已經註冊的指令,
* 如果不是我們的自定義指令,那麼就不需要去處理它了
* 如果是已註冊的指令,我們就交給directive去處理。(演示只有一個v-model)
*/
compileElmNode: function (node) {
var attrs = [].slice.call(node.attributes),
$this = this;
attrs.forEach(function (attr) {
if (!$this.isDirective(attr.nodeName)) {
return;
}
var exp = attr.value;
// 匹配v-model指令
directives.model($this.$vm, node, exp);
// 去掉自定義指令
node.removeAttribute(attr.name);
});
},
/**
* 解析{{*}}
*/
compileTextNode: function (node, exp) {
directives.text(this.$vm, node, exp);
},
/**
* 判斷是否是已註冊的指令,這裡就判斷是否包含 v-
*/
isDirective: function (attrNodeName) {
return attrNodeName.indexOf('v-') === 0;
},
/**
* 判斷elmNode節點
*/
isElementNode: function (node) {
return node.nodeType === 1;
}
}
由上面的程式碼可以看出,解析的操作主要在compileElm
方法中進行,這個方法首先獲取到fragment
的childNodes
,然後對childNodes
進行了forEach
操作,如果其中的node
還有子節點的話,則會再次呼叫compileElm
方法,然後解析這個node
,如果是一個ElementNode
節點,則再去判斷是否為{{*}}
雙大括號結構,如果是則會執行compileTextNode
來解析{{*}}
,然後通過compileElmNode
來解析ElmNode
中的指令。
compileTextNode
中的實現比較簡單,主要是呼叫了directives.text(vm, node, exp)
進行解析,這裡我們稍後再看,我們先主要來看下compileElmNode
做了什麼。
compileElmNode
首先把node
中所有的屬性轉成了陣列並拷貝給了attrs
,然後對attrs
進行遍歷獲取其中的指令
,因為我們目前只有一個v-model
指令,所以我們不需要在對指令進行判斷,可以直接呼叫directives.model(vm, node, exp)
來進行v-model
的指令解析,最後在DOM
中刪除我們的自定義指令。
至此我們就複製了el
的所有元素,並根據不同的指令把它們交由directives
中對應的指令解析方法進行解析,這就是我們compile.js
中所做的所有事情。接下來我們看一下directives
是如何進行指令解析操作的,程式碼如下:
// directives.js
/**
* 指令集和
*
* v-model
*/
var directives = {
/**
* 連結patch方法,將指令轉化為真實的資料並展示
*/
_link: function (vm, node, exp, dir) {
var patchFn = patch(vm, node, exp, dir);
patchFn && patchFn(node, vm._getVal(exp));
},
/**
* v-model事件處理,這裡的v-model只針對了<input type='text'>
*/
model: function (vm, node, exp) {
this._link(vm, node, exp, 'model');
var val = vm._getVal(exp);
node.addEventListener('input', function (e) {
var newVal = e.target.value;
if (newVal === val) return;
vm._setVal(exp,newVal);
val = newVal;
});
},
/**
* {{}}事件處理
*/
text: function (vm, node, exp) {
this._link(vm, node, exp, 'text');
}
}
由上面的程式碼我們可以看出,我們首先定義了一個directives
變數,它包含了_link、model、text
三個指令方法,其中_link
為私有方法,model、text
為公開的指令方法,關於_link
我們最後在分析,我們先來看一下model
。
model
指令方法對應的為v-model
指令,它接受三個引數,vm
為我們的MVue
例項,node
為繫結該指令的對應節點,exp
為繫結資料的key。我們先不去管this._link
的呼叫,大家先來想一下我們在index.html
中對於v-model
的使用,我們把v-model='msg'
繫結到了我們的input
標籤上,意為當我們在input
上進行輸入的時候msg
始終等於我們輸入的值。那麼我們在model
指令方法中所要做的事情就很明確了,首先我們通過vm._getVal(exp);
獲取到msg
當前值,然後我們監聽了node
的input
事件,獲取當前使用者輸入的最新值,然後通過vm._setVal(exp,newVal)
配置到vm._data
中,最後通過val = newVal
重新設定val
的值。
然後是text
指令方法,這個方法直接呼叫了this._link
,並且我們還記得在model
指令方法中也呼叫了this._link
,那麼我們來看一下_link
的實現。
在_link
中,他接收四個引數,其中dir
為我們的指令程式碼,然後它呼叫了一個patch
方法,獲取到了一個patchFn
的變數,這個patch
方法位於patch.js
中。
// patch.js
/**
* 更改node value,在編譯之前,替換 v-model {{*}} 為真實資料
* @param {*} vm
* @param {*} node
* @param {*} exp
* @param {*} dir
*/
function patch (vm, node, exp, dir) {
switch (dir) {
case 'model':
/**
* input / textear
*/
return function (node , val) {
node.value = typeof val === 'undefined' ? '' : val;
}
case 'text':
/**
* {{*}}
*/
return function (node , val) {
node.textContent = typeof val === 'undefined' ? '' : val;
}
}
}
patch
的方法實現比較簡單,它首先去判斷了傳入的指令,然後根據不同的指令返回了不同的函式。比如在model
指令方法中,因為我們只支援input、 textear
,所以我們接收到的node
只會是它們兩個中的一個,然後我們通過node.value = val
來改變node中的value
。
我們在directives.js
中獲取到了patch
的返回函式patchFn
,然後執行patchFn
。至此我們的模板已經被解析為瀏覽器可以讀懂的html
程式碼。
<div id="app">
<input type="text">
<div>
<p>hello</p>
</div>
</div>
資料監聽實現
然後我們來看一下 資料監聽模組的實現 ,我們根據上面的 思路整理 想一下這個資料監聽應該如何去實現?我們知道了我們應該在observer
裡面去實現它,但是具體應該怎麼做呢?
再來明確一下我們的目標,我們希望 通過observer
能夠監聽到我們資料data
的變化,當我們呼叫data.msg
或者data.msg = '123'
的時候,會分別啟用getter
或者setter
方法。那麼我們就需要對整個data
進行監聽,當我們獲取到data
物件之後,來遍歷其中的所有資料,並分別為它們新增上getter
和setter
方法。
// observer.js
function observer (value) {
if (typeof value !== 'object') {
return;
}
var ob = new Observer(value);
}
function Observer (data) {
this.data = data;
this.walk();
}
Observer.prototype = {
walk: function () {
var $this = this;
var keys = Object.keys(this.data);
keys.forEach(function (key) {
$this.defineReactive(key, $this.data[key]);
});
},
defineReactive: function (key, value) {
var dep = new Dep();
Object.defineProperty(this.data, key, {
enumerable: true,
configurable: true,
set: function (newValue) {
if (value === newValue) {
return;
}
value = newValue;
dep.notify();
},
get: function () {
dep.depend();
return value;
}
});
},
}
在observer.js
中我們通過observer (value)
方法來生成Observer
物件,其中傳入的value
為data: {msg: 'hello'}
。然後呼叫Observer
的原型方法walk
,遍歷data
呼叫defineReactive
,通過Object.defineProperty
為每條資料都新增上setter、getter
監聽,同時我們聲明瞭一個Dep
物件,這個Dep
物件會負責收集依賴並且派發更新。大家結合我們的思路整理想一下,我們應該在什麼時候去收集依賴?什麼時候去派發更新?
當用戶通過input
進行輸入修改資料的時候,我們是不是應該及時更新檢視?所以在setter
方法被啟用的時候,我們應該呼叫dep.notify()
方法,用於派發更新事件。
當我們的資料被展示出來的時候,也就是在getter
事件被啟用的時候,我們應該去收集依賴,也就是呼叫dep.depend()
方法。
然後我們來看一下Dep
方法的實現,在Dep.js
中。
// Dep.js
var uid = 0;
function Dep () {
// 持有的watcher訂閱者
this.subs = [];
this.id = uid++;
}
Dep.prototype = {
// 使dep與watcher互相持有
depend () {
// Dep.target為watcher例項
if (Dep.target) {
Dep.target.addDep(this)
}
},
// 新增watcher
addSub: function (sub) {
this.subs.push(sub);
},
// 通知所有的watcher進行更新
notify: function () {
this.subs && this.subs.forEach(function (sub) {
sub.update();
});
}
}
Dep.js
的實現比較簡單,它主要是就負責收集依賴(watcher
)並且派發更新(watcher.update()
),我們可以看到Dep
首先聲明瞭subs
用於儲存訂閱了Dep
的watcher
例項,然後給每個Dep
例項建立了一個id
,然後我們為Dep
聲明瞭三個原型方法,當呼叫notify
的時候,Dep
回去遍歷所有的subs
然後呼叫他的update()
方法,當呼叫depend
的時候會呼叫watcher
的addDep
方法使Dep
與Watcher
互相持有。其中的Dep.target
和sub
都為Watcher
例項。
然後我們來看一下Watcher.js
的程式碼實現。
// watcher
function Watcher (vm, exp, patchFn) {
this.depIds = {};
this.$patchFn = patchFn;
this.$vm = vm;
this.getter = this.parsePath(exp)
this.value = this.get();
}
Watcher.prototype = {
// 更新
update: function () {
this.run();
},
// 執行更新操作
run: function () {
var oldVal = this.value;
var newVal = this.get();
if (oldVal === newVal) {
return;
}
this.$patchFn.call(this.$vm, newVal);
},
// 訂閱Dep
addDep: function (dep) {
if (this.depIds.hasOwnProperty(dep.id)) {
return;
}
dep.addSub(this);
this.depIds[dep.id] = dep;
},
// 獲取exp對應值,這時會啟用observer中的get事件
get: function () {
Dep.target = this;
var value = this.getter.call(this.$vm, this.$vm._data);
Dep.target = null;
return value;
},
/**
* 獲取exp的對應值,應對a.b.c
*/
parsePath: function (path) {
var segments = path.split('.');
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
}
在Watcher.js
中它直接接收了patchFn
,大家還記得這個方法是幹什麼的吧?patchFn
是更改node value
,在編譯之前,替換 v-model 、 {{*}}
為真實資料的方法,在Watcher.js
接收了patchFn
,並把它賦值給this.$patchFn
,當我們呼叫this.$patchFn
的時候,就會改變我們的DOM
渲染。
然後我們呼叫parsePath
用於解析物件資料,並返回一個解析函式,然後把它賦值給this.getter
。最後我們呼叫get()
方法,在get()
中我們給Dep.target
持有了Watcher
,並激活了一次getter
方法,使我們在observer
中監聽的getter
事件被啟用,會呼叫dep.depend()
方法,然後呼叫watcher.addDep(dep)
,使Dep
與Watcher
互相持有,相互依賴。
然後我們看一下update
方法的實現,我們知道當資料的setter
事件被啟用的時候,會呼叫dep.notify()
,dep.notify()
又會遍歷所有的訂閱watcher
執行update
方法,那麼在upadte
方法中,直接執行了this.run
,在run()
方法中,首先獲取了 當前watcher
所觀察的exp
的改變前值oldVal
和修改後值newVal
,然後通過patchFn
去修改DOM
。
以上就是我們整個資料監聽的流程,它首先通過observer
來監聽資料的變化,然後當資料的getter
事件被啟用的時候,呼叫dep.depend()
來進行依賴收集,當資料的setter
事件被啟用的時候,呼叫dep.notify()
來進行派發更新,這些的具體操作都是在我們的觀察者watcher
中完成的。
整合MVue
最後我們就需要把我們的 檢視渲染 和 資料監聽 連結起來,那麼這個連線的節點應該在哪裡呢?我們再來捋一下我們的流程。
當用戶編寫了我們的指令程式碼
<div id="app">
<inputtype="text" v-model='msg'>
<div>
<p>{{msg}}</p>
</div>
</div>
的時候,我們通過Compile
進行解析,當發現了我們的自定義指令v-model、{{*}}
的時候,會進行directives
進行指令解析,其中監聽的使用者的輸入事件,並呼叫了vm._setVal()
方法,從而會啟用在observer
中定義的setter
事件,setter
會進行派發更新的操作,呼叫dep.notify()
方法,然後便利subs
呼叫update
方法。
結合上面的描述,我們應該在兩個地方去完成連線節點。首先是在呼叫vm._setVal()
方法的時候,我們需要保證observer
中的setter
事件可以被啟用,那麼我們最好在入口函式中去宣告這個observer
:
function MVue (options) {
this.$options = options;
this._data = options.data || {};
observer(this._data);
new Compile(this, this.$options.el);
}
MVue.prototype = {
_getVal: function (exp) {
return this._data[exp];
},
_setVal: function (exp, newVal) {
this._data[exp] = newVal;
}
}
然後當setter
事件被啟用之前,我們需要初始化完成watcher
使其擁有vm、exp、patchFn
,那麼最好的時機應該在獲取到patchFn
這個返回函式的時候,所以應該在:
var directives = {
_bind: function (vm, exp, patchFn) {
new Watcher(vm,exp, patchFn);
},
/**
* 連結patch方法,將指令轉化為真實的資料並展示
*/
_link: function (vm, node, exp, dir) {
var patchFn = patch(vm, node, exp, dir);
patchFn && patchFn(node, vm._getVal(exp));
this._bind(vm, exp, function (value) {
patchFn && patchFn(node, value);
});
},
...
}
通過_bind
方法來去初始化watcher
。
使用與擴充套件
至此我們的MVue
框架就已經被開發完成了,我們可以點選這裡來獲取本課程中所有的程式碼,當我們需要使用我們的MVue
的時候,我們可以這麼做:
<script src="./mvue3/patch.js"></script>
<script src="./mvue3/dep.js"></script>
<script src="./mvue3/directive.js"></script>
<script src="./mvue3/watcher.js"></script>
<script src="./mvue3/observer.js"></script>
<script src="./mvue3/compile.js"></script>
<script src="./mvue3/mvue.js"></script>
<div id="app">
<input type="text" v-model='msg'>
<div>
<p>{{msg}}</p>
</div>
</div>
<script>
var vm = new MVue({
el: '#app',
data: {
msg: 'hello'
}
});
</script>
因為我們的MVue
並沒有進行模組化,所以需要把所有的JS
全部引入才能使用。大家也可以嘗試一下把MVue
進行模組化,這樣就可以只通過引入<script src="./mvue3/mvue.js"></script>
來使用MVue
了。
現在我們的MVue
還非常的簡單,大家可以想一下如何為我們的MVue
增加更多的功能,比如說更多的指令或者新增v-on:click
的事件處理?這裡給大家留下三個問題,目的是希望大家能夠親自寫一下這個專案,可能會讓大家有更多的理解。
1、實現
v-show
指令
2、實現v-on:click
事件監聽
3、如何和Vue
一樣可以直接通過this.msg
來獲取我們在data
中定義的資料
這三個問題的解決方案都在我們的程式碼中,大家可以作為參考。
這一章為Vue.js
的最後一章,從下一章開始我們就會進入Vue
周邊生態的學習,希望大家一定要親自實現一下MVue
的程式碼。
前端技術日新月異,每一種新的思想出現,都代表了一種技術的躍進、架構的變化,那麼對於目前的前端技術而言,MVVM 的思想已經可以代表當今前端領域的前沿思想理念,Angular、React、Vue 等基於 MVVM 思想的具體實現框架,也成為了人們爭相學習的一個熱點。而 Vue 作為其中唯一沒有大公司支援但卻能與它們並駕齊驅並且隱隱有超越同類的趨勢,不得不說這種增長讓人感到驚奇。
本系列課程內容將會帶領大家由淺入深的學習 Vue 的基礎知識,瞭解 Vue 的原始碼設計和實現原理,和大家一起看一下尤雨溪先生的程式設計思想、架構設計以及如何進行程式碼實現。本系列課程內容主要分為三大部分:
Vue
的基礎知識:在這一部分將學習 Vue 的基礎語法及其原始碼的實現。例如,Vue
的生命週期鉤子如何設計?當聲明瞭一個directive
時,Vue
究竟執行了什麼?為什麼只有通過vue.set
函式才能為響應式物件新增響應式屬性?如果我們自己要實現一個響應式的框架的話,應該如何下手、如何思考等。
Vue
的周邊生態:在這一部分將學習Vue
的周邊生態圈,包括有哪些UI
庫可以和Vue
配合快速構建介面、如何使用vue-router
構建前端路由、如何使用Vuex
進行狀態管理、如何使用Axios
進行網路請求、如何使用Webpack
、使用vue-cli
構建出的專案裡的各種配置有什麼意義?
專案實戰:在這一部分將會通過一個有意思的自動對話系統來進行專案實戰,爭取通過這個小專案把學到的知識點進行一個整合。