通俗易懂的 Vue 您所在的位置:网站首页 computed属性 通俗易懂的 Vue

通俗易懂的 Vue

2023-08-17 08:51| 来源: 网络整理| 查看: 265

大家好,新人一个,初次写博客还请大家多多关照。

对于Vue的响应式,想必大家都有所了解,在Vue响应式数据中,computed 是比较特殊的响应式数据,它们可以监听使用到的 数据,数据 改变 computed 的数据也会重新计算。

今天主要是讨论 computed 实现原理 。 computed 在内部主要是运用 Watcher 和 Dep 构造函数进行收集依赖和派发更新。

咱们先来看看 Watcher 和 Dep 源码。

var uid = 0; /** * dep 就是用来给每个数据做个标记,可以用来收集数据和派发更新 */ var Dep = function Dep () { this.id = uid++; //给每一个 Dep 打一个标记。这里需要说明一下,vm中data的每个数据进行初始化的时候都会调用this.dep = new Dep(),每一个数据都会有dep属性。 //(通过Observer 构造函数进行数据初始化,这里就不多说了,大家感兴趣可以看一下源码) this.subs = []; // 这个subs收集的是watcher,派发更新的时候遍历每个watcher调用update方法 }; Dep.prototype.addSub = function addSub (sub) { this.subs.push(sub);//这个方式是用来收集watcher的(每个computed构建出来的Watcher实例) }; Dep.prototype.depend = function depend () { if (Dep.target) { Dep.target.addDep(this); // 这里的方法是Watcher原型上的addDep方法,请看下面 } }; Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.depIds.has(id)) { this.depIds.add(id); //将依赖的每个dep,添加到 watcher 的 deps集合中,完成数据的收集 this.depIds.push(dep); } }; Dep.prototype.notify = function notify () { // 用来出发watcher的更新,每当数据改变,每个数据的dep就会出发motify方法,将收集的watcher逐个进行数据更新, //这里就是为什么computed知道依赖改变了,然后就会自动更新。其实不是computed知道依赖改变的,而是依赖改变以后出发computed更新。 for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; Dep.target = null; var targetStack = []; function pushTarget (target) { targetStack.push(target); // 将 Dep 原型 上的 Target 设置为 watcher Dep.target = target; } function popTarget () { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; } // vm 是 Vue 实例 // expOrFn 是传过来的数据 get 函数 // cb 是回调函数 var Watcher = function Watcher (vm,expOrFn,cb,) { this.vm = vm; this.cb = cb; this.id = ++uid$2; // uid for batching this.deps = []; // deps 是用来收集 依赖数据节点 的集合 this.depIds = new _Set(); this.value = this.get(); // 首次执行get方法(get也就是expOrFn),初次收集依赖是在这个环节 }; Watcher.prototype.get = function get () { pushTarget(this); // 将 Dep 的 target 设置为当前 watcher,这个函数在 Dep 那块最后面 var value; var vm = this.vm; value = this.getter.call(vm, vm); popTarget(); this.cleanupDeps(); 将 Dep 的 target 设置为当前 空,这个函数我会放在后面 } return value }; /** * 添加依赖的方法,重点!!! 着重看一下 (我进行了一些简化,便于理解) */ Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.depIds.has(id)) { this.depIds.add(id); this.depIds.push(dep); } if (!this.depIds.has(id)) { dep.addSub(this); } //这步是将自身(也就是watcher)添加到dep中的watcher集合,派发数据更新时用到 }; /** * 更新数据的方法,在派发更新的时候会用到。 computed 更新数据的时候,用 dep 的 notify 方法进 * 行更新数据,更新数据主要调用的是 run 方法 */ Watcher.prototype.update = function update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true; } else if (this.sync) { this.run(); } else { queueWatcher(this); } }; /** * 在这个阶段主要是运行get方法,拿到数据 (简化以后的代码) */ Watcher.prototype.run = function run () { if (this.active) { var value = this.get(); this.value = value } }; /** * 深度收集依赖,computed 可以收集 computed 数据就是依靠这个方法 */ Watcher.prototype.depend = function depend () { console.log(this.deps) var i = this.deps.length; while (i--) { this.deps[i].depend(); //注意这里的 depend 方法是 Dep 原型上的方法,不是Watcher 的法 } };

大家可以仔细看一下我的注释,进行了每个步骤的描述,当然都只是我自己的理解,有不对的地方还请大家多多指教。

现在来细说一下收集依赖的流程。当初始化Vue实例以后, 在初始化 computed 阶段,vue 会对每个 computed 进行运算和收集依赖。当 computed 初始化的时候会依靠当前的 computed 生成一个 Watcher,并且将 getter 方法传入。

function initComputed (vm, computed) { var watchers = vm._computedWatchers = Object.create(null); for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if (getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ); } ) }

 这时候已经有了watcher实例。 当 watcher 初始化,会调用 get方法(收集依赖在这里),

Watcher.prototype.get = function get () { pushTarget(this); // 将 Dep 的 target 设置为当前 watcher,这个函数在 Dep 那块最后面 var value; var vm = this.vm; value = this.getter.call(vm, vm); popTarget(); this.cleanupDeps(); 将 Dep 的 target 设置为当前 空,这个函数我会放在后面 } return value };

get执行得时候会调用pushTarget方法(这个方法就是将当前的全局的 Dep.target 设置成当前运行的 watcher ),然后会进行 getter(),getter() 执行的时候会触发 每个数据 的 get 方法,请看get源码

Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; if (Dep.target) { //判断当前执行是否为watcher,也可以说是否为computed运算的数据 dep.depend(); // 收集到watcher里面的deps里面 if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value })

这里的 get 方法会首先判断是否有 Dep.target,这个判断最终目的是判断当前获取数据的是否是watcher,如果是,就会调用 Dep 的 depend 方法进行收集,这样 watcher 就会把运算的每个数据的dep收集到 自己的 deps 属性中,完成了数据依赖收集。

咱们再来细说一下Dep 的 depend 方法,这个方法主要是调用的 Watcher 里面的 addDep方法, 这个方法进行的是双向数据收集,也就是说 watcher 会收集 deps, 同样 dep 也会收集 watcher 集合,存到dep 的 subs 属性中, 每当 dep 的数据更新时,就会将subs的每个 watcher 进行 update ,这样就完成了数据更新,这就是 computed 的实现原理。

简单点说,就是 computed 在运行的时候,首先将全局的 Dep.target 设置当前 computed 的 watcher,然后在运行 computed 代码,里面用到的数据会调用它们自己的 get 方法,在 get 方法里,他们会将自己的 dep.id 存到当前的 Dep.target 里,然后还要将当前的 Dep.target(也就是当前的 watcher) 存到自己的 dep.subs 属性 中,每当自己数据更新触发 set 方法,就会把自己 dep.subs 中的每个watcher 拿出来进行数据更新,从而更新 computed 。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有