手摸手從0實現簡版Vue --- (模板編譯)

接:javascript

手摸手從0實現簡版Vue --- (對象劫持)html

手摸手從0實現簡版Vue --- (數組劫持)vue

1. 若是有用戶傳入了el,去調用$mount方法

在前面對數據進行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

此時的$mount須要作兩件事:node

  1. 經過用戶配置的el字段,獲取DOM元素,並將該元素掛載到vm.$el字段上;
  2. 經過實例化一個渲染 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.jsgit

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

2. _update

_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方法:函數

3. 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>
複製代碼

能夠看到頁面展現:

image-20200307144823192

說明咱們的變量被正常渲染到頁面上了,可是咱們去修改變量的值,發現頁面不能正常更新,別急,下一部分咱們去搞定依賴收集去更新視圖。

代碼部分可看本次提交commit

但願各位老闆點個star,小弟跪謝~

相關文章
相關標籤/搜索