本笔记系列文章,我主要以 UMD 完整版的 Vue为主来学习,UMD 版可以通过 <script> 标签直接用在浏览器中,而完整版是同时包含编译器和运行时的版本,这样就便于我们直接修改代码打印一些调试信息和编译模板。
Vue版本:2.6.14
1.配置demo环境
为了便于学习与调试,我们新建一个简单的 demo 项目。
mkdir demo
cd demo
web 服务器使用 Nginx,并给 Nginx 配置一个 vhost 以便可以通过浏览器访问页面,我这里配置 URL 为:http://localhost/demo/index.html
访问 Vue 的 GitHub:https://github.com/vuejs/vue,在 demo 目录下 Git 拉取 Vue 的源码
git clone https://github.com/vuejs/vue.git
这样 Vue 的整个源码就放在了 demo/vue 下。然后在 demo 根目录新建一个 html 静态文件 index.html,其中直接动态引入 /vue/dist/vue.js 文件。
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1">
<title>Vue demo</title>
<style type="text/css">
body {margin: 0;background-color: #c8e4f1;padding: 10px;}
#app {width: 560px;min-height: 600px;background-color: #ffffff;padding: 20px;box-sizing: border-box;box-shadow: 5px 5px 8px #adadad;position: relative;}
.blog {position: absolute;top: 0;right: 0;margin: 5px;padding: 0;font-size: 12px;color: #aaa;}
.cmpt-test {background-color: #dfdfdf;padding: 5px;}
.cmpt-hello {background-color: #aaaaaa;padding: 5px;}
.btns {margin: 20px 0 0;}
.btns button {padding: 5px 15px;background-color: #81cff3;outline: none;border: 0;cursor: pointer;}
.btns button:hover{background-color: #779f62;}
</style>
<script type="text/javascript">
window.log = console.log;
var js = document.createElement('script');
js.type = 'text/javascript';
js.src = './vue/dist/vue.js?v='+String(Date.now()).slice(6);
js.onload = function() {
Vue.component('Hello', {
name: 'hello',
template: `<div class="cmpt-hello">
<p>Hello component</p>
Hello: {{name}}; {{dclnet}} - code: {{code}}
</div>`,
data: function() {
return {
name: 'deng_'
}
},
props: {code: String},
computed: {
dclnet: function() {
return `fav: ${this.name}, you like [js,php,java,c/c++...]`;
}
},
watch: {
code: function(newVal, oldVal) {
log(`code is change ${oldVal} => ${newVal}`);
}
}
})
Vue.component('Test', {
name: 'test',
template: `<div class="cmpt-test">
<p>Test component</p>
<Hello :code="inputText"></Hello>
<p>my input: {{inputText}}</p>
<input type="text" v-model="inputText"/>
</div>`,
data: function() {
return {
inputText: ''
}
},
watch: {
inputText: function(newVal, oldVal) {
log(`inputText change ${oldVal} => ${newVal}`);
}
}
})
window.app = new Vue({
el: '#app',
data: {
id: 1,
dclnet: true,
items: ['item1', 'item2', 'item3', 'item4', 'item5'],
name: '',
obj: {
uid: 888,
lang: 'java',
platform: 'windows'
}
},
computed: {
fullname: function() {
return `[${this.name}-deng_]`;
}
},
watch: {
name: function(nval, oval) {
log('name changed: => ', nval)
}
},
methods: {
toggleShow: function() {
this.id += 1;
this.dclnet = !this.dclnet;
},
updateList: function() {
if (this.items.length == 5) {
this.items = ['newHead1', 'newHead2'].concat(this.items.slice(2, 4).concat(['tail1', 'tail2']))
} else {
this.items = ['item1', 'item2', 'item3', 'item4', 'item5'];
}
},
editName: function() {
log(this.name)
this.name = 'new_name';
}
},
beforeCreate: function() {
log("app component beforeCreate.");
},
created: function() {
log("app component created.", this.name);
},
beforeMount: function() {
log("app component beforeMount.");
},
mounted: function() {
log("app component mounted.");
this.$set(this.obj, 'child1', 111);
},
beforeUpdate: function() {
log("app component beforeUpdate.");
},
updated: function() {
log("app component updated.");
},
beforeDestroy: function() {
log("app component beforeDestroy.");
},
destroyed: function() {
log("app component destroyed.");
}
});
}
</script>
</head>
<body>
<script type="text/javascript">
document.body.appendChild(js);
</script>
<div id="app">
<h3 class="stext">Vue demo static text</h3>
<input type="text" id="ginput" v-model="name" autocomplete="off">
<p v-if="!dclnet" data-vid="89bc" class="tip">test v-if</p>
<p v-show="!dclnet">test v-show.</p>
<p key="tipkey" :class="id>1 ? 'tc': 'fc'" alt data-vid="a0bcf" id="tip" class="tip">name:{{name}} - id:{{id}} - fullname:{{fullname}}</p>
<ul :class="'test>-'+id" class="ullist">
<!--这是注释-->
<li v-for="(item,index) in items" :key="index">{{item}}</li>
</ul>
<Test></Test>
<div class="btns">
<button @click="toggleShow"> test-if-show </button>
<button @click="updateList"> update list </button>
<button @click="editName"> edit name </button>
</div>
<p class="blog">https://blog.csdn.net/dclnet</p>
</div>
</body>
</html>
2.Vue目录结构
新建好项目后,我们看到 /demo/vue 目录是如图这样的,其中我们主要是关注 src 和 dist 目录,其他目录我们可以暂时忽略。dist 目录中是已经生成好的不同功能版本的 Vue,为了便于调试与分析,我们后续章节直接在html中引用 vue.js 来学习。src 目录为 Vue 的源码目录,结构如下。
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
compiler 为编译相关的代码;
core 包含了 Vue 的核心代码,我们后续学习的重点;
platforms 主要是平台相关的,这里我们主要关注其中的 web 平台即可;
server 服务的相关的,因为这里我们主要学习 web 相关的 Vue,服务端的一般跳过;
sfc 为解析单文件组件的功能;
shared 包含了一些全局基本的功能函数等;
3.类型检查
Vue2 没有使用 TypeScript,源码直接就是 JavaScript 代码。为了便于开发与测试,Vue 使用了 Flow 这个 Facebook 贡献的类型检查工具。它的具体使用可自行查阅其官网学习 Flow: A Static Type Checker for JavaScript。这里我们只需要简单的了解一些相关知识即可。
使用 Flow 可以在各种需要定义变量的地方声明变量的类型以便 Flow 做检查,如:
var count: number = 0; // 定义为数字类型
var name: string = ''; // 定义为字符串类型
// 定义为数组类型 其中元素为数字类型
var arr: Array<number> = [1, 2, 3];
var arr: number[] = [1, 2, 3]; // 简写形式,实际同上
// 可选类型的数组
let arr1: ?number[] = null; // Works!
let arr2: ?number[] = [1, 2]; // Works!
let arr3: ?number[] = [null]; // Error!
// 元素 为可选类型 的数组
let arr1: (?number)[] = null; // Error!
let arr2: (?number)[] = [1, 2]; // Works!
let arr3: (?number)[] = [null]; // Works!
var obj: { foo?: boolean } = {}; // 定义对象的 可选属性
// 定义可选类型 vaule 可以为 number, null, or undefined
function acceptsMaybeNumber(value: ?number) {
// ...
}
// 不使用类型检查 可以声明为 any
function add(one: any, two: any): number {
return one + two;
}
// 传参可以为 string 或 number
function stringifyBasicValue(value: string | number) {
return '' + value;
}
// 定义函数入参类型 和 返回值类型
function concat(a: string, b: string): string {
return a + b;
}
// 函数返回值类型 与 传参类型 相同
function identity<T>(value: T): T {
return value;
}
Flow 还有很多知识可以学习,但是目前知道这些就可以阅读源码了。做好这些准备工作,我们就可以继续接下来的源码学习了。