北京做网站哪家公司最好,内蒙网站建设赫伟创意星空科技,建设网站所采用的技术,网站建设博客作业响应式原理
响应式是Vue的核心特性之一#xff0c;数据驱动视图#xff0c;我们修改数据视图随之响应更新#xff0c;其原理是#xff1a;
⚡️ 通过数据劫持结合发布和-订阅者模式的方式#xff0c;通过拦截对数据的操作#xff0c;在数据变动时发 布消息给订阅者数据驱动视图我们修改数据视图随之响应更新其原理是
⚡️ 通过数据劫持结合发布和-订阅者模式的方式通过拦截对数据的操作在数据变动时发 布消息给订阅者触发相应的监听回调。 发布者-订阅者模式
简单地说发布者-订阅者模式的流程就是监听器监听数据状态变化, 一旦数据发生变化则会通知对应的订阅者让订阅者执行对应的业务逻辑 。
首先要对数据进行劫持监听所以我们需要设置一个监听器Observer用来监听所有属性。如果属性发生变化了就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个所以我们需要有一个消息订阅器Dep来专门收集这些订阅者然后在监听器Observer和订阅者Watcher之间进行统一管理。接着我们还需要有一个指令解析器Compile对每个节点元素进行扫描和解析将相关指令对应初始化成一个订阅者Watcher并替换模板数据或者绑定相应的函数此时当订阅者Watcher接收到相应属性的变化就会执行对应的更新函数从而更新视图
实现一个监听器 Observer 用来劫持并监听所有属性如果属性发生变化就通知订阅者实现一个订阅器 Dep用来收集订阅者对监听器 Observer 和 订阅者 Watcher 进行统一管理实现一个订阅者 Watcher可以收到属性的变化通知并执行相应的方法从而更新视图实现一个解析器 Compile可以解析每个节点的相关指令对模板数据和订阅器进行初始化。 1. 监听器 Observer
监听器 Observer 的实现主要是指让数据对象变得“可观测”即每次数据读或写时我们能感知到数据被读取了或数据被改写了。要使数据变得“可观测”。 Vue2.x是借助Object.defineProperty()实现的 通过Object.defineProperty对data上的数据递归地进行getter和setter操作。也就是对属性的读取、修改进行拦截数据劫持 // 循环遍历数据对象的每个属性
function observable(obj) {if (!obj || typeof obj ! object) {return;}let keys Object.keys(obj);keys.forEach((key) {defineReactive(obj, key, obj[key])})return obj;
}// 将对象的属性用 Object.defineProperty() 进行设置
function defineReactive(obj, key, val) {enumerable: true,configurable: true,Object.defineProperty(obj, key, {//拦截get当我们访问data.key时会被这个方法拦截到get() {//在这里收集依赖console.log(${key}属性被读取了...);return val;},//拦截set当我们为data.key赋值时会被这个方法拦截到set(newVal) {//当数据变更时通知依赖项变更UIconsole.log(${key}属性被修改了...);val newVal;}})
}我们通过Object.defineProperty为对象obj添加属性可以设置对象属性的getter和setter函数。之后我们每次通过点语法获取属性都会执行这里的getter函数在这个函数中我们会把调用此属性的依赖收集到一个集合中 而在我们给属性赋值(修改属性)时会触发这里定义的setter函数在次函数中会去通知集合中的依赖更新做到数据变更驱动视图变更。 Vue3.x是借助Proxy实现的 通过Proxy对象创建一个对象的代理从而实现基本操作的拦截和自定义如属性查找、赋值、枚举、函数调用等。 Proxy的监听是深层次的监听整个对象而不是某个属性。Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。 let nObjnew Proxy(obj,{//拦截get当我们访问nObj.key时会被这个方法拦截到get: function (target, propKey, receiver) {console.log(getting ${propKey}!);return Reflect.get(target, propKey, receiver);},//拦截set当我们为nObj.key赋值时会被这个方法拦截到set: function (target, propKey, value, receiver) {console.log(setting ${propKey}!);return Reflect.set(target, propKey, value, receiver);}
}).
2. 订阅器 Dep
发布 —订阅设计模式
发布-订阅模式又叫观察者模式它定义对象间的一种一对多的依赖关系当一个对象的状态改变时所有依赖于它的对象都将得到通知。
举例说明 小明最近看上了一套房子到了售楼处之后才被告知该楼盘的房子早已售罄。好在售楼 MM 告诉小明不久后还有一些尾盘推出开发商正在办理相关手续手续办好后便可以购买。 但到底是什么时候目前还没有人能够知道。于是小明把电话号码留在了售楼处。售楼 MM 答应他只要新楼盘一推出就马上发信息通知小明。 除了小明还有小红、小强、小龙也是一样他们的电话号码都被记在售楼处的花名册上新楼盘推出的时候售楼 MM会翻开花名册遍历上面的电话号码依次发送一条短信来通知他们。 这就是发布-订阅模式在现实中的例子。
发布—订阅模式的优点 发布-订阅模式广泛应用于异步编程中这是一种替代传递回调函数的方案比如我们可以订阅 ajax 请求的 error 、succ 等事件。在异步编程中使用发布-订阅模式 我们就无需过多关注对象在异步运行期间的内部状态而只需要订阅感兴趣的事件发生点。 发布-订阅模式可以取代对象之间硬编码的通知机制一个对象不用再显式地调用另外一个对象的某个接口。发布-订阅模式让两个对象松耦合地联系在一起虽然不太清楚彼此的细节但这不影响它们之间相互通信。当有新的订阅者出现时发布者的代码不需要任何修改同样发布者需要改变时也不会影响到之前的订阅者。只要之前约定的事件名没有变化就 可以自由地改变它们。
订阅器 Dep 实现
完成了数据的’可观测’即我们知道了数据在什么时候被读或写了那么我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了。为了方便我们需要先将所有依赖收集起来一旦数据发生变化就统一通知更新。 其实这就是上面所说的“发布订阅者”模式数据变化为“发布者”依赖对象为“订阅者”数据变化时所有依赖的对象都会得到通知。
现在我们需要创建一个依赖收集容器也就是消息订阅器 Dep用来容纳所有的“订阅者”。 消息订阅器 Dep 主要负责收集订阅者然后当数据变化的时候后执行对应订阅者的更新函数。
创建消息订阅器 Dep:
function Dep () {this.subs [];
}
Dep.prototype {addSub: function(sub) {this.subs.push(sub);},notify: function() {this.subs.forEach(function(sub) {sub.update();});}
};
Dep.target null;有了订阅器我们再将上面的 defineReactive 函数进行改造一下向其植入订阅器
defineReactive: function(data, key, val) {var dep new Dep();Object.defineProperty(data, key, {enumerable: true,configurable: true,get: function getter () {if (Dep.target) {dep.addSub(Dep.target);}return val;},set: function setter (newVal) {if (newVal val) {return;}val newVal;dep.notify();}});
}上面代码中有一个静态属性 Dep.target这是一个全局唯一 的Watcher因为在同一时间只能有一个全局的 Watcher 被计算另外它的自身属性 subs 也是 Watcher 的数组。
.
3. 订阅者 Watcher
订阅者Watcher 在初始化的时候需要将自己添加进订阅器 Dep 中那该如何添加呢我们已经知道监听器Observer 是在 get 函数执行了添加订阅者Wather 的操作的所以我们只要在订阅者 Watcher 初始化的时候触发对应的 get 函数去执行添加订阅者操作即可那要如何触发 get 的函数只要获取对应的属性值就可以触发了核心原因就是因为我们使用了 Object.defineProperty( ) 进行数据监听。这里还有一个细节点需要处理我们只要在订阅者 Watcher 初始化的时候才需要添加订阅者所以需要做一个判断操作因此可以在订阅器上做一下手脚在 Dep.target 上缓存下订阅者添加成功后再将其去掉就可以了。订阅者 Watcher 的实现如下
function Watcher(vm, exp, cb) {this.vm vm; // 一个 Vue 的实例对象this.exp exp; // 是 node 节点的 v-model 等指令的属性值 或者插值符号中的属性。如 v-modelnameexp 就是namethis.cb cb;// 是 Watcher 绑定的更新函数this.value this.get(); // 将自己添加到订阅器的操作
}Watcher.prototype {update: function() {this.run();},run: function() {var value this.vm.data[this.exp];var oldVal this.value;if (value ! oldVal) {this.value value;this.cb.call(this.vm, value, oldVal);}},get: function() {Dep.target this; // 将自己赋值为全局的订阅者 全局变量 订阅者 赋值 var value this.vm.data[this.exp] // 强制执行监听器里的get函数Dep.target null; // 全局变量 订阅者 释放return value;}
};4. 解析器 Compile
解析器 Compile 关键逻辑代码分析
通过监听器 Observer 订阅器 Dep 和订阅者 Watcher 的实现其实就已经实现了一个双向数据绑定的例子但是整个过程都没有去解析 dom 节点而是直接固定某个节点进行替换数据的所以接下来需要实现一个解析器 Compile 来做解析和绑定工作。解析器 Compile 实现步骤
解析模板指令并替换模板数据初始化视图将模板指令对应的节点绑定对应的更新函数初始化相应的订阅器
我们下面对 ‘{{变量}}’ 这种形式的指令处理的关键代码进行分析感受解析器 Compile 的处理逻辑关键代码如下
compileText: function(node, exp) {var self this;var initText this.vm[exp]; // 获取属性值this.updateText(node, initText); // dom 更新节点文本值// 将这个指令初始化为一个订阅者后续 exp 改变时就会触发这个更新回调从而更新视图new Watcher(this.vm, exp, function (value) { self.updateText(node, value);});
}双向数据绑定
双向数据绑定通常是指我们使用的v-model指令的实现是Vue的一个特性也可以说是一个input事件和value的语法糖。 Vue通过v-model指令为组件添加上input事件处理和value属性的赋值。
templateinput v-modellocalValue/
/template上述的组件就相当于如下代码
template!-- 这里添加了input时间的监听和value的属性绑定 --input inputonInput :valuelocalValue /span{{localValue}}/span
/template
scriptexport default{data(){return {localValue:,}},methods:{onInput(v){//在input事件的处理函数中更新value的绑定值this.localValuev.target.value;console.log(this.localValue)}}}
/script因此当我们修改input输入框中的值时我们通过v-model绑定的值也会同步修改基于上述原理我们可以很容易的实现一个数据双向绑定的组件。