<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="myDiv"></div>
<!-- Vue的响应式原理 -->
<!-- <script type="text/javascript" src="./index.js"></script> -->
<!-- 发布订阅者模式 -->
<!-- <script type="text/javascript" src="./publishSubscribe.js"></script> -->
<!-- 观察者模式 -->
<script type="text/javascript" src="./watcher.js"></script>
</body>
</html>
index.js
/**
* Vue的响应式原理
*/
var myDiv = document.getElementById('myDiv');
console.log('myDiv', myDiv);
let data = {
msg: 'Hello'
}
let vm = {};
/**
* objObject.defineProperty的作用就是直接在对象上定义一个新属性 , 或者修改已存在的属性
* 同事可以接收三个参数
* 示例 :
* let obj = {
* msg: 'Hello' ,
* }
* Object.defineproperty(obj, prop, desc)
* obj : 第一个参数就是在哪一个对象身上添加或者修改属性 示例中obj
* prop :第二个参数就是要修改或者添加的属性名 示例中msg
* desc : 配置项 , 一般就是一个对象
* writable: 是否可重写 控制属性是否可以被修改 默认为flase
* value: 当前值 用来设置属性值(可以理解为默认值)
* get: 读取时内部调用的函数 是获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为undefined
* set: 写入时内部调用的函数 是设置值的时候的方法,类型为 function ,设置值的时候会被调用,undefined
* enumerable: 是否可以遍历 控制属性书否可以被枚举 默认为false
* configurable: 是否可再次修改配置项 控制属性是否可以被删除 默认为false
*
* *个人理解 vue中的setter和getter函数就是在调用这里的set和get方法
*/
Object.defineProperty(vm, 'msg', {
//可枚举
enumerable: true,
//可配置
configurable: true,
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是msg的值
get() {
console.log('get', data.msg);
return data.msg;
},
//当设置值的时候执行
//当有人修改person的msg属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(newValue) {
console.log('set', newValue);
if (newValue == data.msg) {
return;
}
data.msg = newValue;
myDiv.textContent = data.msg;
/**
* 在这个set方法中我们会发现 如果传入的值newValue 等等于原先的值 就return出去 如果不等于 就给原先的值重新赋值
*/
}
})
//测试
vm.msg = 'Hello world';
console.log(vm.msg)
/**
* Vue响应式原理就是
* vue(注入data , 并把data成员转成 setter getter) → → → Observe(数据劫持监听数据对象所有属性, → → → Dep(发布者 添加watcher,当数据变化通知所有观察者)
* ↓ 变动就拿到最新值通知Dep) ↓
* ↓ ↓
* ↓ ↓
* ↓ ↓
* compiler(解析指令) → → → → watcher(观察者 数据变化就更新视图) ← ← ← ←
*
*
* Vue
* 记录传入的选项,设置 data / data/ data/el
* 把 data 的成员注入到 Vue 实例
* 负责调用 Observer 实现数据响应式处理(数据劫持)
* 负责调用 Compiler 编译指令/插值表达式等
*
* Observer
* 负责把 data 中的成员转换成 getter/setter
* 负责把多层属性转换成 getter/setter
* 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
* 数据劫持
* 添加 Dep 和 Watcher 的依赖关系
* 数据变化发送通知
*
* Compiler
* 负责编译模板,解析指令/插值表达式
* 负责页面的首次渲染过程
* 当数据变化后重新渲染
*
* Dep
* 收集依赖,添加订阅者(watcher)
* 通知所有订阅者
*
* Watcher
* 自身实例化的时候往dep对象中添加自己
* 当数据变化dep通知所有的 Watcher 实例更新视图
*/
/**
* vue源码实现原理
* Vue功能
负责接收初始化的参数(选项)
负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
负责调用 observer 监听 data 中所有属性的变化
负责调用 compiler 解析指令/插值表达式
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
// data 中的属性名称
this.key = key
// 当数据变化的时候,调用 cb 更新视图
this.cb = cb
// 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
Dep.target = this
// 触发一次 getter,让 dep 为当前 key 记录 watcher
this.oldValue = vm[key]
// 清空 target
Dep.target = null
}
update() {
const newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.cb(newValue)
}
}
_proxyData(){
// loop data
Object.keys(data).forEach(key => {
ObjectFlags.defineProperty(this, key, {
get(){
return data[key];
},
set(newValue){
if(data[key] === newValue){
return;
}
data[key] = newValue;
}
})
})
}
}
*
*
* Observer功能
* 负责把 data 选项中的属性转换成响应式数据
* data 中的某个属性也是对象,把该属性转换成响应式数据 deep reactive
* 数据变化发送通知
*
* class Observer {
//$data => getter / setter
constructor(data) {
this.walk(data);
}
//1. if not obj return
//2. if obj loop and getter / setter
walk(data) {
if(!data || Object.prototype.toString(data).slice(8, -1) === 'object'){
return;
}
//loop data
Object.keys(data).forEach(key => {
this.defineReactive(key, data[key]);
})
}
//define reactive
defineReactive(data, key, val){
const that = this;
this.walk(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
return val;
},
set(newValue){
if(newValue === val){
return;
}
// newValue => reactive
that.walk(newValue);
val = newValue;
}
})
}
}
*
*
* Dep
class Dep {
constructor () {
// 存储所有的订阅者
this.subs = []
}
// 添加订阅者
addSub (sub) {
if (sub && sub.update) { this.subs.push(sub)
}
}
// 通知观察者
notify () {
this.subs.forEach(sub => sub.update())
}
}
在 compiler.js 中收集依赖,发送通知
// defineReactive 中
// 创建 dep 对象收集依赖
const dep = new Dep()
// getter 中
// get 的过程中收集依赖
Dep.target && dep.addSub(Dep.target)
// setter 中
// 当数据变化之后,发送通知
dep.notify()
*
*
* watcher
数据变化触发依赖,dep通知所有的watcher实例更新视图
自身实例化的时候往dep对象中添加自己的结构
class Watcher {
constructor (vm, key, cb) {
this.vm = vm
// data 中的属性名称
this.key = key
// 当数据变化的时候,调用 cb 更新视图
this.cb = cb
// 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
Dep.target = this
// 触发一次 getter,让 dep 为当前 key 记录 watcher
this.oldValue = vm[key]
// 清空 target
Dep.target = null
}
update () {
const newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.cb(newValue)
}
}
在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化
// 因为在 textUpdater等中要使用 this
updaterFn && updaterFn.call(this, node, this.vm[key], key)
// v-text 指令的更新方法
textUpdater (node, value, key) {
node.textContent = value
// 每一个指令中创建一个 watcher,观察数据的变化
new Watcher(this.vm, key, value => {
node.textContent = value
})
}
视图变化更新数据
// v-model 指令的更新方法
modelUpdater (node, value, key) {
node.value = value
// 每一个指令中创建一个 watcher,观察数据的变化
new Watcher(this.vm, key, value => {
node.value = value
}
// 监听视图的变化
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
*/
publishSubscribe.js
/**
* 发布订阅者模式
* 发布订阅模式通俗讲 : 假设在我们的程序中, 存在一个信号中心 , 当某个任务执行完成 , 就像信号中心"发布(Public)"一个信号 ,而其他信号可以想限号中心"订阅(Subscribe)"这个信号
* 从而知道自己什么时候可以开始执行。这个就叫"发布/订阅模式"(publish-subscribe pattern)
*/
/**
* vue中的发布订阅(手写实现)
*/
// class EventEmitter { constructor(){:这是一个ES6类的定义,它创建了一个名为EventEmitter的类。
class EventEmitter {
constructor() {
/**
* this.subs = []; 在这个类的构造函数中 创建了一个变量subs 这个变量用来存储时间类型和对应的事件处理函数
*/
this.subs = [];
}
/**
*
* @param {*} eventType 表示事件的类型 click change submit...等事件
* @param {*} fn 表示事件处理函数
*/
$on(eventType, fn) {
/**
* this.subs[eventType] = this.subs[eventType] || [];
* 用来初始化时间类型对应的处理函数数组,如果订阅过该时间类型,就创建一个新的数组
*/
this.subs[eventType] = this.subs[eventType] || [];
/**
* this.subs[eventType].push
* 将事件处理函数fn添加到事件类型evenTyp对应的数组中 , 这样可以在时间触发时执行这些处理函数
*/
this.subs[eventType].push(fn);
}
/**
*
* @param {*} eventType 定义一个方法 用于触发事件 , 接收参数为要触发时间的类型
*/
$emit(eventType) {
/**
* this.subs[eventType] && this.subs[eventType].forEach(v => v());
* 这行代码首先检查是存在否订阅传入的evenType 类型的事件处理函数 如果有就遍历执行数组中的每个时间处理函数执行
* 这样就实现了时间的触发 , 所有订阅了改时间的处理函数都会被执行
*/
this.subs[eventType] && this.subs[eventType].forEach(v => v());
}
}
//测试
/**
* 创建一个EventEmitter示例 bus
* 然后通过bus.$on方法取注册两个事件处理函数用来 处理click事件
* 最后通过bus.$emit('click') 触发click事件, 相应的事件处理函数被执行 , 打印出相应的消息
* bus.$emit('change')并未被执行 是因为我们没有注册 change事件处理函数 所以没有打印结果
* 如果想让bus.$emit('change')执行 我们可以通过bus.$on注册事件处理函数
* 例 : bus.$on('change' , function(){
* console.log('我可以被正常执行了');
* })
*
*/
let bus = new EventEmitter();
//registe event
bus.$on('click', function(){
console.log('click'); //click
})
bus.$on('click', function(){
console.log('click1') //click1
})
bus.$emit('click');
bus.$emit('change');
watcher.js
/**
* 观察者模式
* 由具体的目标调度 例如当事件触发时 , Dep就会调用观察者方法 所以观察者模式的订阅者和发布者存在依赖
*/
/**
* class Dep{} 定义一个名为Dep的类
*/
class Dep {
/**
* constructor(){} 类的构造函数,用于初始化对象
*/
constructor() {
/**
* this.subs = []; 在构造函数内部创建一个名为subs的空数组用于存储所有订阅者
*/
this.subs = [];
}
/**
*
* @param {*} addSub() 定义一个addSub方法 用于将所有的订阅者添加到sub数组中
* 在这个方法中 使用条件语句 sub && sub.update确保传入的sub对象存在update方法 然后再将其添加到订阅者列表中
*/
addSub(sub) {
(sub && sub.update) && this.subs.push(sub);
}
/**
* @param {*} notify() 定义一个notify方法 , 用于通知所有订阅者执行其update方法 ,
* 通过遍历subs数组并对每个订阅者调用其update()方法
*/
notify() {
this.subs.forEach(sub => {
sub.update();
})
}
}
/**
* class Watcher {} 定义了一个名为 Watcher 的类
*/
class Watcher {
/**
* @param {*} update() Watcher 类的方法,当被调用时,它会在控制台中输出 "update"
*/
update() {
console.log('update');
}
}
//测试
/**
* let dep = new Dep();:创建一个 Dep 类的实例,即一个发布者对象。
*/
let dep = new Dep();
/**
* let watcher = new Watcher();:创建一个 Watcher 类的实例,即一个订阅者对象。
*/
let watcher = new Watcher();
/**
* dep.addSub(watcher);:将 watcher 对象添加到 dep 的订阅者列表中,以便 watcher 可以接收来自 dep 的通知。
*/
dep.addSub(watcher);
/**
* dep.notify();:调用 dep 的 notify 方法,触发所有订阅者的 update 方法,这将在控制台中输出 "update"。
*/
dep.notify();