1.Vuex(现以Vuex3版本叙述)
Vuex3之所以能在组件中通过this.$store访问到store 的数据,因为在Vue.use(Vuex),Vuex源码中在install函数中通过applymixin 函数,使用Vue.mixin({beforeCreate:vuexinit } ),vuexinit中给每个组件都注入了store的实例,(this.$store = 我们给Vue.Config 传入的store),包含我们能使用的this.$store.commit这些函数。
大白话就是通过mixin混入到组件的实例中
1.1 为何重复install
这里为何重复install,何时会走入该逻辑?
在cdn引入Vue的时候, <script src="vue"></script>
1.2 Vuex4的注入方式呢?
Vuex 4数据响应方面与3大体相同,鉴于Vue3已经放弃mixin,所以取消了使用Mixin方式混入到实例,使用了单例模式,provide inject
2.Vue-router(手写一个简版的router[hash])
myRouter.js
class MyRouter {
constructor(config) {
// 参数组织
// 路由配置列表
this._routes = config.routes; // 数组,路由的配置项
// 路由历史栈
this.routeHistory = [];
this.currentUrl = '';
this.currentIndex = -1;
// 跳转中间变量
this.changeFlag = false;
// 流程调用
this.init();
}
init() {
// 监听hash变化
window.addEventListener(
'hashchange',
this.refresh.bind(this),
false
);
// load
window.addEventListener(
'load',
this.refresh.bind(this),
false
);
}
// 单页更新
refresh() {
// 1. 路由参数处理
if (this.changeFlag) {
this.changeFlag = false;
} else {
this.currentUrl = location.hash.slice(1) || '/';
// 去除分叉路径
this.routeHistory = this.routeHistory.slice(0, this.currentIndex + 1);
this.routeHistory.push(this.currentUrl);
this.currentIndex++;
}
// 2. 切换模块
let path = MyRouter.getPath();
let currentComponentName = '';
let nodeList = document.querySelectorAll('[data-component-name]');
// 查找当前路由名称对应
// find()
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].path === path) {
currentComponentName = this._routes[i].name;
break;
}
}
// 遍历控制节点模块展示
nodeList.forEach(item => {
if (item.dataset.componentName === currentComponentName) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
})
}
push(option) {
if (option.path) {
MyRouter.changeHash(option.path, option.query);
} else if (option.name) {
let path = '';
for (let i = 0; i < this._routes.length; i++) {
if (this._routes[i].name === option.name) {
path = this._routes[i].path;
break;
}
}
if (path) {
MyRouter.changeHash(path, option.query);
}
}
}
back() {
this.changeFlag = true;
// ……
}
front() {
this.changeFlag = true;
}
static getPath() {
let href = window.location.href;
let index = href.indexOf('#');
if (index < 0) {
return '';
}
href = href.slice(index + 1);
let searchIndex = href.indexOf("?");
if (searchIndex < 0) {
return href;
} else {
return href.slice(0, searchIndex);
}
}
static changeHash(path, query) {
if (query) {
let str = '';
for (let i in query) {
str += '&' + i + '=' + query[i];
}
window.location.hash
= str
? path + '?' + str.slice(1)
: path;
} else {
window.location.hash = path;
}
}
}
编写一个html文件引入上述myRouter进行测试使用
<!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>router</title>
</head>
<body>
<ul>
<li>
<a onclick="router.push({name: 'course'})">课程</a>
<a onclick="router.push({name: 'teacher'})">学子</a>
</li>
</ul>
<div class="main-content">
<div class="main-box" data-component-name="course">大前端</div>
<div class="main-box" data-component-name="teacher">LuyolG</div>
</div>
</body>
</html>
<script src="./myRouter.js"></script>
<script>
window.router = new MyRouter({
routes: [
{
path: "/course",
name: "course",
},
{
path: "/teacher",
name: "teacher",
},
],
});
</script>
<style>
[data-component-name] {
display: none;
}
</style>
3.Vue 响应式原理(这里会写个defineReactive)
这是Vue组件化简化的流程,我们可以跟着这个流程去理解响应式到底是在哪一步
这是个响应式的思维导图,其关键点在于Observe、Dep、Watcher
其中我们要知道,Dep有收集、派发功能,对应的就是:
收集:获取数据时(get),会触发依赖收集,这样我们就知道视图哪里用到我们这个数据
派发:我们每次修改数据时(set),只需通知有依赖我们这个数据的地方去更改即可
部分更详细的流程可查阅上方推荐的文章,这里我们手写一下响应式核心文件defineReactive
class Vue {
constructor(options) {
const data = options.data
this._data = data
// 数据劫持 => initData
_proxy(this, '_data', data)
// 核心逻辑
observe(data)
new Watch(this, function () {
return data.name + '创建响应式' // 这里读取data.name,触发get,打印'依赖收集'
}, function () {
console.log('watch cb:', this.value)
})
}
}
const _proxy = function (vm, sourceKey, data) {
const keys = Object.keys(data);
keys.forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm[sourceKey][key]
},
set(val) {
vm[sourceKey][key] = val
}
})
})
}
const observe = function (data) {
const ob = new Observer(data)
}
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key)
})
}
}
const defineReactive = function (obj, key) {
let val = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('依赖收集')
dep.depend()
return val
},
set(newVal) {
console.log('派发更新')
val = newVal
dep.notify()
}
})
}
class Dep {
constructor() {
this.id = Dep.uid++
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
removeSub(sub) {
const subIndex = this.subs.indexOf(sub)
this.subs.splices(subIndex, 1)
}
}
Dep.uid = 0
Dep.target = null
class Watch {
constructor(vm, render, cb) {
this.vm = vm
this.render = render
this.cb = cb
this.deps = []
this.depsIds = new Set()
this.newDeps = []
this.newDepsIds = new Set()
this.value = this.get()
this.cb(this.value)
}
get() {
Dep.target = this
this.newDeps = []
this.newDepsIds = new Set()
const value = this.render()
Dep.target = null
this.deps.forEach(oldDep => {
const notExistInNewDeps = !this.newDepsIds.has(oldDep.id)
if (notExistInNewDeps) {
oldDep.removeSub(this)
}
})
this.deps = this.newDeps
this.depsIds = this.newDepsIds
return value
}
addDep(dep) {
const depId = dep.id
if (!this.newDepsIds.has(depId)) {
this.newDeps.push(dep)
this.newDepsIds.add(depId)
if (!this.depsIds.has(depId)) {
dep.addSub(this)
}
}
}
update() {
this.value = this.get()
this.cb(this.value)
}
}
let luyolg = new Vue({
data: {
name: 'LuyolG',
study1: 100,
study2: 99
}
})
luyolg.study1 = 1
luyolg.study2 = 2
让我们一步步分析这段代码的执行顺序和打印顺序。
-
实例化 Vue 对象:
let luyolg = new Vue({ data: { name: 'LuyolG', study1: 100, study2: 99 } }) -
Vue 构造函数:
this._data = data将data赋值给this._data。- 调用
_proxy(this, '_data', data),将data中的属性代理到Vue实例上。 - 调用
observe(data),开始观察data中的属性。 - 创建
Watch实例,传入回调函数。
-
_proxy 函数:
- 遍历
data的属性,并使用Object.defineProperty将data的属性代理到Vue实例上。
- 遍历
-
observe 函数:
- 创建
Observer实例,调用walk方法遍历data的属性,并调用defineReactive。
- 创建
-
defineReactive 函数:
- 使用
Object.defineProperty将data的属性变成响应式属性。 - 在
get方法中打印依赖收集,并调用dep.depend()。 - 在
set方法中打印派发更新,并调用dep.notify()。
- 使用
-
Watch 构造函数:
- 调用
this.get(),设置Dep.target为当前Watch实例。 - 执行
this.render(),触发data.name的get方法,打印依赖收集。 - 调用
dep.depend(),将当前Watch实例添加到Dep的订阅者列表中。 - 执行回调函数
cb,打印watch cb: LuyolG创建响应式。
- 调用
-
修改属性:
luyolg.study1 = 1 luyolg.study2 = 2- 触发
study1和study2的set方法,分别打印派发更新。
- 触发
总结打印顺序:
`依赖收集`
`watch cb: LuyolG创建响应式`
`派发更新`
`派发更新`