Vue 源码学习
资料
熟悉设计模式
AST 抽象语法树
htmlParse 解析器
wue 仿Vue实现
Vue.js 源码学习笔记
Virtual DOM patching algorithm based on Snabbdom
逐行学习vue 源码
组件的本质
以下是个人学习vue 源码的先后学习过程:(假设我是小白,按照下面顺序学习会轻松很多,循序渐进)
-
学习
正则表达式
-
学习js 设计模式,重点
观察者模式
50行代码的MVVM,感受闭包的艺术 -
了解
AST 抽象语法树
的概念,并通过博文开头的资料 htmlParse解析器 中分析parse
原理 -
参看githut 上的开源
仿 Vue 实现项目
wue 仿Vue实现、Vue 源码注释版(注释版可以放在后面看) -
查看调试
vue.js 2.1.3
版本单文件脚本(或其他新版本) https://cdn.bootcss.com/vue/2.1.3/vue.js -
学习
es6
模块化编程,学习rollup
构建工具,学习flow
类型检查工具 -
es6 版本的vue 学习 源码调试方法,对应版本V2.5.9
-
待补充~
步骤5 举例
单步调试如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 版本:vue 2.1.3 -->
<script type="text/javascript" src="./vue.js"></script>
</head>
<body>
<div id="app">
<div><span v-text="reverse"></span></div>
<div><span v-text="tip"></span></div>
<div><bar :age="info.age"></bar></div>
</div>
<script>
let bar = {
name: 'Bar',
template: '<div><span>name: {{name}}</span><br/><span>age: {{age}}</span></div>',
props: [ 'age' ],
created() {
console.log('child created.')
},
data() {
return {
name: 11
}
}
}
var app = new Vue({
el: "#app",
components: {
bar: bar
},
created() {
console.log('created.')
},
data() {
return {
message: "hello.",
info: {
age: 12
}
}
},
computed: {
reverse() {
return this.message.split("").reverse().join("")
},
tip() {
return `${this.message} world.`
}
}
})
setTimeout(()=>{
app.info.age = 23
}, 400)
</script>
</body>
</html>
编译过程中会生成render code
如下:
_h(
'div',
{attrs:{"id":"app"}},
[
_h(
'div',
[
_h('span',{domProps:{"textContent":_s(reverse)}})]),
" ",
_h(
'div',
[_h('span',{domProps:{"textContent":_s(tip)}})]
),
" ",
_h('div',[_h('bar',{attrs:{"age":info.age}})]
)
]
)
其中_h
和 _s
方法分别对应:
// shorthands used in render functions
Vue.prototype._h = createElement;
// toString for mustaches
Vue.prototype._s = _toString;
官网example 代码对应render code示例:render code 示例
大体过程:
render code
的执行,即代码调用vm._render()
, 后生成了包含dom 结构的vnode
实例;vm
的patch
调用会把vnode 树
转为document 的对象并挂在vm.$el
上,并挂载到根节点上,如示例中的<div id='app'>
上;
代码案例
示例1:
index.html
<body>
<div id="demo">
<div v-text="message"></div>
<span>{{info.age}}</span>
</div>
<script src="./app.js"></script>
</body>
app.js
var demo = new Vue({
el: '#demo',
data: {
info: {
name: {
firstName: 'Lili'
}
}
},
computed: {
message () {
return 'Hello' + this.info.name.firstName
}
},
created: function () {
this.init()
},
methods: {
init () {
setTimeout(() => {
this.info.age = 88
delete this.info.name.firstName
this.$forceUpdate()
}, 1000)
}
}
})
执行结果:
问题:上述示例如果注释掉 this.$forceUpdate() 的结果呢?
自己动手试一下吧
示例2:
index.html
<body>
<div id="demo">
<ul style="border-bottom: 1px solid plum;">
<li v-for="item in info.list" :key="item">{{item}}</li>
</ul>
</div>
<script src="./app.js"></script>
</body>
app.js
var demo = new Vue({
el: '#demo',
data: {
info: {
list: ['app', 'web', 'ios']
}
}
})
// 通过数组索引修改数值
demo.info.list[0] = 'apple'
执行结果:
问题1:那我如何修改数组的值呢?
(下文回答)
现在将app.js 做了如下调整:
var demo = new Vue({
el: '#demo',
data: {
info: {
list: ['app', 'web', 'ios']
}
},
created: function () {
this.init()
},
methods: {
init () {
this.info.list[0] = 'apple'
}
}
})
发现执行结果有效了:
问题2:为什么这样又可以了呢?
现在来揭晓答案:
问题1:原因是Object.defineProperty
的局限,set
方法在一些场景下不会触发(例如示例)。官方给的解决方法是利用Vue.set
或者vm.$set
。可参看官网列表渲染。问题2:原因是
init
方法是在created
生命周期中调用的,此时dom结构还没开始生成。如果放在mounted
生命周期中调用则同样不会更新。
(未完,待续)