手把手教你Vue從零擼一個(gè)迷你版MVVM框架

這段時(shí)間 在工作之余的休息時(shí)間,學(xué)習(xí)了解Vue 痰催,學(xué)習(xí)Vue的設(shè)計(jì)思想迎瞧,通過Vue官網(wǎng)學(xué)習(xí)Vue的語法凶硅,通過Vue前端技術(shù),搭建構(gòu)建了一個(gè)簡單的項(xiàng)目捷绑,在項(xiàng)目學(xué)習(xí)完之后氢妈,發(fā)現(xiàn)Vue是一個(gè)很有意思的前端技術(shù)首量,沒事就看了源碼,第一次看的時(shí)候不知道如何下手鸭叙,就開始在百度拣宏,谷歌,兩大搜索神器的幫助下宋下,找到了學(xué)習(xí)的快捷之路辑莫。發(fā)現(xiàn)了難啃的骨頭摆昧,才是最有意思的事蜒程。

Vue框架到底為我們做了什么伺帘?

  • 數(shù)據(jù)和視圖分離伪嫁,解耦(開放封閉原則)
    1.所有數(shù)據(jù)和視圖不分離的偶垮,都會(huì)命中開放封閉原則
    2.Vue 數(shù)據(jù)獨(dú)立在 data 里面似舵,視圖在 template 中
  • 以數(shù)據(jù)驅(qū)動(dòng)視圖,只關(guān)心數(shù)據(jù)變化龙助,dom 操作被封裝
    1. 使用原生js是直接通過操作dom來修改視圖蛛芥,例如
    ducument.getElementById('xx').innerHTML="xxx"
    
    1. 以數(shù)據(jù)驅(qū)動(dòng)視圖就是仅淑,我們只管修改數(shù)據(jù),視圖的部分由框架去幫我們修改赡鲜,符合開放封閉模式.

如何理解 MVVM 昆禽?

  • MVC
    1. Model 數(shù)據(jù) → View 視圖 → Controller 控制器
  • MVVM
    1. MVVM不算是一種創(chuàng)新
    2. 但是其中的 ViewModel 是一種創(chuàng)新
    3. ViewModel 是真正結(jié)合前端應(yīng)用場(chǎng)景的實(shí)現(xiàn)
  • 如何理解MVVM
    1. MVVM - Model View ViewModel醉鳖,數(shù)據(jù),視圖壮韭,視圖模型
    2. 三者與 Vue 的對(duì)應(yīng):view 對(duì)應(yīng) template纹因,vm 對(duì)應(yīng) new Vue({…})瞭恰,model 對(duì)應(yīng) data
    3. 三者的關(guān)系:view 可以通過事件綁定的方式影響 model,model 可以通過數(shù)據(jù)綁定的形式影響到view恶耽,viewModel是把 model 和 view 連起來的連接器
我們來看一下最后我們要達(dá)到的效果
vue.gif

我們先來看下我們簡單的結(jié)構(gòu)

mini-MVVM結(jié)構(gòu)

Vue項(xiàng)目源碼

以上就是我們簡單搭建的項(xiàng)目目錄結(jié)構(gòu)偷俭,有需要了解可以去閱讀源碼來了解更多涌萤,這里就不做過多的展開

我們先來看下我們index.html完整代碼

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>MiniMVVM 框架demo演示</title>
        <link rel="stylesheet" href="./css/mini-mvvm.css"/> 
    </head>
    <body>
        <div id="app">
            <h1>{{title}}</h1>
            <div>
                <span>文本輸入框:</span>
                <input type="text" v-model="message"/>
            </div>
            <section>
                <samp>數(shù)據(jù)顯示結(jié)果:</samp>
                <hr/>
                <span>{{message}}</span>
            </section>
        </div>

    <script type="text/javascript" src="js/mini-mvvm.js"></script>
    <script>
        var app=new MiniMVVM({
            el:'#app',
            data:{
                title:'Mini-MVVM demo演示',
                message:'Hello World ! ! !'
            }
        });
    </script>

    </body>
</html>

大家看到這里有沒有發(fā)現(xiàn)负溪,我們調(diào)用不是Vue 而是我們自己自定義的MiniMVVM,沒錯(cuò)淌实。那我們?cè)賮砜纯碫ue的用法猖腕,幫大家回顧一下

<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
Hello Vue!

我們已經(jīng)成功創(chuàng)建了第一個(gè) Vue 應(yīng)用倘感!看起來這跟渲染一個(gè)字符串模板非常類似,但是 Vue 在背后做了大量工作∮倌辏現(xiàn)在數(shù)據(jù)和 DOM 已經(jīng)被建立了關(guān)聯(lián)蜡豹,所有東西都是響應(yīng)式的镜廉。我們要怎么確認(rèn)呢?打開你的瀏覽器的 JavaScript 控制臺(tái) (就在這個(gè)頁面打開)齐遵,并修改 app.message 的值塔插,你將看到上例相應(yīng)地更新想许。
接下來是我們的函數(shù)類

(function () {
    //初始化類
    class MiniMVVM {...}
    //模板編譯類
    class TemplateCompile {...}

    //數(shù)據(jù)劫持類断序,響應(yīng)式
    class Observer {...}

    //觀察者
    class Watcher {...}

    //發(fā)布訂閱類 自定義事件
    class Emitter {...}

    //存儲(chǔ)指令和工具方法
    let CompileTool = {...};
    window.MiniMVVM = MiniMVVM;
})();

這是我們mini版的函數(shù)類逢倍,這里我們只實(shí)現(xiàn)了簡單的雙向數(shù)據(jù)綁定景图,目的是為了讓大家學(xué)習(xí)思想碉哑,以及理解分析


  function initMixin (Vue) {...}

  function initInternalComponent (vm, options) {...}

  function resolveConstructorOptions (Ctor) {...}

  function resolveModifiedOptions (Ctor) {...}

  function Vue (options) {
    if (!(this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }

  initMixin(Vue);
  stateMixin(Vue);
  eventsMixin(Vue);
  lifecycleMixin(Vue);
  renderMixin(Vue);

  /*  */

  function initUse (Vue) {...}

  /*  */

  function initMixin$1 (Vue) {...}

  /*  */

  function initExtend (Vue) {

如果讓我們光來看Vue.js 就是一萬兩千行代碼扣典,我相信大多數(shù)和我一樣贮尖,看到這么多行代碼人已經(jīng)崩潰了直接就選擇了放棄,我們雖然是mini版的薪前,我們也借助vue設(shè)計(jì)來寫我們mini版关斜。
下面我們就來簡單的分析下我們這個(gè)函數(shù)的作用以及用法

//初始化
class MiniMVVM {
        constructor(options) {
            this.$el = options.el;
            this.$data = options.data;
            //判斷是否有模板痢畜,如果有模板的話,就執(zhí)行編譯和數(shù)據(jù)劫持
            if (this.$el) {
                //1,數(shù)據(jù)劫持
                this.$data = (new Observer()).observe(this.$data);
                //2,模板編譯
                new TemplateCompile(this.$el, this);
            }
        }
    }

這里我們主要是為了獲取我們模板信息以及數(shù)據(jù)
下一步我們來看一下我們數(shù)據(jù)劫持監(jiān)聽

 //數(shù)據(jù)劫持類吼拥,響應(yīng)式
    class Observer {
        constructor(data) {
            this.data = data;
            this.observe(this.data);
        }

        observe(data) {
            //驗(yàn)證是否存在线衫,是否是對(duì)象
            if (!data || typeof data !== 'object') {
                return;
            }
            //使用proxy 代理攔截監(jiān)聽
            let that = this;
            //初始化發(fā)布訂閱
            let emitter = new Emitter();
            let handler = {
                get(target, key) {
                    //遞歸當(dāng)前target[key] 是否是對(duì)象
                    if (typeof target[key] === 'object' && target[key] !== null) {
                        return new Proxy(target[key], handler);
                    }
                    //判斷當(dāng)前key下面的watcher是否存在
                    if (Emitter.watcher) {
                        emitter.addSub(Emitter.watcher);
                    }
                    return target[key];
                },
                set(target, key, newVal) {
                    if (target[key] !== newVal) {
                        target[key] = newVal;
                        emitter.notify();
                    }
                }
            }
            //創(chuàng)建proxy代理
            let pdata = new Proxy(data, handler);
            return pdata;
        }
    }

Proxy語法和用例

let p = new Proxy(target, handler);

將目標(biāo)和處理程序傳遞給Proxy構(gòu)造函數(shù)矿酵,這樣就創(chuàng)建了一個(gè)proxy對(duì)象矗积。

//模板編譯類
    class TemplateCompile {
        constructor(el, vm) {
            //1,獲取元素
            this.el = (el.nodeType === 1 ? el : document.querySelector(el));
            //2棘捣,獲取vm實(shí)例
            this.vm = vm;
            //如果元素存在,然后我們進(jìn)行編譯
            if (this.el) {
                //獲取模板
                let fragment = this.nodeToFragment(this.el);
                //模板語法解析
                this.cpmpile(fragment);
                //把最后生成的文檔結(jié)構(gòu)重新append到我們頁面中去
                this.el.appendChild(fragment);
            }

        }
        //獲取模板
        nodeToFragment(el) {
            let fragment = document.createDocumentFragment();
            let firstChild = null;

            while (firstChild = el.firstChild) {
                fragment.appendChild(firstChild);
            }
            return fragment;
        }
        //解析文檔碎片评疗,編譯模板中的變量
        cpmpile(fragment) {
            //1,獲取所有的子節(jié)點(diǎn)(包括元素解點(diǎn))
            let childNodes = fragment.childNodes;
            //2,遍歷循環(huán)每個(gè)節(jié)點(diǎn)
            Array.from(childNodes).forEach(node => {
                //如果是元素節(jié)點(diǎn)
                if (node.nodeType === 1) {
                    //遞歸遍歷子節(jié)點(diǎn)
                    this.cpmpile(node);
                    //處理我們的元素節(jié)點(diǎn)
                    this.complieElement(node);
                } else {
                    //處理我們的文本節(jié)點(diǎn)
                    this.complieText(node);
                }
            })
        }

        //編譯處理
        complieText(nodeText) {
            //獲取文本節(jié)點(diǎn)的內(nèi)容
            let exp = nodeText.textContent;
            //這里我們通過正則獲取{{}}里面的數(shù)據(jù)
            let re = /{{[^}]+}}/g;
            if (re.test(exp)) {
                //渲染頁面數(shù)據(jù)
                CompileTool.text(nodeText, this.vm, exp)
            }

        }
        //編譯處理元素節(jié)點(diǎn)
        complieElement(node) {
            console.log(node);
            //取出所有的屬性
            let attrs = node.attributes;
            //邊里屬性 檢測(cè)是否具備‘-v’
            Array.from(attrs).forEach(attr => {
                console.log(attr.name);
                if (attr.name.includes('v-')) {
                    //如果是v-attr 這樣的指令 我們?nèi)ノ覀儗?duì)應(yīng)的尋找屬性值字符串所對(duì)應(yīng)的值
                    let exp = attr.value;
                    let type = exp.split('-')[1];
                    //調(diào)用model指令對(duì)應(yīng)的方法,渲染頁面數(shù)據(jù)
                    CompileTool.model(node, this.vm, exp);
                }
            })

        }
    }

這是我們模板類代碼存璃,這里主要是解析我們定義好的模板語法雕拼,下面我們?cè)賮砜聪挛覀冇^察者類

//觀察者
    class Watcher {
        constructor(vm, exp, callback) {
            this.vm = vm;
            this.exp = exp;
            this.callback = callback;
            //獲取當(dāng)前的值
            this.oldValue = this.get();
        }
        get() {
            //初始化發(fā)布訂閱狀態(tài)值
            Emitter.watcher = this;
            //獲取這個(gè)data上面的值
            let val = CompileTool.getVal(this.vm, this.exp)
            //清空一下發(fā)布狀態(tài)置
            Emitter.watcher = null;
            //返回當(dāng)前值
            return val;
        }
        update() {
            //獲取當(dāng)前值
            let newVal = CompileTool.getVal(this.vm, this.exp)
            //拿到舊的值
            let oldVal = this.oldValue;
            //比較兩次的值啥寇,是否保持一致辑甜,如果不一致就執(zhí)行回調(diào)
            if (newVal != oldVal) {
                this.callback(newVal);
            }
        }
    }

觀察者見名思義,看過或者學(xué)習(xí)過設(shè)計(jì)模式的都應(yīng)該知道觀察者模式,這里就不做過多的解釋岂傲,有興趣的朋友可以百度一下子檀。
剩下的就是我們的發(fā)布訂閱

//發(fā)布訂閱類 自定義事件
    class Emitter {
        constructor() {
            this.subs = [];
        }
        //添加一個(gè)訂閱
        addSub(watcher) {
            this.subs.push(watcher);
        }
        //通知執(zhí)行訂閱
        notify() {
            this.subs.forEach(v => v.update());
        }
    }

通過以上不到300的行的代碼褂痰,我們就實(shí)現(xiàn)了簡單的雙向數(shù)據(jù)綁定
有朋友會(huì)說你現(xiàn)在沒有其他的文件以及配置,是的归薛,不需要我們也是能實(shí)現(xiàn)數(shù)據(jù)的雙向綁定匪蝙,這里的剩下的webpack打包,webpack打包這個(gè)算是比較簡單了逛球,對(duì)于學(xué)習(xí)前端vue來說颤绕,那是再熟悉不過的了祟身,這里就不過多的介紹了物独,下一章節(jié)我們?cè)诶^續(xù)探討webpack打包腳本詳細(xì)介紹挡篓。

下面提供幾個(gè)學(xué)習(xí)es地址大家可以去看一下,中英文都有憨攒。

ES5地址

中文版:中文地址
英文版:英文地址

ES6地址

中文版:中文地址
英文版:英文地址
ES6的瀏覽器兼容性問題:地址

非常感謝您能抽出時(shí)間來閱讀阀参,如果對(duì)你有所幫助蛛壳,歡迎轉(zhuǎn)發(fā)分享所刀,您的轉(zhuǎn)發(fā)分享就是對(duì)我最大的鼓勵(lì)和支持。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忧吟,一起剝皮案震驚了整個(gè)濱河市溜族,隨后出現(xiàn)的幾起案子垦沉,更是在濱河造成了極大的恐慌,老刑警劉巖寡壮,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件况既,死亡現(xiàn)場(chǎng)離奇詭異组民,居然都是意外死亡邪乍,警方通過查閱死者的電腦和手機(jī)对竣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門否纬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛋褥,“玉大人烙心,你說我怎么就攤上這事∽希” “怎么了匙瘪?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵丹喻,是天一觀的道長。 經(jīng)常有香客問我谅猾,道長鳍悠,這世上最難降的妖魔是什么贼涩? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮谤绳,結(jié)果婚禮上袒哥,老公的妹妹穿的比我還像新娘堡称。我一直安慰自己,他們只是感情好桐臊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布断凶。 她就那樣靜靜地躺著,像睡著了一般肿男。 火紅的嫁衣襯著肌膚如雪却嗡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音撼港,去河邊找鬼餐胀。 笑死瘤载,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的墨技。 我是一名探鬼主播挎狸,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锨匆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恐锣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诀姚,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赫段,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贬丛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炬丸,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稠炬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年首启,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褒纲。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莺掠,死狀恐怖读宙,靈堂內(nèi)的尸體忽然破棺而出结闸,到底是詐尸還是另有隱情,我是刑警寧澤扎附,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布结耀,位于F島的核電站,受9級(jí)特大地震影響香伴,放射性物質(zhì)發(fā)生泄漏即纲。R本人自食惡果不足惜博肋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掘猿。 院中可真熱鬧唇跨,春花似錦、人聲如沸改橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽高诺。三九已至碌识,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虱而,已是汗流浹背筏餐。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牡拇,地道東北人胖烛。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像诅迷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子众旗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容