第一天
上课时间是:6月9、13、16、20、23、27日 晚8点 (每天晚上8点开始) 上课时间是2-3个小时
课程分为两个阶段:
前4天深入讲解
Vue2
中的源码后两天讲解
Vue3
实战 +Vue3
原理剖析
珠峰前端架构课:原价9200现在是7.5折 (名额仅限15人) 后续价格是7500元。
一.开发环境搭建
Rollup
是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码, rollup.js
更专注于Javascript
类库打包 (开发应用时使用Wwebpack
,开发库时使用Rollup
)
安装rollup
环境
npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D
1
rollup.config.js
文件编写
import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
input: './src/index.js',
output: {
format: 'umd', // 模块化类型
file: 'dist/umd/vue.js',
name: 'Vue', // 打包后的全局变量的名字
sourcemap: true
},
plugins: [
babel({
exclude: 'node_modules/**'
}),
process.env.ENV === 'development'?serve({
open: true,
openPage: '/public/index.html',
port: 3000,
contentBase: ''
}):null
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
配置.babelrc文件
{
"presets": [
"@babel/preset-env"
]
}
1
2
3
4
5
2
3
4
5
执行脚本配置
"scripts": {
"build:dev": "rollup -c",
"serve": "cross-env ENV=development rollup -c -w"
}
1
2
3
4
2
3
4
二.Vue响应式原理
导出vue
构造函数
import {initMixin} from './init';
function Vue(options) {
this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;
1
2
3
4
5
6
7
2
3
4
5
6
7
init
方法中初始化vue
状态
import {initState} from './state';
export function initMixin(Vue){
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options
// 初始化状态
initState(vm)
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
根据不同属性进行初始化操作
export function initState(vm){
const opts = vm.$options;
if(opts.props){
initProps(vm);
}
if(opts.methods){
initMethod(vm);
}
if(opts.data){
// 初始化data
initData(vm);
}
if(opts.computed){
initComputed(vm);
}
if(opts.watch){
initWatch(vm);
}
}
function initProps(){}
function initMethod(){}
function initData(){}
function initComputed(){}
function initWatch(){}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.初始化数据
import {observe} from './observer/index.js'
function initData(vm){
let data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(vm) : data;
observe(data);
}
1
2
3
4
5
6
2
3
4
5
6
2.递归属性劫持
class Observer { // 观测值
constructor(value){
this.walk(value);
}
walk(data){ // 让对象上的所有属性依次进行观测
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++){
let key = keys[i];
let value = data[key];
defineReactive(data,key,value);
}
}
}
function defineReactive(data,key,value){
observe(value);
Object.defineProperty(data,key,{
get(){
return value
},
set(newValue){
if(newValue == value) return;
observe(newValue);
value = newValue
}
})
}
export function observe(data) {
if(typeof data !== 'object' && data != null){
return;
}
return new Observer(data);
}
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
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
3.数组方法的劫持
import {arrayMethods} from './array';
class Observer { // 观测值
constructor(value){
if(Array.isArray(value)){
value.__proto__ = arrayMethods; // 重写数组原型方法
this.observeArray(value);
}else{
this.walk(value);
}
}
observeArray(value){
for(let i = 0 ; i < value.length ;i ++){
observe(value[i]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
重写数组原型方法
let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
];
methods.forEach(method => {
arrayMethods[method] = function (...args) {
const result = oldArrayProtoMethods[method].apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2)
default:
break;
}
if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
return result
}
})
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
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
增加__ob__属性
class Observer {
constructor(value){
Object.defineProperty(value,'__ob__',{
enumerable:false,
configurable:false,
value:this
});
// ...
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
给所有响应式数据增加标识,并且可以在响应式上获取
Observer
实例上的方法
4.数据代理
function proxy(vm,source,key){
Object.defineProperty(vm,key,{
get(){
return vm[source][key];
},
set(newValue){
vm[source][key] = newValue;
}
});
}
function initData(vm){
let data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(vm) : data;
for(let key in data){ // 将_data上的属性全部代理给vm实例
proxy(vm,'_data',key)
}
observe(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
三.模板编译
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
// 初始化状态
initState(vm);
// 页面挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果没有render方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有el
if (!template && el) {
template = el.outerHTML;
}
const render= compileToFunctions(template);
options.render = render;
}
}
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
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
将template
编译成render函数
1.解析标签和内容
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
function start(tagName,attrs){
console.log(tagName,attrs)
}
function end(tagName){
console.log(tagName)
}
function chars(text){
console.log(text);
}
function parseHTML(html){
while(html){
let textEnd = html.indexOf('<');
if(textEnd == 0){
const startTagMatch = parseStartTag();
if(startTagMatch){
start(startTagMatch.tagName,startTagMatch.attrs);
continue;
}
const endTagMatch = html.match(endTag);
if(endTagMatch){
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
let text;
if(textEnd >= 0){
text = html.substring(0,textEnd);
}
if(text){
advance(text.length);
chars(text);
}
}
function advance(n){
html = html.substring(n);
}
function parseStartTag(){
const start = html.match(startTagOpen);
if(start){
const match = {
tagName:start[1],
attrs:[]
}
advance(start[0].length);
let attr,end;
while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
advance(attr[0].length);
match.attrs.push({name:attr[1],value:attr[3]});
}
if(end){
advance(end[0].length);
return match
}
}
}
}
export function compileToFunctions(template){
parseHTML(template);
return function(){}
}
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
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
2.生成ast语法树
语法树就是用对象描述js
语法
{
tag:'div',
type:1,
children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
attrs:[{name:'zf',age:10}],
parent:null
}
1
2
3
4
5
6
7
2
3
4
5
6
7
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
function createASTElement(tagName,attrs){
return {
tag:tagName,
type:ELEMENT_TYPE,
children:[],
attrs,
parent:null
}
}
function start(tagName, attrs) {
let element = createASTElement(tagName,attrs);
if(!root){
root = element;
}
currentParent = element;
stack.push(element);
}
function end(tagName) {
let element = stack.pop();
currentParent = stack[stack.length-1];
if(currentParent){
element.parent = currentParent;
currentParent.children.push(element);
}
}
function chars(text) {
text = text.replace(/\s/g,'');
if(text){
currentParent.children.push({
type:TEXT_TYPE,
text
})
}
}
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
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
3.生成代码
template
转化成render函数的结果
<div style="color:red">hello {{name}} <span></span></div>
render(){
return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))
}
1
2
3
4
2
3
4
实现代码生成
function gen(node) {
if (node.type == 1) {
return generate(node);
} else {
let text = node.text
if(!defaultTagRE.test(text)){
return `_v(${JSON.stringify(text)})`
}
let lastIndex = defaultTagRE.lastIndex = 0
let tokens = [];
let match,index;
while (match = defaultTagRE.exec(text)) {
index = match.index;
if(index > lastIndex){
tokens.push(JSON.stringify(text.slice(lastIndex,index)));
}
tokens.push(`_s(${match[1].trim()})`)
lastIndex = index + match[0].length;
}
if(lastIndex < text.length){
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${tokens.join('+')})`;
}
}
function getChildren(el) { // 生成儿子节点
const children = el.children;
if (children) {
return `${children.map(c=>gen(c)).join(',')}`
} else {
return false;
}
}
function genProps(attrs){ // 生成属性
let str = '';
for(let i = 0; i<attrs.length; i++){
let attr = attrs[i];
if(attr.name === 'style'){
let obj = {}
attr.value.split(';').forEach(item=>{
let [key,value] = item.split(':');
obj[key] = value;
})
attr.value = obj;
}
str += `${attr.name}:${JSON.stringify(attr.value)},`;
}
return `{${str.slice(0,-1)}}`;
}
function generate(el) {
let children = getChildren(el);
let code = `_c('${el.tag}',${
el.attrs.length?`${genProps(el.attrs)}`:'undefined'
}${
children? `,${children}`:''
})`;
return code;
}
let code = generate(root);
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
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
render
函数
4.生成export function compileToFunctions(template) {
parseHTML(template);
let code = generate(root);
let render = `with(this){return ${code}}`;
let renderFn = new Function(render);
return renderFn
}
1
2
3
4
5
6
7
2
3
4
5
6
7
第二天 →