這段時間 在工作之余的休息時間,學(xué)習(xí)了解Vue ,學(xué)習(xí)Vue的設(shè)計思想,通過Vue官網(wǎng)學(xué)習(xí)Vue的語法,通過Vue前端技術(shù),搭建構(gòu)建了一個簡單的項目,在項目學(xué)習(xí)完之后,發(fā)現(xiàn)Vue是一個很有意思的前端技術(shù),沒事就看了源碼,第一次看的時候不知道如何下手,就開始在百度,谷歌,兩大搜索神器的幫助下,找到了學(xué)習(xí)的快捷之路。發(fā)現(xiàn)了難啃的骨頭,才是最有意思的事。
Vue框架到底為我們做了什么?
- 數(shù)據(jù)和視圖分離,解耦(開放封閉原則)
1.所有數(shù)據(jù)和視圖不分離的,都會命中開放封閉原則
2.Vue 數(shù)據(jù)獨立在 data 里面,視圖在 template 中 - 以數(shù)據(jù)驅(qū)動視圖,只關(guān)心數(shù)據(jù)變化,dom 操作被封裝
- 使用原生js是直接通過操作dom來修改視圖,例如
ducument.getElementById('xx').innerHTML="xxx"- 以數(shù)據(jù)驅(qū)動視圖就是,我們只管修改數(shù)據(jù),視圖的部分由框架去幫我們修改,符合開放封閉模式.
如何理解 MVVM ?
- MVC
- Model 數(shù)據(jù) → View 視圖 → Controller 控制器
- MVVM
- MVVM不算是一種創(chuàng)新
- 但是其中的 ViewModel 是一種創(chuàng)新
- ViewModel 是真正結(jié)合前端應(yīng)用場景的實現(xiàn)
- 如何理解MVVM
- MVVM - Model View ViewModel,數(shù)據(jù),視圖,視圖模型
- 三者與 Vue 的對應(yīng):view 對應(yīng) template,vm 對應(yīng) new Vue({…}),model 對應(yīng) data
- 三者的關(guān)系:view 可以通過事件綁定的方式影響 model,model 可以通過數(shù)據(jù)綁定的形式影響到view,viewModel是把 model 和 view 連起來的連接器
我們來看一下最后我們要達到的效果

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


以上就是我們簡單搭建的項目目錄結(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,沒錯。那我們再來看看Vue的用法,幫大家回顧一下
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
Hello Vue!
我們已經(jīng)成功創(chuàng)建了第一個 Vue 應(yīng)用!看起來這跟渲染一個字符串模板非常類似,但是 Vue 在背后做了大量工作。現(xiàn)在數(shù)據(jù)和 DOM 已經(jīng)被建立了關(guān)聯(lián),所有東西都是響應(yīng)式的。我們要怎么確認(rèn)呢?打開你的瀏覽器的 JavaScript 控制臺 (就在這個頁面打開),并修改 app.message 的值,你將看到上例相應(yīng)地更新。
接下來是我們的函數(shù)類
(function () {
//初始化類
class MiniMVVM {...}
//模板編譯類
class TemplateCompile {...}
//數(shù)據(jù)劫持類,響應(yīng)式
class Observer {...}
//觀察者
class Watcher {...}
//發(fā)布訂閱類 自定義事件
class Emitter {...}
//存儲指令和工具方法
let CompileTool = {...};
window.MiniMVVM = MiniMVVM;
})();
這是我們mini版的函數(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è)計來寫我們mini版。
下面我們就來簡單的分析下我們這個函數(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) {
//驗證是否存在,是否是對象
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] 是否是對象
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)建了一個proxy對象。
//模板編譯類
class TemplateCompile {
constructor(el, vm) {
//1,獲取元素
this.el = (el.nodeType === 1 ? el : document.querySelector(el));
//2,獲取vm實例
this.vm = vm;
//如果元素存在,然后我們進行編譯
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é)點(包括元素解點)
let childNodes = fragment.childNodes;
//2,遍歷循環(huán)每個節(jié)點
Array.from(childNodes).forEach(node => {
//如果是元素節(jié)點
if (node.nodeType === 1) {
//遞歸遍歷子節(jié)點
this.cpmpile(node);
//處理我們的元素節(jié)點
this.complieElement(node);
} else {
//處理我們的文本節(jié)點
this.complieText(node);
}
})
}
//編譯處理
complieText(nodeText) {
//獲取文本節(jié)點的內(nèi)容
let exp = nodeText.textContent;
//這里我們通過正則獲取{{}}里面的數(shù)據(jù)
let re = /{{[^}]+}}/g;
if (re.test(exp)) {
//渲染頁面數(shù)據(jù)
CompileTool.text(nodeText, this.vm, exp)
}
}
//編譯處理元素節(jié)點
complieElement(node) {
console.log(node);
//取出所有的屬性
let attrs = node.attributes;
//邊里屬性 檢測是否具備‘-v’
Array.from(attrs).forEach(attr => {
console.log(attr.name);
if (attr.name.includes('v-')) {
//如果是v-attr 這樣的指令 我們?nèi)ノ覀儗?yīng)的尋找屬性值字符串所對應(yīng)的值
let exp = attr.value;
let type = exp.split('-')[1];
//調(diào)用model指令對應(yīng)的方法,渲染頁面數(shù)據(jù)
CompileTool.model(node, this.vm, exp);
}
})
}
}
這是我們模板類代碼,這里主要是解析我們定義好的模板語法,下面我們再來看下我們觀察者類
//觀察者
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;
//獲取這個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è)計模式的都應(yīng)該知道觀察者模式,這里就不做過多的解釋,有興趣的朋友可以百度一下。
剩下的就是我們的發(fā)布訂閱
//發(fā)布訂閱類 自定義事件
class Emitter {
constructor() {
this.subs = [];
}
//添加一個訂閱
addSub(watcher) {
this.subs.push(watcher);
}
//通知執(zhí)行訂閱
notify() {
this.subs.forEach(v => v.update());
}
}
通過以上不到300的行的代碼,我們就實現(xiàn)了簡單的雙向數(shù)據(jù)綁定
有朋友會說你現(xiàn)在沒有其他的文件以及配置,是的,不需要我們也是能實現(xiàn)數(shù)據(jù)的雙向綁定,這里的剩下的webpack打包,webpack打包這個算是比較簡單了,對于學(xué)習(xí)前端vue來說,那是再熟悉不過的了,這里就不過多的介紹了,下一章節(jié)我們在繼續(xù)探討webpack打包腳本詳細介紹。
下面提供幾個學(xué)習(xí)es地址大家可以去看一下,中英文都有。
ES5地址
ES6地址
非常感謝您能抽出時間來閱讀,如果對你有所幫助,歡迎轉(zhuǎn)發(fā)分享,您的轉(zhuǎn)發(fā)分享就是對我最大的鼓勵和支持。