vue2源码

Vue2源码学习

感觉工作了几个月了,也该看一看源码了,然后就捣鼓出了个盗版vue.js

实现的功能有模板渲染,生命周期,数据劫持,双向绑定,添加点击事件等很基础的功能,下面是源码

每天看掘金上的源码理论讲解,什么compile和watcher之类的看麻了,实现一下之后就有很大的感悟

首先在Vue的类中将$el和$data挂载在实例对象上,方便后续的操作

模板的编译思想就是,设置正则匹配,获取根节点后,再去获取根节点的子节点,循环去匹配文本节点,利用字符串的replace方法结合正则表达式,匹配到设置了模板字符串的文本节点和键,再利用键从data中获取相应值去替换文本节点

生命周期简单些,就是传递几个函数,判断类型,如果是函数就执行,顺序不能随便放,执行顺序是固定的,后期需要通过bind改变this指向

这是html内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
copyvye.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{ str }}</h1>
<h2>{{ b }}</h2>
<input type="text" v-model="modelStr">
<button @click="changeStr">change str</button>
<h1>{{ modelStr }}</h1>
</div>
<script src="./copyVue.js"></script>
<script>
new Vue({
el:"#app",
data:{
str:'hao hello',
b:'好嗨欧',
modelStr:'v-model关联字符'
},
methods: {
changeStr(){
this.str = '我终于更新了'
}
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
}
})
</script>
</body>
</html>

这是copyVue.js内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
copyVue.js
class Vue {
constructor(options){
if(typeof options.beforeCreate === 'function'){
options.beforeCreate.bind(this)()
}
this.$options = options
this.$data = options.data
this.$watchEvent = {}
this.proxyData()
this.observe()
if(typeof options.created === 'function'){
options.created.bind(this)()
}
if(typeof options.beforeMount === 'function'){
options.beforeMount.bind(this)()
}
this.$el = document.querySelector(options.el)
if(typeof options.mounted === 'function'){
options.mounted.bind(this)()
}
this.compile(this.$el)
}
// 劫持数据 此时并未更新视图
proxyData(){
for(let key in this.$data){
Object.defineProperty(this,key,{
get(){
return this.$data[key]
},
set(val){
this.$data[key] = val
}
})
}
}
// 数据被修改但是视图并未更新
observe(){
for(let key in this.$data){
let value = this.$data[key]
let that = this
Object.defineProperty(this.$data,key,{
get(){
return value
},
set(val){
value = val
if(that.$watchEvent[key]){
that.$watchEvent[key].forEach(item => {
item.update()
})
}
}
})
}
}
compile(node){
let reg = /\{\{(.*?)\}\}/g;
node.childNodes.forEach(item => {
// 首先需要判断节点类型
if(item.nodeType === 1){
if(item.hasAttribute('@click')){
let vmKey = item.getAttribute('@click').trim()
item.addEventListener('click',(event)=>{
this.eventFn = this.$options.methods[vmKey].bind(this)
this.eventFn(event)
})
}
if(item.hasAttribute('v-model')){
let vmKey = item.getAttribute('v-model').trim()
if(this.hasOwnProperty(vmKey)){
item.value = this[vmKey]
}
item.addEventListener('input',(event)=>{
this[vmKey] = item.value
})
}
if(item.childNodes.length > 0){
this.compile(item)
}
}
if(item.nodeType === 3){
// 使用另外一个变量保存文本节点的内容
let nodeContent = item.textContent
item.textContent = nodeContent.replace(reg,(match,vmKey)=>{
vmKey = vmKey.trim()
if(this.hasOwnProperty(vmKey)){
let watcher = new Watch(this,vmKey,item,'textContent')
if(this.$watchEvent[vmKey]){
this.$watchEvent[vmKey].push(watcher)
}else{
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watcher)
}
}
return this.$data[vmKey]
})
}
})
}
}
class Watch {
constructor(vm,key,node,attr){
this.vm = vm
this.key = key
this.node = node
this.attr = attr
}
update(){
this.node[this.attr] = this.vm[this.key]
}
}

loading……