实现 VUE 中 MVVM - step5 - Observe

栏目: 编程工具 · 发布时间: 6年前

内容简介:在

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 这个暂时用不到,先加上。

点击查看相关代码


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Design Handbook

Web Design Handbook

Baeck, Philippe de 编 / 2009-12 / $ 22.54

This non-technical book brings together contemporary web design's latest and most original creative examples in the areas of services, media, blogs, contacts, links and jobs. It also traces the latest......一起来看看 《Web Design Handbook》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具