重点js基础/Vue框架/typescript/Css/node/工具/网络基础

  • Mvvm原理的解读以及模拟实现
  • Vuex源码以及模拟实现(a week)
  • Vue计算属性(原理及相关特性)
  • express完整应用(two week)

前期还是需要在好好学学vue相关的源码知识,特别是mvvm以及vuex的源码阅读,大概领会其思想,目的很简单,就是面试的时候,不能被面试官问倒。
后面会尽量往node/webpack/ast靠拢,如果可能有时间的话,算法还是要了解。
源码调试不方便,不知道是自己的问题还是没有找到规律,vuex的源码是放在vuex.esm.js中,在2.0版本的时候,源码中还含有src文件夹,里面含有未压缩的各模块代码,现在vuex不是这个样子

Mvvm的模拟实现

1
2
3
4
5
6
7
Observer 数据监听器,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用Object.defineProperty的getter和setter来实现。

Compile 指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。

Watcher 订阅者, 作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数。

Dep 消息订阅器,内部维护了一个数组,用来收集订阅者(Watcher),数据变动触发notify 函数,再调用订阅者的 update 方法。

Vuex源码以及模拟实现

源码这么多,不知道如何下手?不知道主线是什么,谁能告诉我如何把源码理清,其实,有好几篇文章里的思路还是比较明晰的,无奈自己太次,有点理解不了尤大大的用意,老是被一些api打断思路。
还有就是对数据结构没啥概念,比如说在deom里定义了两个模块,

1
2
3
4
5
6
{
modules: {
Test,
Fuck,
}
}

这个modules所包含的对象即使传入Store中的对象,即源码中的options

手写基础版的vuex

  • install

vuex对外暴露一个是install方法,传入vue,并把$cppStore绑定到组件上,到时候直接调用this.$cppStore.state即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let Vue: any
function install(initVue: any) {
Vue = initVue
Vue.mixin({
beforeCreate() {
const options = this.$options
console.log('this', options);
if (options && options.cppStore) {
this.$cppStore = typeof options.cppStore === 'function'
? options.cppStore()
: options.cppStore;
} else {
this.$cppStore = options.parent && options.parent.$cppStore;
}
}
})
}
  • CppStore

第二个是对外暴露一个 CppStore class,这个class需要传入一个对象,一般是这种样子的对象

1
2
3
4
5
6
{
state: {},
actions: {},
mutations: {},
getters: {}
}
  • 安装引入以及使用
    1
    2
    3
    4
    5
    6
    7
    import cppStore from './store/cppStore';
    new Vue({
    router,
    store,
    cppStore: (cppStore as any),
    render: (h) => h(App),
    }).$mount('#app');
    store/cppStore
    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
    import vue from 'vue'
    import CppVuex from './../../utils/vuex'
    vue.use(CppVuex)
    export default new CppVuex.CppStore({
    state: {
    count: 10000,
    data: {
    age: 20,
    name: ''
    }
    },
    actions: {
    addCount(context: any, val: number) {
    const obj = {
    age: val,
    name: 'cpp'
    }
    context.commit('CHANGE_COUNT', obj)
    }
    },
    mutations: {
    CHANGE_COUNT(state: any, val: any) {
    state.count += val.age
    state.data = val
    }
    },
    getters: {
    GetCount: (state: any) => {
    return state.count
    }
    }
    })

Vuex 乞丐版如下

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
// 第一版的vuex
let Vue: any
function install(initVue: any) {
Vue = initVue
Vue.mixin({
beforeCreate() {
const options = this.$options
console.log('this', options);
if (options && options.cppStore) {
this.$cppStore = typeof options.cppStore === 'function'
? options.cppStore()
: options.cppStore;
} else {
this.$cppStore = options.parent && options.parent.$cppStore;
}
}
})
}
class CppStore {
protected $options: any = {}
protected state: any = {}
protected mutations: any = {}
protected actions: any = {}
protected getters: any = {}
// dispatch: any = {}
// commit: any = {}
constructor(options: any = {}) {
// 普通传来的对象
this.$options = options
this.state = new Vue({
data: this.$options.state
})
this.$options.getters && this.handleGetters(this.$options.getters)
this.actions = this.$options.actions
this.mutations = this.$options.mutations
// this.commit = (type: any, arg: any) => {
// this.mutations[type](this.state, arg)
// }
const store = this
const { dispatch, commit } = this
// this.commit = function boundCommit (type: any, payload: any, options: any) {
// return commit.call(store, type, payload, options)
// }
}
// getters
protected handleGetters(getters: any) {
this.getters = {}
Object.keys(getters).forEach((key: any) => {
Object.defineProperty(this.getters, key, {
get() {
// 执行getters中的方法
return getters[key](this.state)
}
})
})
}
// 触发dispatch
protected dispatch(type: any, arg: any) {
this.actions[type]({
commit: this.commit,
state: this.state,
getters: this.getters,
dispatch: this.dispatch
}, arg)
}
// // 触发commit
protected commit = (type: any, arg: any) => {
this.mutations[type](this.state, arg)
}
}
export default {
CppStore,
install
}

intstall vuex安装

安装部分,核心就是给Vue注入一个store属性

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}

模拟action

vuex中通过dispatch触发action

  • 知识点
    如何判断函数是否是async函数
    1
    2
    console.log('是否是async函数', this.action[type].constructor);
    console.log('是否是async函数', this.action[type].constructor.name === 'AsyncFunction');

参考连接

手写简单的mvvm

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div id="app">
<h2>{{message}}</h2>
<input v-model="c" type="text">
</div>
<script src="./MVVM.js"></script>
<script>
let mvvm = new Mvvm({
el: '#app',
data: {
a: {
b: 1
},
c: 2,
message: 'this is Message'
}
});
console.log('mvvm', mvvm);
</script>

具体的实现:

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 创造一个实例
function Mvvm(options = {}) {
this.$options = options
let data = this._data = this.$options.data

// 数据劫持
observe(data)

//this 代理了 this._data
// mvvm._data.a.b 相等于 mvvm.a.b
for (let key in data) {
Object.defineProperty(this, key, {
configurable: true,
get() {
return this._data[key] // 访问this.a.b
},
set(newValue) {
this._data[key] = newValue;
}
})
}

// initComputed.call(this);
// 数据编译
new Compile(options.el, this)
}
// 数据劫持 给所有对象增加set get方法
function Observe(data = {}) {
let dep = new Dep()
// 给对象添加set get
for (let key in data) {
// 把data属性通过defineProperty的方式定义属性
let val = data[key];
// 递归继续向下找,实现深度的数据劫持
observe(val);
Object.defineProperty(data, key, {
configurable: true,
get() {
// 将watcher添加到订阅事件中 [watcher]
Dep.target && dep.addSub(Dep.target)
return val
},
set(newVal) {
if (val === newVal) return;
val = newVal;
observe(newVal)
}
})
}
}

// 方便递归调用
function observe(val) {
if (!val || typeof val !== 'object') {
return -1
}
return new Observe(val)
}

// 数据编译
function Compile(el, vm) {
// 将el挂载到实例上
vm.$el = document.querySelector(el);
// ?
let fragment = document.createDocumentFragment();
let child
while(child = vm.$el.firstChild) {
fragment.appendChild(child)
console.log('child', child);
console.log('fragment', fragment);
}
// 对 el内容进行替换
function replace(frag) {
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent;
let reg = /\{\{(.*?)\}\}/g; // 正则匹配{{}}
console.log('node', node);
if (node.nodeType === 3 && reg.test(txt)) {
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
console.log(placeholder); // 匹配到的分组 如:song, album.name, singer...
new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
return placeholder.split('.').reduce((val, key) => {
return val[key];
}, vm);
});
};
// 替换
replaceTxt();
}
if (node.nodeType === 1) { // 元素节点
let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组
Array.from(nodeAttr).forEach(attr => {
let name = attr.name; // v-model type
let exp = attr.value; // c text
if (name.includes('v-')){
node.value = vm[exp]; // this.c 为 2
}
// 监听变化
new Watcher(vm, exp, function(newVal) {
node.value = newVal; // 当watcher触发时会自动将内容放进输入框中
});

node.addEventListener('input', e => {
let newVal = e.target.value;
// 相当于给this.c赋了一个新值
// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
vm[exp] = newVal;
});
});
}
// 如果还有子节点,继续递归replace
if (node.childNodes && node.childNodes.length) {
replace(node);
}
})
}
// 替换内容
replace(fragment);
vm.$el.appendChild(fragment); // 文档碎片放到el元素中
}
// 发布订阅
function Dep() {
this.subs = []
}
Dep.prototype = {
addSub(sub) {
// 订阅就是放入subs
this.subs.push(sub)
},
// this.subs数组里的每一项,都有一个update方法
notify() {
console.log('this.subs', this.subs);
this.subs.forEach(sub => {
sub.update()
})
}
}
// 监听者
// 通过Watcher这个类创建的实例,都拥有update方法
function Watcher(vm, exp, fn) {
if (!vm && !exp) return;
this.vm = vm;
this.exp = exp;
this.fn = fn;
Dep.target = this;
let arr = exp.split('.');
let val = vm;
arr.forEach(key => { // 取值
val = val[key]; // 获取到this.a.b,默认就会调用get方法
});
Dep.target = null;
}
Watcher.prototype.update = function() {
// notify的时候值已经更改了
// 再通过vm, exp来获取新的值
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach(key => {
val = val[key]; // 通过get获取到新的值
});
this.fn(val)
}
// let watcher = new Watcher(() => console.log(111)); //
// let dep = new Dep();
// dep.addSub(watcher); // 将watcher放到数组中,watcher自带update方法, => [watcher]
// dep.addSub(watcher);
// dep.notify(); // 111, 111

yun….一头雾水,我太难了!!!