vue的简单使用:
new Vue(options),options中配置需要的data,computed,methods,生命周期钩子等
- 通过胡子语法绑定变量,通过
v-bind(@)绑定methods中事件等
<div id="app">
<div class="user">姓名:{{user.name}}</div>
<input type="text" v-model="value.a">
<button @click="add">点我加1</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
user: {
name: '张三'
},
value: { a: 23 }
},
methods:{
add(){
this.value.a++
}
}
})
</script>
本次手写实现的功能:
1. 模板编译与渲染
- 主要采用
document.createDocumentFragment()方法创建文档碎片节点,生成虚拟dom,编译模板。
- 本次未使用抽象语法树方法编译模板,想了解这部分可以参考:vue源码--手写实现AST抽象语法树
- 未封装
h函数生成虚拟dom,未封装patch进行diff与渲染dom,想了解可参考:虚拟dom与diff算法
- 只通过正则表达式解析胡子语法,想了解mustache模板引擎实现可参考:手写vue的胡子语法
2. 数据响应
- 编译模板、
watch监听时,涉及到使用data数据的地方都需要收集依赖,监听数据变化
- 注意:本次手写vue复用了之前写过的数据响应式代码。详情请参考:vue数据响应式原理
3. 指令编译
- 以
v-model为例
- 编译时获取节点的所有属性,如果是v-model="data",则让当前节点node.value=data,并监听其input事件,触发input时,改变this.data的值
4. 事件监听
- 以
@click为例
- 编译时获取节点的属性,如果是@click,则让当前node监听这个方法,并执行其回调
5. 生命周期
- 以
created为例
- 在初始化数据完成后,调用created的回调函数
- 生命周期具体执行时间,可以参考Vue官网
实现代码
Vue.js
- Vue构造函数,初始化vue实例,监听
watch中的属性值,执行生命周期钩子等
- 代码中用到的
observe函数与Watcher构造函数,请参考:vue数据响应式原理 中的完整代码部分
import Compile from "./Compile"
import { observe, Watcher } from "./initData"
export default class Vue {
constructor(options) {
this.$options = options
this._data = options.data
observe(this._data)
this._initData(this._data)
this._initData(options.methods)
this.$options.created.call(this)
this._initWatch(options.watch)
new Compile(options.el, this)
}
_initData(data) {
let self = this
Object.keys(data).forEach(key => {
Object.defineProperty(self, key, {
get() {
return data[key]
},
set(val) {
data[key] = val
}
})
})
}
_initWatch(watch) {
let self = this
Object.keys(watch).forEach(key => {
new Watcher(self, key, watch[key])
})
}
}
Compile.js
- Compile构造函数,用于编译模板,包括解析:
v-moel,@click,{{name}}等
- 代码中用到的
parsePath函数与Watcher构造函数,请参考:vue数据响应式原理 中的完整代码部分
import { parsePath, Watcher } from "./initData"
export default class Compile {
constructor(el, vue) {
this.$el = document.querySelector(el)
this.$vue = vue
if (this.$el) {
let $fragment = this.node2Fragment(this.$el)
this.compile($fragment)
this.$el.appendChild($fragment)
}
}
node2Fragment(el) {
let fragment = document.createDocumentFragment();
let ch
while (ch = el.firstChild) {
fragment.appendChild(ch)
}
return fragment
}
compile(el) {
let txtReg = /{{(.*?)}}/
el.childNodes.forEach(ch => {
if (ch.nodeType == 1) {
this.compileElement(ch)
} else if (ch.nodeType == 3 && txtReg.test(ch.textContent)) {
let word = ch.textContent.match(txtReg)[1]
this.compileText(ch, word, txtReg)
}
})
}
compileElement(node) {
Array.from(node.attributes).forEach(attr => {
if (attr.name.indexOf('v-') == 0) {
let directive = attr.name.slice(2)
let exp = attr.value
if (directive == 'model') {
let data = parsePath(exp)(this.$vue)
node.value = data
new Watcher(this.$vue, exp, newVal => {
node.value = newVal
})
node.addEventListener('input', e => {
let newVal = e.target.value
this.setValue(this.$vue, exp, newVal)
})
}
}
if (attr.name.indexOf('@') == 0) {
let event = attr.name.slice(1)
let exp = attr.value
node.addEventListener(event, this.$vue[exp].bind(this.$vue))
}
})
this.compile(node)
}
compileText(node, word, txtReg) {
let oldText = node.textContent
let value = parsePath(word)(this.$vue)
node.textContent = oldText.replace(txtReg, value)
new Watcher(this.$vue, word, val => {
node.textContent = oldText.replace(txtReg, val)
})
}
setValue(obj, exp, newVal) {
let arr = exp.split('.')
let res = obj
arr.forEach((item, i) => {
if (i == arr.length - 1) {
res[item] = newVal
} else {
res = res[item]
}
})
}
}
index.html 测试代码
<div id="app">
<div class="user">
<ul>
<li>姓名:{{user.name}}</li>
<li>年龄:{{user.age}}</li>
<li>性别:{{user.gender}}</li>
</ul>
</div>
<input type="text" v-model="value.a">
<div>内容:{{value.a}}</div>
<button @click="add">点我加1</button>
</div>
<script src="index.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
user: {
name: '张三',
age: 18,
gender: '男',
},
value: {
a: 11
}
},
watch: {
'user.name'(newVal, oldVal) {
console.log(`watch监听:user的name发生改变了,新值${newVal},旧值${oldVal}`)
}
},
created() {
console.log('created:this是',this)
},
methods:{
add(){
this.value.a++
}
}
})
</script>
效果:
