Vue2.x 响应式
手写迷你版
- 导出
Vue的构造函数,并实现__init方法
// 导出 Vue 的构造函数
export function Vue(options = {}) {
this.__init(options);
}
// initMixin
Vue.prototype.__init = function (options) {
this.$options = options;
// 假设这里就是一个 el,已经 querySelector 的
this.$el = options.el;
this.$data = options.data;
this.$methods = options.methods;
};
- 代理操作
proxy
Vue.prototype.__init = function (options) {
this.$options = options;
// 假设这里就是一个 el,已经 querySelector 的
this.$el = options.el;
this.$data = options.data;
this.$methods = options.methods;
// beforeCreate -- initState -- initData
// 代理操作:把 this.$data.message 代理到 this.message
proxy(this, this.$data);
};
// this.$data.message --> this.message 代理操作
function proxy(target, data) {
// target: this
// data: 数据对象
Object.keys(data).forEach(key => {
Object.defineProperty(target, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get() {
return data[key];
},
set(newVal) {
// 考虑 NaN 的情况
if (!isSameVal(data[key], newVal)) {
data[key] = newVal;
}
},
});
});
}
function isSameVal(a, b) {
return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
- 观察者
observer
Vue.prototype.__init = function (options) {
this.$options = options;
// 假设这里就是一个 el,已经 querySelector 的
this.$el = options.el;
this.$data = options.data;
this.$methods = options.methods;
// beforeCreate -- initState -- initData
// 代理操作:把 this.$data.message 代理到 this.message
proxy(this, this.$data);
// observer() - Object.defineProperty
// 依赖收集
observer(this.$data);
};
function observer(data) {
new Observer(data);
}
class Observer {
constructor(data) {
// 这里只考虑对象的情况
this.walk(data);
}
walk(data) {
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
}
}
// 把每一个 data 里面的数据收集起来
defineReactive(obj, key, value) {
let that = this;
// 如果 value 还是对象,递归处理
this.walk(value);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newVal) {
if (!isSameVal(value, newVal)) {
value = newVal;
// 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
that.walk(newVal);
}
},
});
}
}
function isSameVal(a, b) {
return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
- 视图怎么更新?
=>数据改变,视图才会更新,需要去观察 - 观察者(订阅者)
Watcher
// 视图怎么更新?
// 数据改变,视图才会更新,需要去观察
// 1、new Watcher(vm, 'num', () => { 更新视图上的 num 显示 })
class Watcher {
constructor(vm, key, cb) {
this.vm = vm; // vm 就是 Vue 的实例
this.key = key;
this.cb = cb;
// 2、此时 Dep.target 作为一个全局变量理解,放的就是这个 watcher
Dep.target = this;
// 3、一旦进行了这一句赋值,是不是就触发了这个值的 getter 函数
this.__old = vm[key];
// 把 Dep.target 删除
Dep.target = null;
}
// 8、执行所有的 cb 函数
update() {
const newVal = this.vm[this.key];
if (!isSameVal(newVal, this.__old)) {
this.cb(newVal);
}
}
}
- 目标(发布者)
Dep
// 每一个数据都要有一个 dep 的依赖
class Dep {
constructor() {
this.watchers = new Set(); // 定义一个存放所有 watcher 的集合数组
}
// 添加 watcher 方法
add(watcher) {
if (watcher && watcher.update) {
this.watchers.add(watcher);
}
}
// 7、让所有的 watcher 执行 update 方法
// 通知方法
notify() {
this.watchers.forEach(watcher => watcher.update());
}
}
- 在
Observer类中,在getter中触发依赖收集,在setter中通知更新
class Observer {
constructor(data) {
// 这里只考虑对象的情况
this.walk(data);
}
walk(data) {
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
}
}
// 把每一个 data 里面的数据收集起来
defineReactive(obj, key, value) {
let that = this;
// 如果 value 还是对象,递归处理
this.walk(value);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 4、对于 num 来说,要执行这一句
// 5、num 中的 dep,就有了这个 watcher
Dep.target && dep.add(Dep.target);
return value;
},
set(newVal) {
if (!isSameVal(value, newVal)) {
value = newVal;
// 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
that.walk(newVal);
// 6、重新 set 值,通知更新
dep.notify();
}
},
});
}
}
- 编译模板
Compiler
Vue.prototype.__init = function (options) {
this.$options = options;
// 假设这里就是一个 el,已经 querySelector 的
this.$el = options.el;
this.$data = options.data;
this.$methods = options.methods;
// beforeCreate -- initState -- initData
// 代理操作:把 this.$data.message 代理到 this.message
proxy(this, this.$data);
// observer() - Object.defineProperty
// 观察者
observer(this.$data);
// 编译模板
new Compiler(this);
};
// 编译模板
class Compiler {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.methods = vm.$methods;
this.compile(vm.$el);
}
// 这里是递归编译 #app 下面的所有的节点内容
compile(el) {
// 类数组
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (node.nodeType === 3) {
// 文本节点 <p>{{ name }}</p>
this.compileText(node);
} else if (node.nodeType === 1) {
// 元素节点 <input type="text" v-model="message" />
this.compileElement(node);
}
// 如果还有子节点,递归遍历
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
// 处理文本节点
compileText(node) {
// 匹配出来 {{message}}
let reg = /\{\{(.+?)\}\}/;
let value = node.textContent;
if (reg.test(value)) {
let key = RegExp.$1.trim();
// 首次开始赋值
node.textContent = value.replace(reg, this.vm[key]);
// 添加观察者
new Watcher(this.vm, key, val => {
// 数据改变时的更新
node.textContent = val;
});
}
}
// 处理元素节点
compileElement(node) {
// 简化,只做匹配 v-on 和 v-model 的匹配
// v-on:click='' / v-model=''
if (node.attributes.length) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name;
if (attrName.startsWith('v-')) {
// v- 指令匹配成功,可能是 v-on:click 或者 v-model
attrName = attrName.indexOf(':') > -1 ? attrName.substring(5) : attrName.substring(2);
let key = attr.value;
// 调用更新函数
this.update(node, key, attrName, this.vm[key]);
}
});
}
}
// 更新函数
update(node, key, attrName, value) {
if (attrName === 'model') {
node.value = value;
new Watcher(this.vm, key, val => (node.value = val));
node.addEventListener('input', () => {
this.vm[key] = node.value;
});
} else if (attrName === 'click') {
node.addEventListener(attrName, this.methods[key].bind(this.vm));
// node.addEventListener(attrName, () => {
// this.methods[key].call(this.vm);
// });
}
}
}
迷你版完整代码
./index.html
<!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>Vue 2.x</title>
<script type="module">
// ! 在 script 标签上加上 type=module 属性来改变方式,可以使用 import
// import { Vue } from './index.js';
import { Vue } from './vue2.js';
let vm = new Vue({
el: document.querySelector('#app'),
data: {
message: 'hello luyi',
num: 35,
},
methods: {
increase() {
this.num++;
},
},
});
</script>
</head>
<body>
<div id="app">
<h1>{{ message }}</h1>
<h2>{{ num }}</h2>
<input type="text" v-model="message" />
<input type="text" v-model="num" />
<button v-on:click="increase">【+】</button>
</div>
</body>
</html>
./vue2.js
// 导出 Vue 的构造函数
export function Vue(options = {}) {
this.__init(options);
}
// initMixin
Vue.prototype.__init = function (options) {
this.$options = options;
// 假设这里就是一个 el,已经 querySelector 的
this.$el = options.el;
this.$data = options.data;
this.$methods = options.methods;
// beforeCreate -- initState -- initData
// 代理操作:把 this.$data.message 代理到 this.message
proxy(this, this.$data);
// observer() - Object.defineProperty
// 观察者
observer(this.$data);
// 编译模板
new Compiler(this);
};
// this.$data.message --> this.message 代理操作
function proxy(target, data) {
// target: this
// data: 数据对象
Object.keys(data).forEach(key => {
Object.defineProperty(target, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get() {
return data[key];
},
set(newVal) {
// 考虑 NaN 的情况
if (!isSameVal(data[key], newVal)) {
data[key] = newVal;
}
},
});
});
}
function observer(data) {
new Observer(data);
}
class Observer {
constructor(data) {
// 这里只考虑对象的情况
this.walk(data);
}
walk(data) {
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
}
}
// 把每一个 data 里面的数据收集起来
defineReactive(obj, key, value) {
let that = this;
// 如果 value 还是对象,递归处理
this.walk(value);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 4、对于 num 来说,要执行这一句
// 5、num 中的 dep,就有了这个 watcher
Dep.target && dep.add(Dep.target);
return value;
},
set(newVal) {
if (!isSameVal(value, newVal)) {
value = newVal;
// 赋值进来的新值是没有响应式的,所以要再 walk 一次,给到响应式
that.walk(newVal);
// 6、重新 set 值,通知更新
dep.notify();
}
},
});
}
}
// 视图怎么更新?
// 数据改变,视图才会更新,需要去观察
// 1、new Watcher(vm, 'num', () => { 更新视图上的 num 显示 })
class Watcher {
constructor(vm, key, cb) {
this.vm = vm; // vm 就是 Vue 的实例
this.key = key;
this.cb = cb;
// 2、此时 Dep.target 作为一个全局变量理解,放的就是这个 watcher
Dep.target = this;
// 3、一旦进行了这一句赋值,是不是就触发了这个值的 getter 函数
this.__old = vm[key];
// 把 Dep.target 删除
Dep.target = null;
}
// 8、执行所有的 cb 函数
update() {
const newVal = this.vm[this.key];
if (!isSameVal(newVal, this.__old)) {
this.cb(newVal);
}
}
}
// 每一个数据都要有一个 dep 的依赖
class Dep {
constructor() {
this.watchers = new Set(); // 定义一个存放所有 watcher 的集合数组
}
// 添加 watcher 方法
add(watcher) {
if (watcher && watcher.update) {
this.watchers.add(watcher);
}
}
// 7、让所有的 watcher 执行 update 方法
// 通知方法
notify() {
this.watchers.forEach(watcher => watcher.update());
}
}
// 编译模板
class Compiler {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.methods = vm.$methods;
this.compile(vm.$el);
}
// 这里是递归编译 #app 下面的所有的节点内容
compile(el) {
// 类数组
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (node.nodeType === 3) {
// 文本节点 <p>{{ name }}</p>
this.compileText(node);
} else if (node.nodeType === 1) {
// 元素节点 <input type="text" v-model="message" />
this.compileElement(node);
}
// 如果还有子节点,递归遍历
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
// 处理文本节点
compileText(node) {
// 匹配出来 {{message}}
let reg = /\{\{(.+?)\}\}/;
let value = node.textContent;
if (reg.test(value)) {
let key = RegExp.$1.trim();
// 首次开始赋值
node.textContent = value.replace(reg, this.vm[key]);
// 添加观察者
new Watcher(this.vm, key, val => {
// 数据改变时的更新
node.textContent = val;
});
}
}
// 处理元素节点
compileElement(node) {
// 简化,只做匹配 v-on 和 v-model 的匹配
// v-on:click='' / v-model=''
if (node.attributes.length) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name;
if (attrName.startsWith('v-')) {
// v- 指令匹配成功,可能是 v-on:click 或者 v-model
attrName = attrName.indexOf(':') > -1 ? attrName.substring(5) : attrName.substring(2);
let key = attr.value;
// 调用更新函数
this.update(node, key, attrName, this.vm[key]);
}
});
}
}
// 更新函数
update(node, key, attrName, value) {
if (attrName === 'model') {
node.value = value;
new Watcher(this.vm, key, val => (node.value = val));
node.addEventListener('input', () => {
this.vm[key] = node.value;
});
} else if (attrName === 'click') {
node.addEventListener(attrName, this.methods[key].bind(this.vm));
// node.addEventListener(attrName, () => {
// this.methods[key].call(this.vm);
// });
}
}
}
function isSameVal(a, b) {
return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
看一看源码/列大纲
Vue2.x 和 Vue3.x 的对比
Vue 3.x中使用了Proxy作为响应式,天生的代理,不用考虑属性重写、数组这些2.x中hack的情况;diff增加了 最大递增子序列 的算法,使移动节点,更高效;- 架构采用
monorepo的方式,分层清晰,同时把编译部分也进行了一些拆解; vue3对编译的内容进行了重写,template -- render函数;vue2编译基于正则,vue3编译基于状态机;-->【状态机和ast编译原理有关】patchFlag标记哪些元素包含哪些属性;- 静态提升;
vue3使用blockTree,对比需要改变的,优化性能,如果要用jsx的写法,就不会优化,但是可以自己去标记;ts重构;compiler拆成了四个包,方便去重写;vue2 options API vs vue3 composition API;vue3使用了rollup打包,支持Tree Shaking;- 实例化方式也有区别;
Vue3.x 响应式
Vue 是怎样追踪变化的?
当我们在 template 模板中使用响应式变量,或者在计算属性中传入 getter 函数后当计算属性中的源数据发生变化后,Vue 总能即时的通知更新并重新渲染组件,其中是通过副作用(effect)函数来实现的。
Vue 通过一个副作用(effect)函数来跟踪当前正在运行的函数,副作用是一个函数包裹器,在函数被调用前就启动跟踪,而 Vue 在派发更新时就能准确的找到这些被收集起来的副作用函数,当数据发生更新时再次执行它。
手写迷你版
- 实现
reactive
/*
vue2 中响应式数据,只要在 data 中定义就可以了
vue3 的响应式数据,需要使用 reactive/ref
reactive(obj) => 参数是一个对象
const reactiveData = reactive({ message: 'hello', num: 0 });
*/
export function reactive(data) {
// 不是对象,直接返回
if (!isObject(data)) return;
// vue2 中响应式:Object.defineProperty
// vue3 中响应式:Proxy 代理
return new Proxy(data, {
get(target, key, receiver) {
// receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例
// Reflect.get 方法查找并返回 target 对象的 name 属性,如果没有该属性,则返回 undefined
const ret = Reflect.get(target, key, receiver);
// 收集依赖 => vue2 的 dep.add(Dep.target);
track(target, key);
return isObject(ret) ? reactive(ret) : ret;
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
// 触发 => vue2 的 dep.notify()
trigger(target, key);
return true;
},
deleteProperty(target, key) {
const ret = Reflect.defineProperty(target, key);
trigger(target, key);
return ret;
},
has(target, key) {
track(target, key);
return Reflect.has(target, key);
},
ownKeys(target) {
track(target, key);
return Reflect.ownKeys(target);
},
});
}
function isObject(data) {
return data && typeof data === 'object';
}
- 实现
ref
/*
定义:const num = ref(0);
使用:num.value,ref 会自动包裹一层 value
*/
export function ref(init) {
// init 就是传入的数据
class RefImpl {
constructor(init) {
// ! 8、执行 ref => 设置 getter、setter 方法
this.__value = init;
}
get value() {
// ! 11、模板上使用值的时候,触发 getter
track(this, 'value');
return this.__value;
}
set value(newVal) {
// ! 14、再次赋值时触发 setter
this.__value = newVal;
trigger(this, 'value');
}
}
return new RefImpl(init);
}
- 定义全局变量
let targetMap = new WeakMap();
// Vue 2.x 的时候,是不是有一个 ”全局变量“,叫做 Dep.target -- watcher
// Vue 3.x 还要有这么一个全局变量,来存放这么个东西 -- effect 副作用
let activeEffect;
- 实现
track
// 进行依赖的收集
function track(target, key) {
// 首先获取 target,如果没有进行赋值
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 再判断 depsMap 中有没有 key,没有就赋值
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
/*
最后 targetMap 的数据结构是这样:
targetMap: { // WeakMap()
target: { // Map()
key: [ReactiveEffect, ReactiveEffect, ...] // Set()
}
}
*/
trackEffect(dep);
}
function trackEffect(dep) {
// 相当于 Vue 2.x 中的 Dep.target && dep.add(Dep.target)
if (!dep.has(activeEffect)) {
// ! 13、dep.add(activeEffect)
dep.add(activeEffect);
}
}
- 实现
trigger
// 触发
function trigger(target, key) {
// ! 15、触发 trigger => 获取 depsMap 对象
const depsMap = targetMap.get(target);
if (!depsMap) return;
// ! 16、遍历执行 activeEffect 的 run 方法
// effect 上面肯定有一个 run 方法
depsMap.get(key).forEach(effect => effect && effect.run());
}
- 定义一个
effect的函数
// 在定义一个 effect 的函数中,第一个参数是一个函数
// 如果这个函数中有使用 ref/reactive,当值改变的时候 effect 就会执行
/*
effect(() => {
console.log(num.value)
})
effect 函数会执行,__effect.run() => activeEffect = this; =>
执行 this.fn() 会触发的当前值的 getter => track 会收集 activeEffect
*/
// ! 2、执行 effect 方法
function effect(fn, options = {}) {
// ! 3、new ReactiveEffect 实例
let __effect = new ReactiveEffect(fn);
// 如果 options 中有 lazy 属性,在加载的时候不会先执行,需要手动调用才会执行
if (!options.lazy) {
// ! 4、执行 run 方法
__effect.run();
}
return __effect;
}
class ReactiveEffect {
constructor(fn) {
this.fn = fn;
}
run() {
// ! 5、activeEffect 保存
activeEffect = this;
return this.fn();
}
}
- 实现
computed
/*
const m = computed(() => `${num.value}!!!`)
computed 执行的时候返回 __computed,当获取 m.value 的时候,触发 getter,执行 e.run();
*/
export function computed(fn, options = {}) {
// 只考虑函数的情况
let __computed;
const e = effect(fn, { lazy: true });
__computed = {
get value() {
// 手动执行 run 方法
return e.run();
},
};
return __computed;
}
- 实现
mount
// ! 1、mount 执行
export function mount(instance, el) {
/*
instance => App
el => 挂载的 id
*/
effect(function () {
// ! 6、执行 effect 方法的 fn
instance.$data && update(instance, el);
});
// ! 7、执行 setup 方法,$data 设置值
instance.$data = instance.setup();
// ! 9、执行 update 方法
update(instance, el);
function update(instance, el) {
// ! 10、el.innerHTML 首次渲染
el.innerHTML = instance.render();
}
}
迷你版完整代码
./index.html
<!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>Document</title>
<script type="module">
import { mount, ref, reactive, computed } from './vue3.js';
const App = {
$data: null,
setup() {
let count = ref(0);
let time = reactive({ seconds: 0 });
let com = computed(() => `get computed ${count.value + time.seconds}`);
setInterval(() => {
count.value++;
// 值改变触发 trigger => activeEffect => run()
}, 1000);
setInterval(() => {
time.seconds++;
}, 1000);
return {
count,
time,
com,
};
},
render() {
return `
<h1>Reactive Works</h1>
<h5>this is ref data: ${this.$data.count.value}</h5>
<h5>this is reactive data: ${this.$data.time.seconds}</h5>
<h5>this is computed data: ${this.$data.com.value}</h5>
`;
},
};
mount(App, document.querySelector('#app'));
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
./vue3.js
let targetMap = new WeakMap();
// Vue 2.x 的时候,是不是有一个 ”全局变量“,叫做 Dep.target -- watcher
// Vue 3.x 还要有这么一个全局变量,来存放这么个东西 -- effect 副作用
let activeEffect;
/*
vue2 中响应式数据,只要在 data 中定义就可以了
vue3 的响应式数据,需要使用 reactive/ref
reactive(obj) => 参数是一个对象
const reactiveData = reactive({ message: 'hello', num: 0 });
*/
export function reactive(data) {
// 不是对象,直接返回
if (!isObject(data)) return;
// vue2 中响应式:Object.defineProperty
// vue3 中响应式:Proxy 代理
return new Proxy(data, {
get(target, key, receiver) {
// receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例
// Reflect.get 方法查找并返回 target 对象的 name 属性,如果没有该属性,则返回 undefined
const ret = Reflect.get(target, key, receiver);
// 收集依赖 => vue2 的 dep.add(Dep.target);
track(target, key);
return isObject(ret) ? reactive(ret) : ret;
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
// 触发 => vue2 的 dep.notify()
trigger(target, key);
return true;
},
deleteProperty(target, key) {
const ret = Reflect.defineProperty(target, key);
trigger(target, key);
return ret;
},
has(target, key) {
track(target, key);
return Reflect.has(target, key);
},
ownKeys(target) {
track(target, key);
return Reflect.ownKeys(target);
},
});
}
/*
定义:const num = ref(0);
使用:num.value,ref 会自动包裹一层 value
*/
export function ref(init) {
// init 就是传入的数据
class RefImpl {
constructor(init) {
// ! 8、执行 ref => 设置 getter、setter 方法
this.__value = init;
}
get value() {
// ! 11、模板上使用值的时候,触发 getter
track(this, 'value');
return this.__value;
}
set value(newVal) {
// ! 14、再次赋值时触发 setter
this.__value = newVal;
trigger(this, 'value');
}
}
return new RefImpl(init);
}
/*
const m = computed(() => `${num.value}!!!`)
computed 执行的时候返回 __computed,当获取 m.value 的时候,触发 getter,执行 e.run();
*/
export function computed(fn, options = {}) {
// 只考虑函数的情况
let __computed;
const e = effect(fn, { lazy: true });
__computed = {
get value() {
// 手动执行 run 方法
return e.run();
},
};
return __computed;
}
// ! 1、mount 执行
export function mount(instance, el) {
/*
instance => App
el => 挂载的 id
*/
effect(function () {
// ! 6、执行 effect 方法的 fn
instance.$data && update(instance, el);
});
// ! 7、执行 setup 方法,$data 设置值
instance.$data = instance.setup();
// ! 9、执行 update 方法
update(instance, el);
function update(instance, el) {
// ! 10、el.innerHTML 首次渲染
el.innerHTML = instance.render();
}
}
// 在定义一个 effect 的函数中,第一个参数是一个函数
// 如果这个函数中有使用 ref/reactive,当值改变的时候 effect 就会执行
/*
effect(() => {
console.log(num.value)
})
effect 函数会执行,__effect.run() => activeEffect = this; =>
执行 this.fn() 会触发的当前值的 getter => track 会收集 activeEffect
*/
// ! 2、执行 effect 方法
function effect(fn, options = {}) {
// ! 3、new ReactiveEffect 实例
let __effect = new ReactiveEffect(fn);
// 如果 options 中有 lazy 属性,在加载的时候不会先执行,需要手动调用才会执行
if (!options.lazy) {
// ! 4、执行 run 方法
__effect.run();
}
return __effect;
}
class ReactiveEffect {
constructor(fn) {
this.fn = fn;
}
run() {
// ! 5、activeEffect 保存
activeEffect = this;
return this.fn();
}
}
// 进行依赖的收集
function track(target, key) {
// 首先获取 target,如果没有进行赋值
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 再判断 depsMap 中有没有 key,没有就赋值
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
/*
最后 targetMap 的数据结构是这样:
targetMap: { // WeakMap()
target: { // Map()
key: [ReactiveEffect, ReactiveEffect, ...] // Set()
}
}
*/
trackEffect(dep);
}
function trackEffect(dep) {
// 相当于 Vue 2.x 中的 Dep.target && dep.add(Dep.target)
if (!dep.has(activeEffect)) {
// ! 13、dep.add(activeEffect)
dep.add(activeEffect);
}
}
// 触发
function trigger(target, key) {
// ! 15、触发 trigger => 获取 depsMap 对象
const depsMap = targetMap.get(target);
if (!depsMap) return;
// ! 16、遍历执行 activeEffect 的 run 方法
// effect 上面肯定有一个 run 方法
depsMap.get(key).forEach(effect => effect && effect.run());
}
function isObject(data) {
return data && typeof data === 'object';
}
/*
mount 执行 =>
执行 effect 方法 => new ReactiveEffect => 执行 run 方法 => activeEffect 保存 => 执行 effect 方法的 fn =>
执行 setup 方法,$data 设置值 => 执行 ref => 设置 getter、setter 方法 =>
执行 update 方法 => el.innerHTML 首次渲染
模板上使用值的时候,触发 getter =>
触发 getter => track 依赖收集 => 设置数据结构 => dep.add(activeEffect) => getter 返回值渲染模板
再次赋值时触发 setter => 触发 trigger => 获取 depsMap 对象 => 遍历执行 activeEffect 的 run 方法(也就是上面 dep.add 添加的) =>
run 方法中的 fn 就是首次执行 effect 方法的 fn => 执行 update 方法 => el.innerHTML 更新
*/
步骤描述
let count = ref(0);
effect(() => {console.log(count.value)});
// effect 的函数函会执行 run();
// 触发 count.value 去收集依赖,也就是 track;
// track 的过程 => targetMap() 添加副作用函数
setInterval(() => {
count.value++;
}, 1000);
// 触发 trigger => targetMap 取出对应的 activeEffect 对象,执行 run 方法;
看一看源码/列大纲
Vue3 的 diff
- 路径:
core/packages/runtime-core/src/renderer.ts
问题 2:Vue2 中如何实现数据响应
通过 Object.DefineProperty,劫持数据的 get 和 set,结合观察者模式实现。
问题 3:请写一个观察者模式
class Subject {
deps: Array<Observer>;
state: Number;
constructor() {
this.deps = [];
this.state = 0;
}
attach(obs: Observer) {
this.deps.push(obs);
}
setState(num: Number) {
this.state = num;
this.notifyAllObservers();
}
notifyAllObservers(): void {
this.deps.forEach(obs => {
obs.run(this.state);
});
}
}
abstract class Observer {
subject: Subject;
constructor(subject: Subject) {
this.subject = subject;
this.subject.attach(this);
}
abstract run(data: String | Number | undefined): void;
}
class BinaryObserver extends Observer {
constructor(subject: Subject) {
super(subject);
}
run(data: String | Number | undefined): void {
console.log('hello, this is binaryObserver:' + data);
}
}
class ArrayObserver extends Observer {
constructor(subject: Subject) {
super(subject);
}
run(data: String | Number | undefined): void {
console.log('hello, this is ArrayObserver:' + data);
}
}
// main
const subject = new Subject();
const obs = new BinaryObserver(subject);
subject.setState(10);
subject.setState(15);
问题 4:请写一个最长上升子序列的算法
function maxUp(arr) {
if (arr.length === 0) return 0;
let dp = [];
let max = 0;
dp[0] = 1;
for (let i = 1; i < arr.length; i++) {
dp[i] = 1;
for (let j = 0; j < i; j++) {
if (arr[i] > arr[j]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
max = Math.max(dp[i], max);
}
return max;
}
发布订阅模式 vs 观察者模式
观察者模式
所谓观察者模式,其实就是为了实现 松耦合(loosely coupled)。
用《Head First 设计模式》里的气象站为例子,每当气象测量数据有更新,changed() 方法就会被调用,于是我们可以在 changed() 方法里面,更新气象仪器上的数据,比如温度、气压等等。
但是这样写有个问题,就是如果以后我们想在 changed() 方法被调用时,更新更多的信息,比如说湿度,那就要去修改 changed() 方法的代码,这就是紧耦合的坏处。
怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。
观察者模式里面,changed() 方法所在的实例对象,就是被观察者(Subject,或者叫 Observable),它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了:
发布订阅模式
在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。
互不相识?那他们之间如何交流?
答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。
发布者只需告诉 Broker,我要发的消息,topic 是 AAA;
订阅者只需告诉 Broker,我要订阅 topic 是AAA 的消息;
于是,当 Broker 收到发布者发过来消息,并且 topic 是 AAA 时,就会把消息推送给订阅了 topic 是 AAA 的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。
总结
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者
+被观察者; - 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人
Broker;
往更深层次讲:
- 观察者和被观察者,是松耦合的关系;
- 发布者和订阅者,则完全不存在耦合;
从使用层面上讲:
- 观察者模式,多用于单个应用内部;
- 发布订阅模式,则更多的是一种跨应用的模式(
cross-application pattern),比如我们常用的消息中间件;