如何在JavaScript中观察复杂对象及其变化?

3
我正在寻找类似于AngularJS的$watch函数(定义在这里),它允许“观察复杂对象”及其更改。据我所知,它可以监视对象内变量的更改,即使它们本身也在一个对象中(在被监视的对象内部)。
我希望在原生JavaScript(或JQuery)中具有相同的“可监视性”,但我似乎找不到任何东西。 我知道Object.watch()和Polyfill,它们可以在这里找到,但我非常确定这只进行引用检查,或者仅监视对象内的“直接”变量,并且不会检查嵌套的属性“深入”对象内部。
是否有人知道任何库、函数、任何能够帮助我提供这种“深层监视”功能的方法?或者,如果Object.watch()确实提供了我想要的功能,甚至可以帮助我更好地理解它?
我正在创建一个实时音乐应用程序,希望对乐器进行“深度”监视,以便我可以看到它的任何变量、参数等是否发生更改,以便我可以将其与服务器和其他客户端同步。
任何帮助都将不胜感激,谢谢!

1
注意:链接的问题是已过时的解决方案或旧的解决方案。使用代理的解决方案更通用和现代化。代理对象有92%的支持(不过在IE上无法工作-请检查ES6 Proxy Polyfill for IE11)。 - jcubic
3个回答

11

正如@Booster2ooo所提到的,您可以使用Proxy对象来观察更改,您可以使用以下代码:

function proxify(object, change) {
    // we use unique field to determine if object is proxy
    // we can't test this otherwise because typeof and
    // instanceof is used on original object
    if (object && object.__proxy__) {
         return object;
    }
    var proxy = new Proxy(object, {
        get: function(object, name) {
            if (name == '__proxy__') {
                return true;
            }
            return object[name];
        },
        set: function(object, name, value) {
            var old = object[name];
            if (value && typeof value == 'object') {
                // new object need to be proxified as well
                value = proxify(value, change);
            }
            object[name] = value;
            change(object, name, old, value);
        }
    });
    for (var prop in object) {
        if (object.hasOwnProperty(prop) && object[prop] &&
            typeof object[prop] == 'object') {
            // proxify all child objects
            object[prop] = proxify(object[prop], change);
        }
    }
    return proxy;
}

你可以像这样使用此函数:

object = proxify(object, function(object, property, oldValue, newValue) {
    console.log('property ' + property + ' changed from ' + oldValue +
                ' to ' + newValue);
});

...


非常感谢您提供代理函数的示例,因为我对它如何应用于我的问题感到困惑。您提供的函数确实显示了对象的所有更改,这非常棒!现在我需要调整它以显示我所需的内容 :) 因为我正在使用您的代码,所以我想将您的答案标记为最佳答案,但是您提到它是另一个评论的扩展,您认为我应该将哪个标记为答案? - Alistair Hughes
@AlistairHughes 不确定,也许更好,因为它有实际的代码,这将对其他遇到相同问题的人有用。 - jcubic
@jcubic,感谢您提供这个使用代理的绝佳示例。 - Zig Shanklin

2

不应使用Object.watch

警告:通常情况下,应尽量避免使用watch()和unwatch()。这两种方法仅在Gecko中实现,并且主要用于调试。此外,使用观察点会对性能产生严重的负面影响,特别是在全局对象(例如window)上使用时更为明显。通常可以使用setter和getter或代理。有关详细信息,请参见浏览器兼容性。还请不要将Object.watch与Object.observe混淆。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch

我更喜欢看一下Proxies:

Proxy对象用于定义基本操作的自定义行为(例如属性查找、赋值、枚举、函数调用等)。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

如果涉及到DOM,则可能需要使用MutationObserver:

MutationObserver提供了一种方式,使开发人员能够对DOM中的更改做出反应。它被设计为DOM3事件规范中定义的Mutation Events的替代品。

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

探索并享受吧 :)


1

jcubic的答案很不错,但遗憾的是它无法处理嵌套对象。

我在GitHub上发布了一个库(Observable Slim),允许您观察/监视对对象及其任何嵌套子对象所做的更改。 它还具有一些额外的功能:

  • 每当发生更改时,向指定回调报告。
  • 将防止用户尝试代理代理。
  • 保留已代理对象的存储,并重复使用现有代理而不是创建新代理(非常显著的性能影响)。
  • 采用ES5编写,并使用Proxy polyfill的分支版本,因此可以相当轻松地在旧浏览器中部署并支持数组变异方法。

它的工作原理如下:

var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
    console.log(JSON.stringify(changes));
});

p.testing.blah = 42; // console:  [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]

请随意查看,希望您也能做出贡献!


我不确定你是否正确,我的proxify函数是递归的,它应该适用于任何嵌套对象。每个嵌套对象也应该被代理。 - jcubic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接