手寫MVVM框架 之vue雙向數據綁定原理剖析


<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>自定義MVVM框架,這是比較牛逼的v-text,v-model和數據綁定原理介紹</title>
    </head>

    <body>
        <div id="app">
            <p v-text="message"></p>
            <input type="text" v-model="message"/>
            <p>{{message}}</p>
        </div>
        <script src="observer.js"></script>
        <script src="TemepCompiler.js"></script>
        <script src="watcher.js"></script>
        <script src="mvvm.js"></script>
        <script>
            var vm = new MVVM({ el: "#app", //掛載視圖
 data: { msg:'這是自己寫的mvvm框架', message:'mvvm出來吧' } //定義數據
 }) </script>
    </body>

</html>
//創建一個MVVM框架類
class MVVM { //類構造器(創造實例模板代碼)
    constructor(options) { //相當於函數的參數 {el:...,data:...}
        //緩存重要屬性
        this.$vm = this; this.$el = options.el; this.$data = options.data; //判斷視圖是否存在
        if(this.$el) { //添加屬性觀察對象(實現屬性挾持)
            new Observer(this.$data); //創建模板編譯器,來解析視圖
            this.$compiler = new TemepCompiler(this.$el, this.$vm); } } }
class TemepCompiler { //類構造器(創造實例模板代碼)
    constructor(el, vm) { //相當於函數的參數 (this.$el,this.$vm)
        //緩存重要屬性
        this.vm = vm; this.el = this.isElemetNode(el) ? el : document.querySelector(el); //判斷視圖是否存在
        if(this.el) { //1.把模板內容放入內存(片段)
            var fragment = this.node2fragment(this.el); //2.解析模板
            this.compile(fragment); //3.把內存的結果返回頁面
            this.el.appendChild(fragment); } } //工具方法
 isElemetNode(node) { return node.nodeType === 1 //1.元素節點 2.屬性節點 3.文本節點
 } isTextNode(node) { return node.nodeType === 3 //1.元素節點 2.屬性節點 3.文本節點
 } toArray(arr) { return [].slice.call(arr); //假數組轉為數組;
 } isDerective(attrName) { return attrName.indexOf("v-") >= 0; //判斷屬性名中是否有v-開頭的屬性
 } //核心方法(節省內存)把模板放入內存,等待解析
 node2fragment(node) { //創建內存片段
        var fragment = document.createDocumentFragment(), child; //模板內容丟到內存
        while(child = node.firstChild) { fragment.appendChild(child); } //返回
        return fragment; } compile(parentNode) { //獲取子節點
        var childNodes = parentNode.childNodes, //類數組
            compiler = this; //遍歷每一個節點
        this.toArray(childNodes).forEach((node) => { //判斷節點類型
            if(compiler.isElemetNode(node)) { //1.屬性節點(解析指令)
 compiler.compileElement(node); } else { //2.文本節點(解析指令)
                var textReg = /\{\{(.+)\}\}/; //(\轉義)文本表達式驗證規則
                var expr = node.textContent; //var expr = node.innerText; //谷歌支持
                //按照規則驗證內容
                if(textReg.test(expr)) { expr = RegExp.$1 //緩存最近一次的正則里面的值;
                    //調用方法編譯
                    compiler.compileText(node, expr); //如果還有子節點,繼續解析;
 } } }); } //解析元素節點的指令的方法
 compileElement(node) { //獲取當前元素節點的所有屬性
        var arrs = node.attributes; self = this; //遍歷當前元素所有屬性
        this.toArray(arrs).forEach(attr => { var attrName = attr.name; //判斷屬性是否是指令
            if(self.isDerective(attrName)) { var type = attrName.substr(2); //v-text,v-model...
                var expr = attr.value; //指令的值就是表達式
                //找幫手
 CompilerUntils[type](node, self.vm, expr); } }) } //解析表達式的方法
 compileText(node, expr) { CompilerUntils.text(node, this.vm, expr); } } //解析指令幫手
CompilerUntils = { //解析text指令
 text(node, vm, expr) { //第一次觀察
        //1.找到更新規則對象的更新方法
        var updaterFn = this.updater["textUpdater"]; //2.執行方法
        updaterFn && updaterFn(node, vm.$data[expr]) //等價if(updaterFn){updaterFn(node,vm.$data[expr])}

        //第n+1次觀察
        new Watcher(vm, expr, (newVaule) => { //觸發訂閱時按之前的規則對節點進行更新;v-model的也一樣;
            updaterFn && updaterFn(node, newVaule); }) }, //解析model指令
 model(node, vm, expr) { //1.找到更新規則對象的更新方法
        var updaterFn = this.updater["modelUpdater"]; //2.執行方法
        updaterFn && updaterFn(node, vm.$data[expr]) //等價if(updaterFn){updaterFn(node,vm.$data[expr])}

        //第n+1次觀察
        new Watcher(vm, expr, (newVaule) => { //觸發訂閱時按之前的規則對節點進行更新;v-model的也一樣;
            updaterFn && updaterFn(node, newVaule); }) //視圖到模型變化
        node.addEventListener("input", (e) => { var newValue = e.target.value; //把值放進數據
            console.log(newValue+'新值'); vm.$data[expr] = newValue; }) }, updater: { //文本更新方法
 textUpdater(node, value) { node.textContent; //node.innerText; //谷歌支持
 }, //輸入框值更新方法
 modelUpdater(node, value) { node.value = value; } } }
class Observer { //構造函數
 constructor(data) { //提供一個解析方法,完成屬性的分析,和挾持
        this.observe(data); } observe(data) { //判斷數據的有效性 必須是對象
        if(!data || typeof data !== "object") { return; } var keys = Object.keys(data); //拿到所有的屬性(key)轉為數組
        keys.forEach((key) => { //重新定義key
            this.defineReactive(data, key, data[key]); //動態屬性需要中括號不能.
 }) } //針對當前對象屬性的重新定義(挾持)
 defineReactive(obj, key, val) { var dep = new Dep(); //重新定義 
 Object.defineProperty(obj, key, { enumerable: true, //是否可以遍歷
            configurable: false, //是否可以重新配置
            get() { //getter取值
                Dep.target && dep.addSub(Dep.target); //拿到訂閱者
                //返回屬性
                return val; }, set(newValue) { //setter修改值
                val = newValue; //新值覆蓋舊值
                dep.notify(); //通知,觸發update操作;
 } }) } } //創建發布者 //1.管理訂閱者 //2.通知
class Dep { constructor() { this.subs = []; } //添加訂閱
    addSub(sub) { //其實就是Watcher實例
        this.subs.push(sub); } //集體通知
 notify() { this.subs.forEach((sub) => { sub.update(); }) } }
//定義一個訂閱者
class Watcher { //構造函數
    //1.需要使用訂閱功能的節點
    //2.全局vm對象,用於獲取數據
    //3.需要使用訂閱功能的節點
 constructor(vm, expr, cb) { //緩存重要屬性
        this.vm = vm; this.expr = expr; this.cb = cb; //緩存當前值
        this.value = this.get(); } //獲取當前值
 get() { //把當前訂閱者添加到全局
        Dep.target = this; //watcher實例
        //獲取當前值
        var value = this.vm.$data[this.expr]; //清空全局
        Dep.target = null; //返回
        return value; } //提供一個更新
 update() { //獲取新值
        var newValue = this.vm.$data[this.expr]; //獲取老值
        var oldValue = this.value; //執行回調
        if(newValue !== oldValue) { this.cb(newValue); //效果一樣關鍵需不需要改變this指向this.cb.call(this.vm,newValue)
 } } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM