接: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的方式,咱們首先用一種較簡單的方式去實現文本渲染。segmentfault
首先使用createDocumentFragment
把全部節點都剪貼到內存中,而後編譯內存中的文檔碎片。數組
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
方法:app
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,小弟跪謝~