實現一個簡單的MVVM(Compile)

codeXiu發表於2019-06-06

介紹

我覺得現在學習前端的人都知道MVVM是什麼意思,現在主流的框架React、Vue都使用的MVVM的原理,今天我們就來實現一下MVVM其中的一小部分Compie指令解析

  • compile就是解析的意思,解析我們使用的模板語法。我這篇文章主要是仿照實現Vue的Compile,

效果

  • Vue實現的效果
<div id="app">
    <input type="text" v-model="a">
    <p>{{ b.c }}</p>
    <div v-html="c"></div>
</div>
let vm = new Vue({
    el : "#app",
    data: {
        a: 1,
        b: {
            c: 1
        },
        c: "<p>165165</p>"
    }
});
複製程式碼

實現一個簡單的MVVM(Compile)

實現一個簡單的MVVM(Compile)

  • 我們模擬實現的效果
<div id="app">
    <ul>
        <input type="text" v-model="b.c">
        <li>{{ a }}</li>
        <li>
        	<span>9</span>
        </li>
        <div v-html="c"></div>
        <div v-html="<b>56156</b>"></div>
        {{ b.c }}
    </ul>	
</div>
let vm = new MVVM({
    el: "#app",
    data: {
    	a: 1,
    	b: {
    	    c: 2
    	},
    	c: "<p>56165</p>"
    }
});
複製程式碼

實現一個簡單的MVVM(Compile)
實現一個簡單的MVVM(Compile)

模擬實現

  • 我們自定義一個MVVM的類,來模擬new Vue的過程。
let vm = new MVVM({
    el: "#app",
    data: {
    	a: 1,
    	b: {
    	    c: 2
    	},
    	c: "<p>56165</p>"
    }
});
複製程式碼
  • MVVM
class MVVM {
    constructor(options) {
    	this.$el = options.el; // 掛載點
    	this.$data = options.data; // 資料
    	new Compile(this.$el, this.$data); // 解析模板
    }
}
複製程式碼
  • 解析模板
    • 檢視傳入的el是元素還是字串
      • 如果是字串,根據class或id查詢元素
      • 如果是元素直接使用
    • 如果元素存在,將元素加入到記憶體中,解析模板
class Compile {
    constructor(el, data) {
    	this.$el = this.isElement(el) ? el : document.querySelector(el);
    	this.$data = data;
        if (this.$el) {
            // 第一步:加入記憶體
            let fragment = this.toFragment(this.$el);
            // 第二步:解析指令
            this.compileDirect(fragment);
            // 第三步:加入到DOM中
            this.$el.appendChild(fragment);
        }
    }
}
複製程式碼

檢視是否是元素

isElement(node) {
    // 1代表是元素
    return node.nodeType === 1;
}
複製程式碼
  1. 加入到記憶體
toFragment(element) {
    // 建立文件碎片
    let fragment = document.createDocumentFragment();
    let firstChild;
    // 逐個獲取第一個元素加入到碎片中,直到文件中沒有子元素
    while (firstChild = element.firstChild) {
    	fragment.appendChild(firstChild);
    }
    return fragment;
}
複製程式碼

效果

實現一個簡單的MVVM(Compile)
元素都加入到了記憶體中,DOM中沒有子元素。

  1. 解析指令
  • 獲取子節點
    • 如果子節點是元素,解析元素,遞迴遍歷子元素。
    • 如果是文字,解析文字。
compileDirect(el) {
    let children = el.childNodes;
    [...children].forEach(child => {
        if (this.isElement(child)) {
            // 解析元素
            this.compileNode(child);
            // 遍歷子元素
            this.compileDirect(child);
        } else {
            // 解析文字
            this.compileText(child);
        }
    });
}
複製程式碼

2.1 建立工具類

compileUtil = {
    // v-text || 文字
    text(node, value) {
    	node.textContent = value;
    },
    // v-model
    model(node, value) {
    	node.value = value;
    },
    // v-html
    html(node, value) {
    	node.innerHTML = value;
    }
}
複製程式碼

2.2 解析文字

compileText(node) {
    // 獲取文字內容,{{ a }}
    let text = node.textContent;
    if (text) {
        // 正則匹配, 是否包含指令,{{ a }}
    	let value = text.match(/{{([^}])+}}/g);
    	if (value) {
    	    // 獲取 {{ a }} 裡面的資料: a
            value = value[0].replace(/{{([^}]+)}}/g, '$1').trim();
            compileUtil["text"](node, this.getValue(value));
    	}
    }
}
複製程式碼

2.2.1 獲取文字指令的值

  • 如果為級聯屬性,a.b.c需要一層一層的獲取值
getValue(value) {
    value = value.split(".");
    // 第一次為 this.$data["a"]
    // 第二次為 this.$data["a"]["b"]
    // 第三次為 this.$data["a"]["b"]["c"]
    return value.reduce((prevRes, next) => {
    	return prevRes[next];
    }, this.$data);
}
複製程式碼

2.3 解析元素屬性指令的值

  • 獲取元素的所有屬性
    實現一個簡單的MVVM(Compile)
  • 如果有遍歷每一個屬性
    實現一個簡單的MVVM(Compile)
  • 獲取到屬性的名字 如: class、v-model(這裡沒有實現v-bind以及縮寫的形式)
    實現一個簡單的MVVM(Compile)
  • 根據名字獲取對應處理的函式,獲取對應的值

實現一個簡單的MVVM(Compile)

compileNode(node, data) {
    let attrs = [...node.attributes];
    if (attrs.length > 0) {
        attrs.forEach(attr => {
            // v-model → model
            // v-text → text
            // v-html → html
            let type = attr.name.slice(2);
            // 如果有對應的函式則處理
            compileUtil[type] && compileUtil[type](node, this.getValue(attr.value) || attr.value);
        });
    }
}
複製程式碼
  1. 插入到文件中
this.$el.appendChild(fragment);
複製程式碼

實現一個簡單的MVVM(Compile)

總結

  • 簡單的實現了一個Compile的過程,知識是實現了一些簡單的指令解析,還沒有實現v-bind、v-on以及它們的縮寫形式:、@。
  • 重點再於理解,不只是知道和會用,還要學會靈活的運用以及知道他們簡單的原理並能加以實現。
  • 如果有興趣的可以看一下我的另兩篇文章

相關文章