從vue的的使用開始想,開始寫
- vue的基本使用我想大家都會了,看下面的代碼
<div id="app">
<input type="text" v-model="inputVal" style="width:600px">
<br />
這是輸入框的值:{{inputVal}}
<hr />
我是{{person.name}},
<br />
我今年 {{person.age}}
<ul>
<li>das</li>
<li>asd</li>
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el:"#app",
data:{
inputVal: '輸入框的值',
person:{
name: 'jack',
age: 18
}
}
})
</script>
最基本和重要的兩點:
- 數(shù)據(jù)的雙向綁定(input值變,下面inputVal的值也變)
- 模版渲染數(shù)據(jù)(將person的值渲染到了頁面中去)
- 先不考慮這兩點,我們該如何去實現(xiàn)我們的vue;
- new Vue ,那說明vue一定是一個構(gòu)造函數(shù)
- 傳遞了兩個參數(shù)(后期在擴充)el, data
ok,那我們基于這兩個點,先寫一個構(gòu)造函數(shù)vue
class Vue{
constructor(options){
this.$el = options.el;
this.$data = options.data;
if(this.$el){
new Compiler(this.$el,this.$data,this)
}
}
}
顯而易見,這里面就是一個簡單的數(shù)據(jù)保存操作,然后我們需要新建一個編譯類,這個類的目的就是為了編譯,讓vue這個基礎類不做太多的邏輯處理,只是一個接受數(shù)據(jù),來調(diào)度這個項目,便于維護,這是一個編程習慣吧!
ok,現(xiàn)在就去看看complier這個類怎么去做的。
- 創(chuàng)建好vue基類以后,去創(chuàng)建編譯類方法,complier;
明確好這個complier類是要做什么
- 把#app里面的dom節(jié)點全部轉(zhuǎn)移到fragment這個文檔隨便中去
- 編譯我們的fragment里面的文檔碎片的內(nèi)容,將數(shù)據(jù)映射到視圖
- 再將fragment添加回#app中去;
看代碼
class Compiler{
constructor(el,data,vm){
this.$el = el;
this.data = data;
this.vm = vm;
let node = this.isElementNode(this.$el) ? this.$el : document.querySelector(options.el)
// 避免重繪回流,一次性將節(jié)點全部加載到文檔碎片中去
let fragment = this.nodeToFragment(node);
// 編譯
this.compile(fragment)
// 添加到文檔中去
node.appendChild(fragment)
}
// 判斷用戶傳進來的是不是node節(jié)點
isElementNode(el){
return el.nodeType === 1;
}
}
上面的代碼注釋已經(jīng)很詳細,大家又不懂的可以留言
上面這些代碼完成了第一步的需求了,然后我們開始最重要的第二步;
這個compile函數(shù)也是最關鍵的,去看看compile做了什么把;
// 編譯
compile(fragment){
[...fragment.childNodes].forEach((e)=>{
if(e.nodeType === 1){
// 因為里面還有子元素,所以還需要在遍歷一遍
this.compile(e);
[...e.attributes].forEach((attr,index)=>{
// arrt是個對象
let { name ,value } = attr;
name.startsWith('v-') && this.handleElementNode(name,value,e);
})
}
if(e.nodeType === 3){
let reg = /\{\{.+?\}\}/;
reg.test(e.textContent) && this.handleTextNode(e.textContent,e);
}
})
}
接受轉(zhuǎn)換好的fragment這個文檔碎片進行遍歷操作,
切記:這里面的fragment.childNodes是一個類數(shù)組,必須轉(zhuǎn)為數(shù)組在能操作
然后我們進行判斷,看他是一個元素節(jié)點還是一個文本節(jié)點。如果是一個元素節(jié)點,那我們繼續(xù)遞歸,讀出里面的子節(jié)點,直到文本節(jié)點為止;
如果是元素節(jié)點,那我們?nèi)∠滤麄兊膶傩裕纯从袥]有v-開頭的屬性,我們?nèi)〕鰜恚?br> 提示:然后對屬性再進行遍歷,遍歷的子節(jié)點是一個對象,對象,對象,真的很奇怪。。。,然后通過結(jié)構(gòu)的方法我們拿到了v-model和后面的表達式;
然后取出來我們在調(diào)用handleElementNode的方法轉(zhuǎn)換,傳入指令和指令后面的表達式,節(jié)點;等下看這個方法;如果是文本節(jié)點,我們需要匹配到{{}} 這種差值表達式,那利用差值表達式即可,如果符合這種我們就調(diào)用handleTextNode方法,傳入文本值和節(jié)點;
handleElementNode && handleTextNode
// 轉(zhuǎn)換指令元素類
handleElementNode(attr,val,node){
let value = this.getVal(val);
let [,dir] = attr.split('-');
CompileUtil[dir](node,value)
}
// 轉(zhuǎn)換差值表達式 文本類
handleTextNode(val,node){
let reg = /\{\{(.+?)\}\}/;
let newVal = val.replace(reg, (...arg) => {
return this.getVal(arg[1])
});
console.log(node)
node.textContent = newVal;
}
// 根據(jù)傳入的表達式獲取data的數(shù)據(jù)
getVal(expr){
let data = this.data;
return expr.split('.').reduce((pre,cur)=>{
return pre[cur]
},data)
}
上面的代碼很簡單,獲取值,然后賦值;
就是getVal的方法不好理解,就是我們傳入‘person.age’這樣的表達式,然后去data上面去取值,顯然我們需要先轉(zhuǎn)為數(shù)組然后依次去取,那么reduce最好實現(xiàn)不過了,這個在lodash庫中就是get方法,不理解也可以用數(shù)組循環(huán)做;
還有一個replace方法,很顯然我們有兩個不同的正則表達式,
let reg1 = /{{.+?}}/;
let reg2 = /{{(.+?)}}/;
下面就是比上面多一個括號,這個作用就是當我匹配到括號里面的內(nèi)容的時候拿出來成為一個分組,我在match replace這種方法里面就能拿到分組,就是我們拿到了{{ person.name }}中的person.name;上面只是用來匹配這樣的字符串,具體可以看replace方法,還可以看我git上面有一個關于正則的學習筆記地址;在編譯handleElementNode中有一個CompileUtil函數(shù),我們在這里其實有一個CompileUtil工具函數(shù),具體如下:
CompileUtil = {
model(node,val){
node.value = val;
},
html(){},
text(){},
}
大家如果用過vue,那么一看就知道啥意思了,不做多解釋了;
到現(xiàn)在為止,我們完成了第一步,data渲染成了view了,不信大家可以看看,html頁面有沒有渲染完成,然后我們進行比較重要的一步了,那就是數(shù)據(jù)雙向綁定了;先歇一會把。在第二節(jié)接著去實現(xiàn)把,一口氣打了這么多字有點小累了。