医院网站建设论证报告,河源网站seo,网站没有管理员权限设置,网站建设是 口号前言
Vue响应式原理由以下三个部分组成#xff1a;
数据劫持#xff1a;Vue通过Object.defineProperty()方法对data中的每个属性进行拦截#xff0c;当属性值发生变化时#xff0c;会触发setter方法#xff0c;通知依赖更新。发布-订阅模式#xff1a;Vue使用发布-订阅…前言
Vue响应式原理由以下三个部分组成
数据劫持Vue通过Object.defineProperty()方法对data中的每个属性进行拦截当属性值发生变化时会触发setter方法通知依赖更新。发布-订阅模式Vue使用发布-订阅模式来实现数据的响应式更新。当数据发生变化时会通知依赖进行更新。依赖收集Vue在渲染组件时会对模板中使用到的数据进行依赖收集将组件中使用到的数据和对应的Watcher对象建立关联当数据发生变化时会通知相关的Watcher对象进行更新。
实现流程
手写Vue响应式原理可以分为以下几个步骤
实现Observer类通过Object.defineProperty()方法对data中的每个属性进行拦截当属性值发生变化时会触发setter方法通知依赖更新。实现Dep类用于管理Watcher对象包括添加和删除Watcher对象以及通知Watcher对象进行更新。实现Watcher类用于建立视图与数据之间的联系当数据发生变化时会通知Watcher对象进行更新。实现Compile类用于解析模板指令将指令对应的数据渲染到视图中并建立视图与数据的联系。实现Vue类将Observer、Watcher、Compile类进行整合实现Vue的响应式更新机制。
总的来说手写Vue响应式原理主要由Observer、Dep、Watcher、Compile、Vue这几个组成部分构成。其中Observer用于拦截数据变化Dep用于管理Watcher对象Watcher用于建立视图与数据之间的联系Compile用于解析模板指令Vue将这些类进行整合实现了Vue的响应式更新机制。
创建一个Dep类
我们使用递归来遍历数据对象中的所有属性对每个属性使用Object.defineProperty()方法进行定义。在defineReactive()方法中我们还创建了一个Dep类Dep类用于管理所有订阅者Watcher和通知它们更新。
class Dep {constructor() {this.subs []; // 存储依赖的数组}// 添加依赖addSub(sub) {if (sub sub.update) {this.subs.push(sub);}}// 通知依赖更新notify() {this.subs.forEach(sub {sub.update();});}
}Dep.target null; // 静态属性 target用于保存当前的 Watcher 对象
上面的代码定义了一个名为Dep的类它有以下几个方法
constructor()构造函数初始化订阅者数组subs为空数组。addSub(sub)添加订阅者的方法将传入的订阅者对象sub添加到subs数组中。notify()通知所有订阅者更新的方法遍历subs数组对每个订阅者调用其update()方法。target定义一个全局变量target用于存储当前的订阅者对象。
在Vue中每个响应式数据如data中的属性都会对应一个Dep对象。当这个属性被读取时会将当前的订阅者对象存储到Dep.target中然后在属性的getter方法中将Dep.target添加到当前属性的Dep对象的订阅者数组中当属性的值被修改时会调用该属性的Dep对象的notify()方法通知所有订阅者更新。
创建一个Watcher类
接下来我们需要创建一个Watcher类它的主要作用是在数据发生变化时触发视图的更新操作。在Watcher类中我们首先需要保存更新视图所需的回调函数并将Watcher实例添加到数据的订阅列表中。在数据发生变化时我们遍历订阅列表并依次调用回调函数来更新视图。
// 创建一个Watcher类用于管理依赖与视图的更新
class Watcher {constructor(vm, key, cb) { this.vm vm; this.key key; this.cb cb;// 将当前Watcher实例指定为Dep.target Dep.target this; // 获取数据的值触发数据的get方法从而将当前Watcher实例添加到Dep中 this.oldValue vm[key]; Dep.target null;}// 更新视图
update() {const newValue this.vm[this.key]; if (this.oldValue newValue) { return; } this.cb(newValue); this.oldValue newValue; }
}
创建一个Observer类
Observer 类该类用于对数据进行监听和响应式处理主要实现了 walk 和 defineReactive 两个方法。walk 方法遍历对象中所有属性对每个属性调用 defineReactive 方法进行响应式处理defineReactive 方法利用 Object.defineProperty 给每个属性添加 getter 和 setter当属性被访问或修改时会触发相应的依赖更新。
class Observer {constructor(data) {this.walk(data);}// 对数据对象进行递归遍历为每个属性添加getter和setterwalk(data) {Object.keys(data).forEach(key {this.defineReactive(data, key, data[key]);});}defineReactive(obj, key, val) {const dep new Dep(); // 创建一个依赖收集器Object.defineProperty(obj, key, {enumerable: true, // 可枚举configurable: true, // 可配置get() {// 添加依赖if (Dep.target) {dep.depend();}return val;},set(newVal) {if (val newVal) {return;}val newVal;// 触发依赖更新dep.notify();}});}
}
创建一个Compile类
Compile类的代码。在Compile类中我们首先需要遍历模板中的节点并根据节点的类型来处理它们。对于普通节点我们将对它们的文本内容进行处理对于包含指令的节点我们将创建Watcher实例并将它们添加到订阅列表中。
class Compile {constructor(el, vm) {this.el document.querySelector(el); // 获取根节点this.vm vm; // 保存 Vue 实例this.compile(this.el); // 编译模板}compile(el) {const childNodes el.childNodes; // 获取根节点的子节点列表Array.from(childNodes).forEach(node {if (node.nodeType 1) { // 元素节点this.compileElement(node);} else if (this.isInterpolation(node)) { // 文本节点且包含插值语法this.compileText(node);}// 递归编译子节点if (node.childNodes node.childNodes.length 0) {this.compile(node);}});}compileElement(node) {const attrs node.attributes; // 获取元素节点的属性列表Array.from(attrs).forEach(attr {const attrName attr.name;const exp attr.value;if (attrName.startsWith(v-)) { // 匹配指令const dir attrName.substring(2); // 获取指令名称this[dir] this[dir](node, exp); // 调用对应的指令函数}});}compileText(node) {const exp node.textContent; // 获取插值语法中的表达式node.textContent this.getVMValue(exp); // 将插值语法替换为表达式的值}isInterpolation(node) {return node.nodeType 3 /\{\{(.*)\}\}/.test(node.textContent); // 文本节点且包含插值语法}getVMValue(exp) {let value this.vm;exp.split(.).forEach(key {value value[key];});return value;}// v-model 指令model(node, exp) {this.bind(node, exp, model);node.addEventListener(input, e {const newValue e.target.value;this.setVMValue(exp, newValue);});}// v-bind 指令bind(node, exp, dir) {const updaterFn this[dir Updater];updaterFn updaterFn(node, this.getVMValue(exp));new Watcher(this.vm, exp, value {updaterFn updaterFn(node, value);});}// model 指令更新视图modelUpdater(node, value) {node.value value;}// v-text 指令text(node, exp) {this.bind(node, exp, text);}// text 指令更新视图textUpdater(node, value) {node.textContent value;}setVMValue(exp, value) {let vm this.vm;const keys exp.split(.);keys.forEach((key, index) {if (index keys.length - 1) {vm vm[key];} else {vm[key] value;}});}
}
Compile 类的实例化需要传入两个参数el 和 vm。其中el 是根节点的选择器vm 是 Vue 实例。
Compile 类主要实现了以下功能
遍历根节点及其子节点对每个元素节点和包含插值语法的文本节点进行编译。对于元素节点遍历其属性列表匹配指令并调用对应的指令函数进行处理。对于包含插值语法的文本节点替换为表达式的值。实现了 v-model、v-bind 和 v-text 指令的处理。实现了响应式数据的处理通过 Watcher 对数据进行监听数据发生变化时自动更新视图。
创建一个完整的Vue实例
创建一个Vue类将Observer、Watcher和Compile类组合在一起以创建一个完整的Vue实例。
class Vue {constructor(options) {this.$options options;this.$data options.data;this.$el typeof options.el string ? document.querySelector(options.el) : options.el;// 将 Vue 实例的属性代理到 $data 对象上this._proxyData(this.$data);// 创建Observer实例监听数据变化new Observer(this.$data);// 创建Compile实例解析模板指令new Compile(this.$el, this);}//使用_proxyData()方法来将数据代理到Vue实例中,就可以在Vue实例中通过this.key的方式来访问数据_proxyData(data) {Object.keys(data).forEach((key) {Object.defineProperty(this, key, {enumerable: true,configurable: true,get() {return data[key];},set(newValue) {if (newValue data[key]) {return;}data[key] newValue;},});});}
}
到此为止我们已经完成了手写代码来模拟Vue2.0的响应式数据实现的过程。我们可以通过这个过程来深入理解Vue2.0的响应式数据原理从而更好地应用Vue2.0开发应用程序。 后续会继续更新vue2.0其他源码系列包括目前在学习vue3.0源码也会后续更新出来喜欢的点点关注。