资讯专栏INFORMATION COLUMN

VUE - MVVM - part5 - Observe

xi4oh4o / 1375人阅读

摘要:具体代码执行方式进入到的目录下,命令行运行即可。确保为一个对象如果对象下有则不需要再次生成函数返回该对象的实例,这里判断了如果该对象下已经有实例,则直接返回,不再去生产实例。这就确保了一个对象下的实例仅被实例化一次。

看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。

回顾

step4 中,我们大致实现了一个 MVVM 的框架,由3个部分组成:

defineReactive 控制了对象属性,使变为可监听结构

Dep 收集管理依赖

Watcher 一个抽象的依赖

defineReactiveDep 改造了对象下的某个属性,将目标变成了观察者模式中的目标,当目标发生变化时,会调用观察者;

Watcher 就是一个具体的观察者,会注册到目标中。

之前的代码实现了观察者模式,使得数据的变化得以响应,但是还是有两个需要优化的地方:

如果我们想让对象的属性都得以响应,那我们必须对对象下的所有属性进行遍历,依次调用 defineReactive 这不是很方便

代码都在一个文件中,不利于管理

解决 问题2

先解决第二个问题,我们仅仅需要把代码进行划分即可,然后用 webpack/babel 打包即可,当然这里就不说如何去配置 webpack ,使用 webpack/babel 我们就可以使用 ES6 的语法和模块系统了。

但是为了偷懒,我把代码直接放在 node 环境中执行了,但是 import 语法需要特定的 node 版本,我这里使用的是 8.11.1(版本网上都应该是支持的),同时需要特定的文件后缀(.mjs)和命令 node --experimental-modules xxx.mjs

具体代码

执行方式进入到 step5 的目录下,命令行运行 node --experimental-modules test.mjs 即可。

当然你也可以用 webpack/babel 进行打包和转码,然后放到浏览器上运行即可。

问题1

对于问题1,我们需要做的仅仅是实现一个方法进行遍历对象属性即可。我们把这个过程抽象成一个对象 Observe 。至于为什么要把这个过程抽象成一个对象,后面会说。

注: 由于是在 node 环境下运行代码,这里就直接用 ES6 的语法了。同样的我把别的模块也用 ES6 语法写了一遍。

export class Observer {

    constructor(value) {
        this.value = value
        this.walk(value)
        // 标志这个对象已经被遍历过,同时保存 Observer
        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]])
        }
    }
}

从代码可以看出,这个类在实例化的时候自动遍历了传入参数下的所有属性(value),并把每个属性都应用了 defineReactive
为了确保传入的值为对象,我们再写一个方法来判断。

export function observe (value) {
    // 确保 observe 为一个对象
    if (typeof value !== "object") {
        return
    }
    let ob
    // 如果对象下有 Observer 则不需要再次生成 Observer
    if (value.hasOwnProperty("__ob__") && value.__ob__ instanceof Observer) {
        ob = value.__ob__
    } else if (Object.isExtensible(value)) {
        ob = new Observer(value)
    }
    return ob
}

函数返回该对象的 Observer 实例,这里判断了如果该对象下已经有 Observer 实例,则直接返回,不再去生产 Observer 实例。这就确保了一个对象下的 Observer 实例仅被实例化一次。

上面代码实现了对某个对象下所有属性的转化,但是如果对象下的某个属性是对象呢?
所以我们还需改造一下 defineReactive 具体代码为:

export function defineReactive(object, key, value) {
    let dep = new Dep()
    // 遍历 value 下的属性,由于在 observe 中已经判断是否为对象,这里就不判断了
    observe(value)
    Object.defineProperty(object, key, {
        configurable: true,
        enumerable: true,
        get: function () {
            if (Dep.target) {
                dep.addSub(Dep.target)
                Dep.target.addDep(dep)
            }
            return value
        },
        set: function (newValue) {
            if (newValue !== value) {
                value = newValue
                dep.notify()
            }
        }
    })
}

ok 我们来测试下

import Watcher from "./Watcher"
import {observe} from "./Observe"

let object = {
    num1: 1,
    num2: 1,
    objectTest: {
        num3: 1
    }
}

observe(object)

let watcher = new Watcher(object, function () {
    return this.num1 + this.num2 + this.objectTest.num3
}, function (newValue, oldValue) {
    console.log(`监听函数,${object.num1} + ${object.num2} + ${object.objectTest.num3} = ${newValue}`)
})

object.num1 = 2
// 监听函数,2 + 1 + 1 = 4
object.objectTest.num3 = 2
// 监听函数,2 + 1 + 2 = 5

当然为了更好的了解这个过程,最好把 step5 目录中的代码拉下来一起看。至于之前实现的功能这里就不专门写测试了。

最后

最后解释下为什么要把遍历对象属性这个过程抽象成一个对象

对象在 js 下存放是是引用,也就是说有可能几个对象下的某个属性是同一个对象下的引用,如下

let obj1 = {num1: 1}
let obj2 = {obj: obj1}
let obj3 = {obj: obj1}

如果我们抽象成对象,而仅仅是函数调用的话,那么 obj1 这个对象就会遍历两次,而抽象成一个对象的话,我们可以把这个对象保存在 obj1 下(__ob__ 属性),遍历的时候判断一下就好。

当然解决上面问题我们也可以在 obj1 下设置一个标志位即可,但是这个对象在之后会有特殊的用途,先这样写吧。(与数组和 Vue.set 有关)

在代码中我为 DepWatch 添加了 id 这个暂时用不到,先加上。

点击查看相关代码

系列文章地址

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/94305.html

相关文章

  • VUE - MVVM - part6 - Array

    摘要:回顾在前面的几个中,我们实现对象的属性的监听,但是有关于数组的行为我们一直没有处理。并且上述的几个数组方法是数组对象提供的,我们要想办法去触发下的函数。在设置值的时候就能成功触发依赖。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 回顾 在前面的几个 step 中,我们实现对象的属性的监听,但是有关于数组的行为我们一直没有处理。我们先分析下导致数组有哪些行为: ...

    0x584a 评论0 收藏0
  • VUE - MVVM - part9 - Vue

    摘要:调用父类的方法类在我们上一步已经实现。我们先实现的绑定,因为是要被监听,所以要进行进一步的处理。调用父类的方法方法绑定完事,其实就这么简单。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 前言 激动人心的时候即将来临,之前我们做的 8 步,其实都在为这一步打基础,这一步,我们来简单实现一个 Vue 对象,还没有看过之前代码的同学,请确认看过之前的文章。 主要实现内...

    yzd 评论0 收藏0
  • VUE - MVVM - part1 - defineProperty

    摘要:在中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。换个说法,当我们取值的时候,函数自动帮我们添加了针对当前值的依赖,当这个值发生变化的时候,处理了这些依赖,比如说节点的变化。 在 VUE 中关于如何实现在网上可以搜出不少,在看了部分源码后,梳理一下内容。 首先,我们需要了解一下 js 中的一个 API :Object.defineProperty(obj, prop,...

    liukai90 评论0 收藏0
  • VUE - MVVM - part7 - Event

    摘要:事件是什么在标准浏览器中,我们经常使用来为一个添加一个事件等。仔细看这些情况,归结到代码中,无非就是一个行为或情况的名称,和一些列的动作,而在中动作就是,一系列的动作就是一个函数的集合。 看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。 事件是什么? 在标准浏览器中,我们经常使用:addEventListener 来为一个 DOM 添加一个事件(click、mouse...

    xialong 评论0 收藏0
  • VUE - MVVM - part12 - props

    摘要:看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。而该组件实例的父实例却并不固定,所以我们将这些在使用时才能确定的参数在组件实例化的时候传入。系列文章地址优化优化总结 看这篇之前,如果没有看过之前的文章,移步拉到文章末尾查看之前的文章。 前言 在上一步,我们实现 extend 方法,用于扩展 Vue 类,而我们知道子组件需要通过 extend 方法来实现,我们从测试例...

    bluesky 评论0 收藏0

发表评论

0条评论

xi4oh4o

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<