最后一个课程,本质上可以模拟实现一个自己搭建的vue框架,包含了数据的监听与双向数据绑定,生命周期的创建,以及一些v-开头的自定义指令的实现。具体怎么操作的呢,接下来一步步整理。
app根节点里的内容
在index.html里,调试与运行咱自己的miniVue,包含:
1、获取{{}}里的值,如data里的name
2、各种自定义的v-开头的指令
3、有input框,测试双向数据绑定
4、有一个button,点击数据加1,测试是否实现了数据改变视图的功能
<div id="app">
{{a.b.c.d}} a.b.c.d的值
<hr />
你好呀 {{name}}!
<h2 class="title" v-if="isShowType1">hello {{name}}</h1>
<h2 class="title" v-show="isShowType2">hello world</h1>
<input type="text" v-model="a.b.c.d">
<input type="text" v-model="name">
<button onclick="add()">按我加1</button>
</div>
首页miniVue里的内容
实际的具体使用实例,有el挂载点,data数据对象,以及watch监听数据被改变时进行的一些操作,以及生命周期(未实现)
let vm = new miniVue({
el:'#app',
data:{
isShowType1: true,
isShowType2: true,
name: 'rita',
page: 'index',
a:{
b:{
c:{
d: 999
}
}
}
},
watch:{
name(){
console.log('watch: 正在试图改变 name属性');
}
},
})
一些有用的算法
getVueVal(obj, key)
可以访问obj里面key属性的值,以及包含点语法时的值,如obj.a.b.c
// 获取data中a.b.c的值,点语法
getVueVal(vue, exp) {
let val = vue
exp = exp.split('.')
exp.forEach(v => {
val = val[v]
})
return val
}
setVueVal(obj, key, newVal)
可以设置obj里面key的值,以及设置包含点语法时的值,如obj.a.b.c = newVal
// 设置a.b.c的值
setVueVal(vue, exp, newVal) {
let val = vue
exp = exp.split('.')
exp.forEach((k, i) => {
// 判断是否是点语法里的最后一个值 a.b.c里的c
if (i < exp.length - 1) {
val = val[k]
} else {
val[k] = newVal
}
})
return val
}
vue的模板编译核心:Compile类
constructor(el, vue) {
// vue实例
this.$vue = vue
// 获取挂载点
this.$el = document.querySelector(el)
// 转换与编译
if (this.$el) {
// 调用转换函数
let $fragment = this.node2Fragment(this.$el)
// 编译
this.compile($fragment)
// 上树
this.$el.appendChild($fragment)
}
}
如何实现v-开头的指令?
1、拿到属性列表,这里是数组,不是字符串
let nodeAttr = node.attributes
2、类数组对象变为数组
Array.prototype.slice.call(nodeAttr).forEach(attr => {})
3、解析指令
attr => 拿到 v-if = 'isShow'
attr.name..substring(2) => 拿到 v-if 后面的 if
4、vue指令都是v-开头的
if (aName.indexOf('v-') === 0) { 内部进行switch-case的查找与判定 }
// 处理标签 解析v-指令
compileElement(node) {
// 是属性列表 不是字符串
let nodeAttr = node.attributes
// 类数组对象变为数组
Array.prototype.slice.call(nodeAttr).forEach(attr => {
// 拿到 class='title'、v-if = 'isShow'
let aName = attr.name
let aValue = attr.value
let dir = aName.substring(2) // 拿到 v-if 后面的 if
// vue指令都是v-开头的
if (aName.indexOf('v-') === 0) {
let val = this.getVueVal(this.$vue, aValue)
// 在这里进行指令的处理
switch (dir) {
case 'model':
// v-if='isShow'为例子,aValue=if, val=isShow
console.log('发现了model指令: ', aValue, val);
break;
case 'for':
console.log('发现了for指令: ', aValue, val);
break;
case 'show':
console.log('发现了show指令: ', aValue, val);
break;
case 'if':
console.log('发现了if指令: ', aValue, val);
break;
}
}
})
}
如何实现v-model的双向数据绑定?
case 'model':
// v-if='isShow'为例子,aValue=if, val=isShow
console.log('发现了model指令: ', aValue, val);
// 数据改变 模板改变
new Watcher(this.$vue, aValue, value => {
node.value = value
})
let v = this.getVueVal(this.$vue, aValue)
node.value = v
// 模板改变 数据也要改变
node.addEventListener('input', e => {
let newVal = e.target.value
this.setVueVal(this.$vue, aValue, newVal)
v = newVal
})
break;
如何实现v-if与v-show?
- v-if的底层原理是设置style上的display属性为none
- v-show的底层原理是设置style上的visibility属性为hidden
case 'show':
console.log('发现了' + dir + '指令: ', aValue, val);
if (val) {
node.style.visibility = 'visible'
} else {
node.style.visibility = 'hidden'
}
break;
case 'if':
console.log('发现了' + dir + '指令: ', aValue, val);
if (val) {
node.style.display = 'block'
} else {
node.style.display = 'none'
}
break;