在开始之前我们还是复习一下MVVM吧
实现MVVM的必要操作:
Object.defineProperty()
自行封装存取数据的方式。往往封装的是发布 / 订阅模式,来完成数据的监听、数据变更时更新的通知数据驱动
数据响应式、双向绑定、数据驱动
数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁琐的 DOM 操作提高开发效率
数据改变,视图改变;视图改变,数据也随之改变
我们可以使用 v-model 在表单元素上创建双向数据绑定
开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图
Object.defineProperty()
完成// 数据响应式
//数据模型仅仅是普通的JS对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作提高开发效率
//vue2
//模拟data选项
let data = {
msg: 'Hello'
}
//模拟实例
let vm = {};
//数据劫持:当访问或者设置vm中的成员的时候,做一些干预工作
Object.defineProperty(vm, 'msg', {
//可枚举
enumerable: true,
//可配置(可以使用delete删除,可以通过defineProperty 重新定义)
configurable: true,
//当获取到值的时候执行
get(){
console.log('get: ', data.msg)
return data.msg;
},
//当设置值的时候执行
set(newValue){
console.log('set: ', newValue);
if(newValue === data.msg){
return;
}
data.msg = newValue;
//!!!数据更改 更新DOM的值
document.querySelector('#app').textContent = data.msg;
}
})
//测试
vm.msg = 'Hello world';
console.log(vm.msg)
//模拟data
let data = {
msg: 'hello',
count: 0
}
//模拟实例
//Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
let vm = new Proxy(data, {
get(target, key){
console.log('get, key', key, target[key]);
return target[key];
},
set(target, key, newValue){
console.log('set, key', key, newValue);
if(target[key] === newValue){
return;
}
target[key] = newValue;
document.querySelector('#app').textContent = target[key];
}
})
//测试
vm.msg = 'Hello world'
console.log(vm.msg);
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信 号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执 行。这就叫做"发布/订阅模式"(publish-subscribe pattern)
let vm = new Vue()
vm.$on('dataChange', () => {console.log('dataChange')});
vm.$on('dataChange', () => {console.log('dataChange1')});
vm.$emit('dataChange');
不难理解,就是一个组件绑定了add-todo的订阅,另一个也绑定了add-todo的发布
//兄弟组件通信过程
//eventBus.js
//事件中心
let eventHub = new Vue();
//componentA.vue
//announcer
function addToDo(){
//发布消息 事件
eventHub.$emit('add-todo', {nametext:this.newTodoText})
this.newTodoText = '';
}
//componentB.vue
//subscriber
function created(){
//subscribe msg or event
eventHub.$on('add-todo', this.addToDo);
}
on 主要做的事 每次将传的eventType给一个订阅事件回调fn
emit 主要做的事 把所有的该事件进行执行回调
/**********模拟自定义实现************/
class EventEmitter {
constructor(){
// { eventType: [ handle1, handle2,] }
this.subs = [];
}
//subscribe
$on(eventType, fn) {
//init []
this.subs[eventType] = this.subs[eventType] || [];
this.subs[eventType].push(fn);
}
//emit
$emit(eventType) {
this.subs[eventType] && this.subs[eventType].forEach(v => v());
}
}
//测试
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');
由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模 式的订阅者与发布者之间是存在依赖的
// 观察者(订阅者) -- Watcher
// update():当事件发生时,具体要做的事情
// 目标(发布者) -- Dep
// subs 数组:存储所有的观察者
// addSub():添加观察者
// notify():当事件发生,调用所有观察者的 update() 方法
// 没有事件中心
// target(announcer)
// Dependency
class Dep {
constructor() {
// storage all announcers
this.subs = [];
}
// add watcher
addSub(sub) {(sub && sub.update) && this.subs.push(sub);}
//anounce all watcher
notify() {this.subs.forEach(sub => sub.update());}
}
// watcher
class Watcher {
update() {
console.log('update');
}
}
//测试
let dep = new Dep();
let watcher = new Watcher();
dep.addSub(watcher);
dep.notify();
记录传入的选项,设置 d a t a / data/ data/el
把 data 的成员注入到 Vue 实例
负责调用 Observer 实现数据响应式处理(数据劫持)
负责调用 Compiler 编译指令/插值表达式等
负责把 data 中的成员转换成 getter/setter
负责把多层属性转换成 getter/setter
如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
数据劫持
添加 Dep 和 Watcher 的依赖关系
数据变化发送通知
负责编译模板,解析指令/插值表达式
负责页面的首次渲染过程
当数据变化后重新渲染
收集依赖,添加订阅者(watcher)
通知所有订阅者
自身实例化的时候往dep对象中添加自己
当数据变化dep通知所有的 Watcher 实例更新视图
class _vue {
constructor (options) {
//1. storage options data
this.$options = options || {};
this.$data = data || {};
const el = options.el;
this.$el = Object.prototype.toString(options.el).slice(8, -1) === 'string' ?
document.querySelector(el) :
el;
//2. data injection
this._proxyData(this.$data)
//3. transfer Observer to proxy data
//4. transfer Compiler to compile
}
_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;
}
})
}
}
class Dep {
constructor () {
// 存储所有的订阅者
this.subs = []
}
// 添加订阅者
addSub (sub) {
if (sub && sub.update) { this.subs.push(sub)
}
}
// 通知观察者
notify () {
this.subs.forEach(sub => sub.update())
}
}
// defineReactive 中
// 创建 dep 对象收集依赖
const dep = new Dep()
// getter 中
// get 的过程中收集依赖
Dep.target && dep.addSub(Dep.target)
// setter 中
// 当数据变化之后,发送通知
dep.notify()
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)
}
}
// 因为在 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
})
}