接:javascript
在前面對數據進行inintState
以後,若是用戶配置了el
屬性,會經過調用$mount
方法,將數據渲染到頁面上,此時:java
Vue.prototype._init = function(options) {
// vue 中的初始化 this.$options 表示 Vue 中的參數
let vm = this;
vm.$options = options;
// MVVM 原理, 須要數據從新初始化
initState(vm);
+ if (vm.$options.el) {
+ vm.$mount();
+ }
}
複製代碼
此時的$mount
須要作兩件事:node
el
字段,獲取DOM元素,並將該元素掛載到vm.$el
字段上;Watcher
,去進行頁面渲染;function query(el) {
if (typeof el === 'string') {
return document.querySelector(el);
};
return el;
}
// 渲染頁面 將組件進行掛載
Vue.prototype.$mount = function () {
let vm = this;
let el = vm.$options.el; // 獲取元素
el = vm.$el = query(el); // 獲取當前掛載的節點 vm.$el 就是我要掛在的一個元素
// 渲染經過 watcher來渲染
let updateComponent = () => { // 更新、渲染的邏輯
vm._update(); // 更新組件
}
new Watcher(vm, updateComponent); // 渲染Watcher, 默認第一次會調用updateComponent
}
複製代碼
這裏會生成一個渲染Watcher
的實例。下面先簡單實現一下這個Watcher
類,在observe
目錄下新建watcher.js
:git
let id = 0; // Watcher 惟一標識
class Watcher { // 每次產生一個watch 都會有一個惟一的標識
/** * * @param {*} vm 當前逐漸的實例 new Vue * @param {*} exprOrFn 用戶可能傳入的一個表達式 也可能傳入一個函數 * @param {*} cb 用戶傳入的回調函數 vm.$watch('msg', cb) * @param {*} opts 一些其餘參數 */
constructor(vm, exprOrFn, cb = () => {}, opts = {}) {
this.vm = vm;
this.exprOrFn = exprOrFn;
if (typeof exprOrFn === 'function') {
this.getter = exprOrFn;
}
this.cb = cb;
this.opts = opts;
this.id = id++;
this.get();
}
get() {
this.getter(); // 讓傳入的函數執行
}
}
export default Watcher;
複製代碼
根據如今的Watcher
實現,新生成這個渲染Watcher
的實例,會默認去執行UpdateComponent
方法,也就是去執行vm._update
方法,下面咱們去看一下_update
方法的實現。github
_update
方法主要實現頁面更新,將編譯後的DOM插入到對應節點中,這裏咱們暫時先不引入虛擬DOM的方式,咱們首先用一種較簡單的方式去實現文本渲染。數組
首先使用createDocumentFragment
把全部節點都剪貼到內存中,而後編譯內存中的文檔碎片。app
Vue.prototype._update = function() {
let vm = this;
let el = vm.$el;
/** TODO 虛擬DOM重寫 */
// 匹配 {{}} 替換
let node = document.createDocumentFragment();
let firstChild;
while(firstChild = el.firstChild) {
node.appendChild(firstChild);
}
compiler(node, vm); // 編譯節點內容 匹配 {{}} 文本,替換爲變量的值
el.appendChild(node);
}
複製代碼
下面咱們去實現compiler
方法:函數
const defaultRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
export const util = {
getValue(vm, expr) { // school.name
let keys = expr.split('.');
return keys.reduce((memo, current) => {
memo = memo[current]; // 至關於 memo = vm.school.name
return memo;
}, vm);
},
/** * 編譯文本 替換{{}} */
compilerText(node, vm) {
node.textContent = node.textContent.replace(defaultRE, function(...args) {
return util.getValue(vm, args[1]);
});
}
}
/** * 文本編譯 */
export function compiler(node, vm) {
let childNodes = node.childNodes;
[...childNodes].forEach(child => { // 一種是元素一種是文本
if (child.nodeType == 1) { // 1表示元素
compiler(child, vm); // 若是子元素仍是非文本, 遞歸編譯當前元素的孩子節點
} else if (child.nodeType == 3) { // 3表示文本
util.compilerText(child, vm);
}
})
}
複製代碼
好了到如今咱們的節點編譯方法也實現了,咱們去看下頁面效果,將 index.html
修改成Vue
模板的形式:
<div id="app">
{{msg}}
<div>
<p>學校名字 {{school.name}}</p>
<p>學校年齡 {{school.age}}</p>
</div>
<div>{{arr}}</div>
</div>
複製代碼
能夠看到頁面展現:
說明咱們的變量被正常渲染到頁面上了,可是咱們去修改變量的值,發現頁面不能正常更新,別急,下一部分咱們去搞定依賴收集去更新視圖。
代碼部分可看本次提交commit
但願各位老闆點個star,小弟跪謝~