写点什么

MVVM 响应式

作者:flow
  • 2022 年 8 月 01 日
  • 本文字数:4526 字

    阅读完需:约 15 分钟

在这里插入图片描述文章目录 MVVM 响应式原理数据响应式双向绑定数据驱动是 Vue 最独特的特性之一 2.x 的响应式 3.x 的响应式发布订阅者模式观察者模式 Vue 响应式原理 VueObserverCompilerDepWatcherVue 功能 ObserverDepwatcherMVVM 在开始之前我们还是复习一下 MVVM 吧


Model 层 通过 ajax 等 api 完成服务端到客户端 model 的同步,View 层 动态视图模板,展示的是 VM 的数据和状态,不处理状态,做的只是数据绑定的声明、指令的声明、事件绑定的声明 VM 层 把 View 需要的层数据暴露,对 View 层的数据绑定声明、指令声明、事件绑定声明负责,处理 View 层声明的业务逻辑。绑定属性监听,当 VM 数据变化,V 会得到更新;当 V 中声明了数据的双向绑定,(通常表单元素),框架就会监听 V 表单值的变化,一旦变化了 VM 中的数据也会自动更新在这里插入图片描述


实现 MVVM 的必要操作:


视图引擎,帮助 developer 操作 DOM 数据存储器,通过 Object.defineProperty()自行封装存取数据的方式。往往封装的是发布 / 订阅模式,来完成数据的监听、数据变更时更新的通知组件机制,因为有涉及继承、生命周期、组件通信机制,所以 MVVM 都有提供响应式原理数据驱动


数据响应式、双向绑定、数据驱动


数据响应式数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁琐的 DOM 操作提高开发效率


双向绑定数据改变,视图改变;视图改变,数据也随之改变我们可以使用 v-model 在表单元素上创建双向数据绑定


数据驱动是 Vue 最独特的特性之一开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图


2.x 的响应式响应式需要做到在修改数据的时候对 dom 的自动更改所以仅仅需要在 set 里面 自动绑定一个 dom 元素,然后更新就完事这里的 get set 操作 通过 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)3.x 的响应式通过 proxy 进行对象代理直接监听对象,而非 2.x 的属性//模拟 datalet data = {msg: 'hello',count: 0}//模拟实例//Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxylet 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)vue 中的发布订阅自定义 let vm = new Vue()vm.on('dataChange', () => {console.log('dataChange1')});vm.$emit('dataChange');兄弟组件的通信过程不难理解,就是一个组件绑定了 add-todo 的订阅,另一个也绑定了 add-todo 的发布


//兄弟组件通信过程//eventBus.js//事件中心 let eventHub = new Vue();


//componentA.vue//announcerfunction addToDo(){//发布消息 事件 eventHub.$emit('add-todo', {nametext:this.newTodoText})this.newTodoText = '';}


//componentB.vue//subscriberfunction created(){//subscribe msg or eventeventHub.$on('add-todo', this.addToDo);}手写实现 on 主要做的事 每次将传的 eventType 给一个订阅事件回调 fnemit 主要做的事 把所有的该事件进行执行回调


/模拟自定义实现**/class EventEmitter {constructor(){// { eventType: [ handle1, handle2,] }this.subs = [];}//subscribeemit(eventType) {this.subs[eventType] && this.subs[eventType].forEach(v => v());}}


//测试 let bus = new EventEmitter();


//registe eventbus.$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)// Dependencyclass Dep {constructor() {// storage all announcersthis.subs = [];}// add watcheraddSub(sub) {(sub && sub.update) && this.subs.push(sub);}//anounce all watchernotify() {this.subs.forEach(sub => sub.update());}}// watcherclass Watcher {update() {console.log('update');}}


//测试 let dep = new Dep();let watcher = new Watcher();dep.addSub(watcher);dep.notify();观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模 式的订阅者与发布者之间是存在依赖的发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在在这里插入图片描述


Vue 响应式原理在这里插入图片描述


Vue 记录传入的选项,设置 d a t a / 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 功能负责接收初始化的参数(选项)负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter 负责调用 observer 监听 data 中所有属性的变化负责调用 compiler 解析指令/插值表达式 class _vue {constructor (options) {//1. storage options datathis.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// Observer


// 功能// 负责把 data 选项中的属性转换成响应式数据// data 中的某个属性也是对象,把该属性转换成响应式数据 deep reactive// 数据变化发送通知


class Observer {//$data => getter / setterconstructor(data) {this.walk(data);}//1. if not obj return//2. if obj loop and getter / setterwalk(data) {if(!data || Object.prototype.toString(data).slice(8, -1) === 'object'){return;}//loop dataObject.keys(data).forEach(key => {this.defineReactive(key, data[key]);})}//define reactivedefineReactive(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 => reactivethat.walk(newValue);val = newValue;}})}}Depclass 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 记录 watcherthis.oldValue = vm[key]// 清空 targetDep.target = null}update () {const newValue = this.vm[this.key]if (this.oldValue === newValue) {return}this.cb(newValue)}}在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化// 因为在 textUpdater 等中要使用 thisupdaterFn && 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})}

用户头像

flow

关注

还未添加个人签名 2022.07.13 加入

还未添加个人简介

评论

发布
暂无评论
MVVM响应式_8月月更_flow_InfoQ写作社区