1. 程式人生 > >深入Vue實現原理,實現一個響應式框架

深入Vue實現原理,實現一個響應式框架

歡迎大家訪問我的個人網站 - Sunday俱樂部


在前面的章節中我們已經學習了Vue.js的基礎內容並且瞭解了Vue.js的原始碼實現,包括:Vue的生命週期、Vue的資料響應、Vue的渲染流程等等,在這一章節我們會和大家一起去實現一個響應式的框架 – MVueMVue 會遵循Vue的程式碼邏輯和實現思路,我們希望能夠藉助MVue來讓大家更好的理解整個Vue的核心思想:響應式資料渲染。

在開始我們的MVue開發之前,我們需要先了解一些必備的知識。首先是Object.defineProperty(obj, prop, descriptor),這個方法可以用來定義物件的屬性描述符

我們可以點選這裡來檢視這個方法的詳細定義。我們這裡主要使用到的是get、set描述符,我們可以使用get、set來監聽物件的屬性setter、getter的呼叫事件。我們看一下下面的這段程式碼:

<div>
    <input type="text" id="input-msg">
    <p id="output-msg"></p>
</div>

<script>
    var obj = {
        msg: 'hello'
    };

    var key = 'msg';

    var val = obj[key];

    Object
.defineProperty(obj, key, { enumerable: true, configurable: true, set: function (newValue) { val = newValue; console.log('setter'); }, get: function () { console.log('getter'); return val; } })
</script
>

在上面的程式碼中我們利用Object.defineProperty監聽了obj.msgsetter、getter事件。所有當我們在控制檯去呼叫obj.msg的時候就會呼叫console.log('getter');,當我們呼叫obj.msg = '123' 的時候就會呼叫console.log('setter');,由此我們就成功的監聽了obj.msg的資料變化。那麼這有什麼意義呢?我們可以利用這個功能來做些什麼呢?我們看一下下面的程式碼:

<div>
    <input type="text" id="input-msg">
    <p id="output-msg"></p>
</div>

<script>
    var inputMsg = document.getElementById('input-msg'),
        outputMsg = document.getElementById('output-msg');


    var obj = {
        msg: 'hello'
    };

    var key = 'msg';

    var val = obj[key];


    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        set: function (newValue) {
            val = newValue;

            outputMsg.innerText = obj[key];
        },

        get: function () {
            console.log('getter');
            return val;
        }
    });


    inputMsg.addEventListener('input', function (event) {
        var newVal = event.target.value;
        obj[key] = newVal;
    });

</script>

在上面的程式碼中,我們通過監聽inputinput事件來去改變obj[key]的值,使obj[key]的值始終等於使用者輸入的值,當obj[key]的值因為使用者的輸入而發生了改變的時候,會啟用Object.defineProperty中的setter事件,然後我們獲取到最新的obj[key]的值並把它賦值給outputMsg。這樣當我們在input中進行輸入的時候,<p>中的值也會跟隨我們的輸入變化。這種通過Object.defineProperty來監聽資料變化的方式就是Vue資料響應的核心思想。

其次大家需要了解的就是觀察者模式,大家可以點選這裡來檢視觀察者模式的詳細解釋,相信這裡會比我解釋的更加清楚。

當大家瞭解完觀察者模式之後我們就可以正式開始我們的MVue的開發工作。

思路整理

整個框架的思路被分成三大塊。

首先就是檢視渲染,我們在html或者<template></template>中進行html內容編寫的時候,往往是這樣:

<div id="app">
    <input type="text" v-model='msg'>
    <div>
        <p>{{msg}}</p>
    </div>
</div>

其中的v-model='msg'{{msg}} 瀏覽器是無法解析的,那麼我們就需要把 瀏覽器不認識的內容轉化為瀏覽器可以解析的內容,在Vue中,Vue通過虛擬DOM(VNode描述真實DOM,然後通過_update來進行具體渲染。我們這裡不去描述這個VNode直接通過_update方法來對DOM進行渲染操作,這個動作是發生在Compile中。Compile會解析我們的具體指令,並重新渲染DOM

其次是監聽我們的資料變化,在最初的例子中我們已經知道我們可以通過Object.defineProperty(obj, prop, descriptor)來實現資料的監聽,那麼就需要一個Observer類來進行資料劫持的工作,這時Observer承擔的就是釋出者的工作。當我們通過Observer來監聽到資料變化之後,我們需要通知我們的觀察者,但是對於我們的釋出者來說,它並不知道誰是這個觀察者,這個觀察者是一個還是多個?所以這個時候,就需要有一個人來負責去收集這些依賴的工作,這個人就是Dep(Dependency),我們通過Dep來去通知觀察者WatcherWatcher訂閱DepDep持有Watcher,兩者互相依賴形成一個訊息中轉站。Watcher接收到訊息,需要更改檢視的時候,那麼就會發布具體的訊息根據具體指令的不同(Directive來執行具體的操作Patch。這就是我們的整個從監聽到渲染的過程,如下圖:

這裡寫圖片描述

最後我們需要把所有的東西整合起來形成一個入口函式,輸出給使用者方便使用者進行呼叫,就好像Vue中的new Vue({})操作,這裡我們叫它MVue

綜合以上的內容,我們需要完成的程式碼內容包括

├── compile.js  渲染DOM,解析指令
├── dep.js  收集依賴
├── directive.js    所有支援到的指令
├── mvue.js 入口函式
├── observer.js 資料劫持
├── patch.js    根據具體的指令來修改渲染的內容
└── watcher.js  觀察者。訂閱Dep,釋出訊息

我們預期的完成效果應該是這樣

<div id="app">
    <input type="text" v-model='msg'>
    <div>
        <p>{{msg}}</p>
    </div>
</div>

<script>
    var vm = new MVue({
        el: '#app',
        data: {
            msg: 'hello'
        }
    });
</script>

入口函式

首先我們需要先生成MVue的入口函式,我們仿照Vue的寫法,建立一個MVue的類,並獲取傳入的options

function MVue (options) {
    this.$options = options;
    this._data = options.data || {};
}

MVue.prototype = {
    _getVal: function (exp) {
        return this._data[exp];
    },

    _setVal: function (exp, newVal) {
        this._data[exp] = newVal;
    }
}

首先我們實現一個MVue的建構函式,併為它提供了兩個私有的原型方法_getVal_setVal用於獲取和設定data中對應key的值。這時我們就可以通過下面的程式碼來建立對應的MVue例項。

var vm = new MVue({
    el: '#app',
    data: {
        msg: 'hello'
    }
});

然後我們就可以在MVue的建構函式之中去進行我們的 檢視渲染資料監聽 的操作。

檢視渲染

然後我們進行我們的檢視渲染,我們再來回顧一下我們需要解析的檢視結構

<div id="app">
    <input type="text" v-model='msg'>
    <div>
        <p>{{msg}}</p>
    </div>
</div>

在這段html之中v-model{{msg}}是我們MVue中的自定義指令,這些指令我們的瀏覽器是無法解析的,所以需要我們把這些指令解析為瀏覽器可以解析的html程式碼。以<p>{{msg}}</p>為例,當我們宣告data: {msg: 'hello'}的時候,應解析為<p>hello</p>

我們的模板解析的操作是通過compile.js來完成的。

function Compile (vm, el) {
    this.$vm = vm;
    el = this.$el = this.isElementNode(el) ? el : document.querySelector(el);

    if (!el) {
        return;
    }

    this._update(el);
};

Compile.prototype = {

    /**
     * Vue中使用vm._render先根據真實DOM建立了虛擬DOM,然後在vm._update把虛擬DOM轉化為真實DOM並渲染,
     * 我們這裡沒有虛擬DOM,所以直接通過createElm方法建立一個fragment用以渲染
     */
    _update: function (el) {
        this.$fragment = document.createDocumentFragment();
        // 複製el的內容到建立的fragment
        this.createElm(el);
        // 把解析之後的fragment放入el中,此時fragment中的所有指令已經被解析為具體資料
        el.appendChild(this.$fragment);
    },

    /**
     * 建立新的DOM 用來替換 原DOM
     */
    createElm: function (node) {
        var childNode = node.firstChild;
        if (childNode) {
            this.$fragment.appendChild(childNode);
            this.createElm(node);
        }
    }
} 

我們聲明瞭一個Compile的構造方法,並呼叫了它的_update原型函式,在_update中我們聲明瞭一個fragment用於承載解析之後的模板內容,通過createElm的遞迴呼叫獲取el中的元素,並把獲取出的元素放入fragment中,最後把fragment新增到el裡面。至此我們已經成功的獲取到了el中的元素,並把這些元素重新規制。

接下來我們就需要對獲取出來的元素進行解析操作,其實就是對v-model{{*}}等指令進行解析,這個解析的時機應該在 遍歷出所有的元素之後,新增fragmentel之前。我們看一下解析DOM的程式碼:

Compile.prototype = {
    _update: function (el) {
        ...
        // 解析被建立完成的fragment,此時fragment已經擁有了el內所有的元素
        this.compileElm();
        ...
    },
    ...
    /**
     * 對DOM進行解析
     */
    compileElm: function (childNodes) {
        var reg = /\{\{(.*)\}\}/;
        if (!childNodes) {
            childNodes = this.$fragment.childNodes;
        }

        [].slice.call(childNodes).forEach(node => {
            if (node.childNodes.length > 0) {
                // 迭代所有的節點
                this.compileElm(node.childNodes);
            }

            // 獲取elementNode節點
            if (this.isElementNode(node)) {
                if (reg.test(node.textContent)) {
                    // 匹配 {{*}}
                    this.compileTextNode(node, RegExp.$1);
                } 
                // 匹配elementNode
                this.compileElmNode(node);

            } 
        });
    },
    /**
     * 解析elementNode,獲取elm的所有屬性然後便利,檢查屬性是否屬於已經註冊的指令,
     * 如果不是我們的自定義指令,那麼就不需要去處理它了
     * 如果是已註冊的指令,我們就交給directive去處理。(演示只有一個v-model)
     */
    compileElmNode: function (node) {
        var attrs = [].slice.call(node.attributes),
            $this = this;

        attrs.forEach(function (attr) {
            if (!$this.isDirective(attr.nodeName)) {
                return;
            }

            var exp = attr.value;
            // 匹配v-model指令
            directives.model($this.$vm, node, exp);
            // 去掉自定義指令
            node.removeAttribute(attr.name);
        });
    },
    /**
     * 解析{{*}}
     */
    compileTextNode: function (node, exp) {
        directives.text(this.$vm, node, exp);
    },
    /**
     * 判斷是否是已註冊的指令,這裡就判斷是否包含 v-
     */
    isDirective: function (attrNodeName) {
        return attrNodeName.indexOf('v-') === 0;
    },
    /**
     * 判斷elmNode節點
     */
    isElementNode: function (node) {
        return node.nodeType === 1;
    }
}

由上面的程式碼可以看出,解析的操作主要在compileElm方法中進行,這個方法首先獲取到fragmentchildNodes,然後對childNodes進行了forEach操作,如果其中的node還有子節點的話,則會再次呼叫compileElm方法,然後解析這個node,如果是一個ElementNode節點,則再去判斷是否為{{*}}雙大括號結構,如果是則會執行compileTextNode來解析{{*}},然後通過compileElmNode來解析ElmNode中的指令。

compileTextNode中的實現比較簡單,主要是呼叫了directives.text(vm, node, exp)進行解析,這裡我們稍後再看,我們先主要來看下compileElmNode做了什麼。

compileElmNode首先把node中所有的屬性轉成了陣列並拷貝給了attrs,然後對attrs進行遍歷獲取其中的指令,因為我們目前只有一個v-model指令,所以我們不需要在對指令進行判斷,可以直接呼叫directives.model(vm, node, exp)來進行v-model的指令解析,最後在DOM中刪除我們的自定義指令。

至此我們就複製了el的所有元素,並根據不同的指令把它們交由directives中對應的指令解析方法進行解析,這就是我們compile.js中所做的所有事情。接下來我們看一下directives是如何進行指令解析操作的,程式碼如下:

// directives.js

/**
 * 指令集和
 * 
 * v-model
 */
var directives = {
    /**
     * 連結patch方法,將指令轉化為真實的資料並展示
     */
    _link: function (vm, node, exp, dir) {
        var patchFn = patch(vm, node, exp, dir);
        patchFn  && patchFn(node, vm._getVal(exp));
    },

    /**
     * v-model事件處理,這裡的v-model只針對了<input type='text'> 
     */
    model: function (vm, node, exp) {
        this._link(vm, node, exp, 'model');

        var val = vm._getVal(exp);
        node.addEventListener('input', function (e) {
            var newVal = e.target.value;
            if (newVal === val) return;
            vm._setVal(exp,newVal);
            val = newVal;
        });
    },

    /**
     * {{}}事件處理
     */
    text: function (vm, node, exp) {
        this._link(vm, node, exp, 'text');
    }
}

由上面的程式碼我們可以看出,我們首先定義了一個directives變數,它包含了_link、model、text三個指令方法,其中_link為私有方法,model、text為公開的指令方法,關於_link我們最後在分析,我們先來看一下model

model指令方法對應的為v-model指令,它接受三個引數,vm為我們的MVue例項,node為繫結該指令的對應節點,exp為繫結資料的key。我們先不去管this._link的呼叫,大家先來想一下我們在index.html中對於v-model的使用,我們把v-model='msg'繫結到了我們的input標籤上,意為當我們在input上進行輸入的時候msg始終等於我們輸入的值。那麼我們在model指令方法中所要做的事情就很明確了,首先我們通過vm._getVal(exp);獲取到msg當前值,然後我們監聽了nodeinput事件,獲取當前使用者輸入的最新值,然後通過vm._setVal(exp,newVal)配置到vm._data中,最後通過val = newVal重新設定val的值。

然後是text指令方法,這個方法直接呼叫了this._link,並且我們還記得在model指令方法中也呼叫了this._link,那麼我們來看一下_link的實現。

_link中,他接收四個引數,其中dir為我們的指令程式碼,然後它呼叫了一個patch方法,獲取到了一個patchFn的變數,這個patch方法位於patch.js中。

// patch.js

/**
 * 更改node value,在編譯之前,替換 v-model  {{*}} 為真實資料
 * @param {*} vm 
 * @param {*} node 
 * @param {*} exp 
 * @param {*} dir 
 */
function patch (vm, node, exp, dir) {

    switch (dir) {
        case 'model':
        /**
         * input / textear
         */
        return function (node , val) {
            node.value = typeof val === 'undefined' ? '' : val;
        }
        case 'text':
        /**
         * {{*}}
         */
        return function (node , val) {
            node.textContent = typeof val === 'undefined' ? '' : val;
        }
    }

}

patch的方法實現比較簡單,它首先去判斷了傳入的指令,然後根據不同的指令返回了不同的函式。比如在model指令方法中,因為我們只支援input、 textear,所以我們接收到的node只會是它們兩個中的一個,然後我們通過node.value = val來改變node中的value

我們在directives.js中獲取到了patch的返回函式patchFn,然後執行patchFn。至此我們的模板已經被解析為瀏覽器可以讀懂的html程式碼。

<div id="app">
   <input type="text">
   <div>
       <p>hello</p>
   </div>
</div>

資料監聽實現

然後我們來看一下 資料監聽模組的實現 ,我們根據上面的 思路整理 想一下這個資料監聽應該如何去實現?我們知道了我們應該在observer裡面去實現它,但是具體應該怎麼做呢?

再來明確一下我們的目標,我們希望 通過observer能夠監聽到我們資料data的變化,當我們呼叫data.msg或者data.msg = '123'的時候,會分別啟用getter或者setter方法。那麼我們就需要對整個data進行監聽,當我們獲取到data物件之後,來遍歷其中的所有資料,並分別為它們新增上gettersetter方法。

// observer.js

function observer (value) {
    if (typeof value !== 'object') {
        return;
    }

    var ob = new Observer(value);
}


function Observer (data) {
    this.data = data;
    this.walk();
}

Observer.prototype = {

    walk: function () {
        var $this = this;
        var keys = Object.keys(this.data);
        keys.forEach(function (key) {
            $this.defineReactive(key, $this.data[key]);
        });
    },

    defineReactive: function (key, value) {
        var dep = new Dep();
        Object.defineProperty(this.data, key, {
            enumerable: true,
            configurable: true,
            set: function (newValue) {
                if (value === newValue) {
                    return;
                }
                value = newValue;
                dep.notify();
            },

            get: function () {
                dep.depend();
                return value;
            }
        });
    },
}

observer.js中我們通過observer (value)方法來生成Observer物件,其中傳入的valuedata: {msg: 'hello'}。然後呼叫Observer的原型方法walk,遍歷data呼叫defineReactive,通過Object.defineProperty為每條資料都新增上setter、getter監聽,同時我們聲明瞭一個Dep物件,這個Dep物件會負責收集依賴並且派發更新。大家結合我們的思路整理想一下,我們應該在什麼時候去收集依賴?什麼時候去派發更新

當用戶通過input進行輸入修改資料的時候,我們是不是應該及時更新檢視?所以在setter方法被啟用的時候,我們應該呼叫dep.notify()方法,用於派發更新事件

當我們的資料被展示出來的時候,也就是在getter事件被啟用的時候,我們應該去收集依賴,也就是呼叫dep.depend()方法。

然後我們來看一下Dep方法的實現,在Dep.js中。

// Dep.js

var uid = 0;
function Dep () {
    // 持有的watcher訂閱者
    this.subs = [];
    this.id = uid++;
}

Dep.prototype = {
    // 使dep與watcher互相持有
    depend () {
        // Dep.target為watcher例項
        if (Dep.target) {
            Dep.target.addDep(this)
        }
    },
    // 新增watcher
    addSub: function (sub) {
        this.subs.push(sub);
    },
    // 通知所有的watcher進行更新
    notify: function () {
        this.subs && this.subs.forEach(function (sub) {
            sub.update();
        });
    }
}

Dep.js的實現比較簡單,它主要是就負責收集依賴(watcher)並且派發更新(watcher.update(),我們可以看到Dep首先聲明瞭subs用於儲存訂閱了Depwatcher例項,然後給每個Dep例項建立了一個id,然後我們為Dep聲明瞭三個原型方法,當呼叫notify的時候,Dep回去遍歷所有的subs然後呼叫他的update()方法,當呼叫depend的時候會呼叫watcheraddDep方法使DepWatcher互相持有。其中的Dep.targetsub都為Watcher例項。

然後我們來看一下Watcher.js的程式碼實現。

// watcher

function Watcher (vm, exp, patchFn) {
    this.depIds = {};
    this.$patchFn = patchFn;
    this.$vm = vm;
    this.getter = this.parsePath(exp)
    this.value = this.get();
}

Watcher.prototype = {
    // 更新
    update: function () {
        this.run();
    },
    // 執行更新操作
    run: function () {
        var oldVal = this.value;
        var newVal = this.get();
        if (oldVal === newVal) {
            return;
        }
        this.$patchFn.call(this.$vm, newVal);
    },
    // 訂閱Dep
    addDep: function (dep) {
        if (this.depIds.hasOwnProperty(dep.id)) {
            return;
        }
        dep.addSub(this);
        this.depIds[dep.id] = dep;
    },
    // 獲取exp對應值,這時會啟用observer中的get事件
    get: function () {
        Dep.target = this;
        var value = this.getter.call(this.$vm, this.$vm._data);
        Dep.target = null;
        return value;
    },
    /**
     * 獲取exp的對應值,應對a.b.c
     */
    parsePath: function (path) {
        var segments = path.split('.');

        return function (obj) {
          for (let i = 0; i < segments.length; i++) {
            if (!obj) return
            obj = obj[segments[i]]
          }
          return obj
        }
      }
}

Watcher.js中它直接接收了patchFn,大家還記得這個方法是幹什麼的吧?patchFn是更改node value,在編譯之前,替換 v-model 、 {{*}} 為真實資料的方法,在Watcher.js接收了patchFn,並把它賦值給this.$patchFn,當我們呼叫this.$patchFn的時候,就會改變我們的DOM渲染。

然後我們呼叫parsePath用於解析物件資料,並返回一個解析函式,然後把它賦值給this.getter。最後我們呼叫get()方法,在get()中我們給Dep.target持有了Watcher,並激活了一次getter方法,使我們在observer中監聽的getter事件被啟用,會呼叫dep.depend()方法,然後呼叫watcher.addDep(dep),使DepWatcher互相持有,相互依賴。

然後我們看一下update方法的實現,我們知道當資料的setter事件被啟用的時候,會呼叫dep.notify(),dep.notify()又會遍歷所有的訂閱watcher執行update方法,那麼在upadte方法中,直接執行了this.run,在run()方法中,首先獲取了 當前watcher所觀察的exp的改變前值oldVal和修改後值newVal,然後通過patchFn去修改DOM

以上就是我們整個資料監聽的流程,它首先通過observer來監聽資料的變化,然後當資料的getter事件被啟用的時候,呼叫dep.depend()來進行依賴收集,當資料的setter事件被啟用的時候,呼叫dep.notify()來進行派發更新,這些的具體操作都是在我們的觀察者watcher中完成的。

整合MVue

最後我們就需要把我們的 檢視渲染資料監聽 連結起來,那麼這個連線的節點應該在哪裡呢?我們再來捋一下我們的流程。

當用戶編寫了我們的指令程式碼

<div id="app">
    <inputtype="text" v-model='msg'>
    <div>
        <p>{{msg}}</p>
    </div>
</div>

的時候,我們通過Compile進行解析,當發現了我們的自定義指令v-model、{{*}}的時候,會進行directives進行指令解析,其中監聽的使用者的輸入事件,並呼叫了vm._setVal()方法,從而會啟用在observer中定義的setter事件,setter會進行派發更新的操作,呼叫dep.notify()方法,然後便利subs呼叫update方法。

結合上面的描述,我們應該在兩個地方去完成連線節點。首先是在呼叫vm._setVal()方法的時候,我們需要保證observer中的setter事件可以被啟用,那麼我們最好在入口函式中去宣告這個observer

function MVue (options) {
    this.$options = options;
    this._data = options.data || {};

    observer(this._data);

    new Compile(this, this.$options.el);
}

MVue.prototype = {
    _getVal: function (exp) {
        return this._data[exp];
    },

    _setVal: function (exp, newVal) {
        this._data[exp] = newVal;
    }
}

然後當setter事件被啟用之前,我們需要初始化完成watcher使其擁有vm、exp、patchFn,那麼最好的時機應該在獲取到patchFn這個返回函式的時候,所以應該在:

var directives = {

    _bind: function (vm, exp, patchFn) {
        new Watcher(vm,exp, patchFn);
    },

    /**
     * 連結patch方法,將指令轉化為真實的資料並展示
     */
    _link: function (vm, node, exp, dir) {
        var patchFn = patch(vm, node, exp, dir);
        patchFn  && patchFn(node, vm._getVal(exp));

        this._bind(vm, exp, function (value) {
            patchFn  && patchFn(node, value);
        });
    },
    ...
}

通過_bind方法來去初始化watcher

使用與擴充套件

至此我們的MVue框架就已經被開發完成了,我們可以點選這裡來獲取本課程中所有的程式碼,當我們需要使用我們的MVue的時候,我們可以這麼做:

<script src="./mvue3/patch.js"></script>
<script src="./mvue3/dep.js"></script>
<script src="./mvue3/directive.js"></script>
<script src="./mvue3/watcher.js"></script>
<script src="./mvue3/observer.js"></script>
<script src="./mvue3/compile.js"></script> 
<script src="./mvue3/mvue.js"></script>

<div id="app">
    <input type="text" v-model='msg'>
    <div>
        <p>{{msg}}</p>
    </div>
</div>

<script>
    var vm = new MVue({
        el: '#app',
        data: {
            msg: 'hello'
        }
    });
</script>

因為我們的MVue並沒有進行模組化,所以需要把所有的JS全部引入才能使用。大家也可以嘗試一下把MVue進行模組化,這樣就可以只通過引入<script src="./mvue3/mvue.js"></script>來使用MVue了。

現在我們的MVue還非常的簡單,大家可以想一下如何為我們的MVue增加更多的功能,比如說更多的指令或者新增v-on:click的事件處理?這裡給大家留下三個問題,目的是希望大家能夠親自寫一下這個專案,可能會讓大家有更多的理解。

1、實現v-show指令
2、實現v-on:click事件監聽
3、如何和Vue一樣可以直接通過this.msg來獲取我們在data中定義的資料

這三個問題的解決方案都在我們的程式碼中,大家可以作為參考。

這一章為Vue.js的最後一章,從下一章開始我們就會進入Vue周邊生態的學習,希望大家一定要親自實現一下MVue的程式碼。


前端技術日新月異,每一種新的思想出現,都代表了一種技術的躍進、架構的變化,那麼對於目前的前端技術而言,MVVM 的思想已經可以代表當今前端領域的前沿思想理念,Angular、React、Vue 等基於 MVVM 思想的具體實現框架,也成為了人們爭相學習的一個熱點。而 Vue 作為其中唯一沒有大公司支援但卻能與它們並駕齊驅並且隱隱有超越同類的趨勢,不得不說這種增長讓人感到驚奇。

本系列課程內容將會帶領大家由淺入深的學習 Vue 的基礎知識,瞭解 Vue 的原始碼設計和實現原理,和大家一起看一下尤雨溪先生的程式設計思想、架構設計以及如何進行程式碼實現。本系列課程內容主要分為三大部分:

Vue 的基礎知識:在這一部分將學習 Vue 的基礎語法及其原始碼的實現。例如,Vue 的生命週期鉤子如何設計?當聲明瞭一個 directive 時,Vue 究竟執行了什麼?為什麼只有通過 vue.set 函式才能為響應式物件新增響應式屬性?如果我們自己要實現一個響應式的框架的話,應該如何下手、如何思考等。
Vue的周邊生態:在這一部分將學習 Vue 的周邊生態圈,包括有哪些 UI 庫可以和 Vue 配合快速構建介面、如何使用 vue-router構建前端路由、如何使用 Vuex 進行狀態管理、如何使用 Axios 進行網路請求、如何使用 Webpack、使用 vue-cli 構建出的專案裡的各種配置有什麼意義?
專案實戰:在這一部分將會通過一個有意思的自動對話系統來進行專案實戰,爭取通過這個小專案把學到的知識點進行一個整合。

這裡寫圖片描述