摘要:回顾在前面的几个中,我们实现对象的属性的监听,但是有关于数组的行为我们一直没有处理。并且上述的几个数组方法是数组对象提供的,我们要想办法去触发下的函数。在设置值的时候就能成功触发依赖。
看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。
回顾在前面的几个 step 中,我们实现对象的属性的监听,但是有关于数组的行为我们一直没有处理。
我们先分析下导致数组有哪些行为:
调用方法:arr.splice(1, 2, "something1", "someting2")
直接赋值:arr[1] = "something"
解决行为一首先我们知道数组下的一些方法是会对原数组照成影响的,有以下几个:
push
pop
shift
unshift
splice
sort
reverse
这几个方法总的来说会照成几个影响:
数组长度发生变化
数组内元素顺序发生变化
不像对象,如果对象的 key 值的顺序发生变化,是不会影响视图的变化,但数组的顺序如果发生变化,视图是要变化的。
也就是说当着几个方法触发的时候,我们需要视图的更新,也就是要触发 Dep 中的 notify 函数。
但是纵观我们现在实现的代码( step5 中的代码),我们并没有特地的为数组提供一个 Dep。
并且上述的几个数组方法是数组对象提供的,我们要想办法去触发 Dep 下的 notify 函数。
我们先为数组提供一个 Dep ,完善后的 Observer :
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { // 为数组设置一个特殊的 Dep this.dep = new Dep() this.observeArray(value) } else { this.walk(value) } Object.defineProperty(value, "__ob__", { value: this, enumerable: false, writable: true, configurable: true }) } /** * 遍历对象下属性,使得属性变成可监听的结构 */ walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * 同上,遍历数组 */ observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
同样的在 defineReactive 我们需要处理数组添加依赖的逻辑
export function defineReactive(object, key, value) { let dep = new Dep() let childOb = observe(value) Object.defineProperty(object, key, { configurable: true, enumerable: true, get: function () { if (Dep.target) { dep.addSub(Dep.target) Dep.target.addDep(dep) // 处理数组的依赖 if(Array.isArray(value)){ childOb.dep.addSub(Dep.target) Dep.target.addDep(childOb.dep) } } return value }, set: function (newValue) { if (newValue !== value) { value = newValue dep.notify() } } }) }
ok 我们现在完成了依赖的添加,剩下的我们要实现依赖的触发。
处理方法:在数组对象调用特定方法时,首先找到的应该是我们自己写的方法,而这个方法中调用了原始方法,并触发依赖。
我们先来包装一下方法,得到一些同名方法:
const arrayProto = Array.prototype // 复制方法 export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse" ] /** * 改变数组的默认处理,将新添加的对象添加监听 */ methodsToPatch.forEach(function (method) { // 原始的数组处理方法 const original = arrayProto[method] let mutator = function (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case "push": case "unshift": inserted = args break case "splice": inserted = args.slice(2) break } // 新添加的对象需要添加监听 if (inserted) ob.observeArray(inserted) // 触发 notify 方法 ob.dep.notify() return result } Object.defineProperty(arrayMethods, method, { value: mutator, enumerable: false, writable: true, configurable: true }) })
ok 我们现在得到了一些列同名的方法,我只要确保在调用时,先调用到我们的方法即可。
有两种方式可以实现:
数组对象上直接有该方法,这样就不会去找对象上的原型链
覆盖对象的 __proto__ ,这样寻找原型链时,就会先找到我们的方法
具体到代码中的实现:
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { this.dep = new Dep() const augment = ("__proto__" in {}) ? protoAugment : copyAugment // 覆盖数组中一些改变了原数组的方法,使得方法得以监听 augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } ... } ... } /** * 如果能使用 __proto__ 则将数组的处理方法进行替换 */ function protoAugment (target, src, keys) { target.__proto__ = src } /** * 如果不能使用 __proto__ 则直接将该方法定义在当前对象下 */ function copyAugment (target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] Object.defineProperty(target, key, { value: src[key], enumerable: false, writable: true, configurable: true }) } }
测试一下:
let object = { arrayTest: [1, 2, 3, 4, 5] } observe(object) let watcher = new Watcher(object, function () { return this.arrayTest.reduce((sum, num) => sum + num) }, function (newValue, oldValue) { console.log(`监听函数,数组内所有元素 = ${newValue}`) }) object.arrayTest.push(10) // 监听函数,数组内所有元素 = 25
到现在为止,我们成功的在数组调用方法的时候,添加并触发了依赖。
解决行为二首先先说明,数组下的索引是和对象下的键有同样的表现,也就是可以用 defineReactive 来处理索引值,但是数组是用来存放一系列的值,我们并不能一开始就确定数组的长度,并且极有可能刚开始数组长度为 0,之后数组中的索引对应的内容也会不断的变化,所以为索引调用 defineReactive 是不切实际的。
但是类似于 arr[1] = "something" 这样的赋值在数组中也是常见的操作,在 Vue 中实现了 $set 具体的细节这里不谈,这里实现了另一种方法,我们仅仅需要在数组对象下添加一个方法即可:
arrayMethods.$apply = function () { this.__ob__.observeArray(this) this.__ob__.dep.notify() }
测试一下:
object.arrayTest[1] = 10 object.arrayTest.$apply() // 监听函数,数组内所有元素 = 33
到目前为了,一个完整的数据监听的模型也就完成了,我们可以使用 observe 方法来得到一个可监听结构,然后用 Watcher 添加依赖。
在设置值的时候就能成功触发依赖。
点击查看相关代码
系列文章地址VUE - MVVM - part1 - defineProperty
VUE - MVVM - part2 - Dep
VUE - MVVM - part3 - Watcher
VUE - MVVM - part4 - 优化Watcher
VUE - MVVM - part5 - Observe
VUE - MVVM - part6 - Array
VUE - MVVM - part7 - Event
VUE - MVVM - part8 - 优化Event
VUE - MVVM - part9 - Vue
VUE - MVVM - part10 - Computed
VUE - MVVM - part11 - Extend
VUE - MVVM - part12 - props
VUE - MVVM - part13 - inject & 总结
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/94449.html
摘要:在中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。换个说法,当我们取值的时候,函数自动帮我们添加了针对当前值的依赖,当这个值发生变化的时候,处理了这些依赖,比如说节点的变化。 在 VUE 中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。 首先,我们需要了解一下 js 中的一个 API :Object.defineProperty(obj, prop,...
摘要:事件是什么在标准浏览器中,我们经常使用来为一个添加一个事件等。仔细看这些情况,归结到代码中,无非就是一个行为或情况的名称,和一些列的动作,而在中动作就是,一系列的动作就是一个函数的集合。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 事件是什么? 在标准浏览器中,我们经常使用:addEventListener 来为一个 DOM 添加一个事件(click、mouse...
摘要:看这篇之前,如果没看过先移步看实现中。同样的,在取值时收集依赖,在设置值当值发生变化时触发依赖。中实现了一个的类来处理以上两个问题,之后再说。以下语法下的,源码中差不多就这样点击查看相关代码系列文章地址优化优化总结 看这篇之前,如果没看过 step1 先移步看 实现 VUE 中 MVVM - step1 - defineProperty。 在上一篇我们大概实现了,Vue 中的依赖收集和...
摘要:所以方法,是对默认进行扩展,从而实现扩展。这里我用了这个库提供的合并方法,用来合并两个对象,并不会修改原对象的内容。测试符合我们的预期,方法也就实现了,下一步,实现父子组件。系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 组件的扩展 在 Vue 中有 extend 方法可以扩展 Vue 的实例,在上一步中,有一些实现是必须要通过子父组件才...
摘要:看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。回顾在上一步我们实现了一个简易的事件管理的类,接下来我们把它给优化下,方便我们的使用。接着我们来优化。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 回顾 在上一步我们实现了一个简易的事件管理的类,接下来我们把它给优化下,方便我们的使用。主要优化内容: 方便为多个事件添加同一个函数 方便为一个事件...
阅读 1208·2021-11-11 16:54
阅读 1721·2021-10-13 09:40
阅读 919·2021-10-08 10:05
阅读 3479·2021-09-22 15:50
阅读 3646·2021-09-22 15:41
阅读 1716·2021-09-22 15:08
阅读 2312·2021-09-07 10:24
阅读 3554·2019-08-30 12:52