为什么Object.observe()已被弃用?

63

有没有其他的方法?

在对象中是否有其他的检测变化的方式?

虽然可以使用代理方法,但请问有谁能告诉我如何使用代理实现这一点:

var obj = {
  foo: 0,
  bar: 1
};

Object.observe(obj, function(changes) {
  console.log(changes);
});

obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]

obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

5
现在有Proxy()了! - Jai
3
Proxy(代理)是Javascript中的一个内置对象,可以用来自定义对象的基本操作行为。使用Proxy对象,可以拦截并重定义对象上的默认操作,例如获取属性值、设置属性值、函数调用等等。通过重写这些操作,你可以实现元编程(metaprogramming)和操作拦截(interception),从而实现更加灵活的编程方式。 - Arun P Johny
1
或者使用 gettersetter - Arun P Johny
我看到了代理方法,但有些困惑,请您能否提供一个简单的例子。 - Shad
4
相对于Object.observe,代理和getter/setter非常有限。我希望它没有被弃用。 - trusktr
2
Object.observe没有破坏严格相等。 - Matthew James Davis
4个回答

67

你可以通过使用getter和setter来实现这一点。

var obj = {
  get foo() {
    console.log({ name: 'foo', object: obj, type: 'get' });
    return obj._foo;
  },
  set bar(val) {
    console.log({ name: 'bar', object: obj, type: 'set', oldValue: obj._bar });
    return obj._bar = val;
  }
};

obj.bar = 2;
// {name: 'bar', object: <obj>, type: 'set', oldValue: undefined}

obj.foo;
// {name: 'foo', object: <obj>, type: 'get'}

或者,如果您使用支持代理的浏览器,您可以编写一个更通用的解决方案。

var obj = {
  foo: 1,
  bar: 2
};

var proxied = new Proxy(obj, {
  get: function(target, prop) {
    console.log({ type: 'get', target, prop });
    return Reflect.get(target, prop);
  },
  set: function(target, prop, value) {
    console.log({ type: 'set', target, prop, value });
    return Reflect.set(target, prop, value);
  }
});

proxied.bar = 2;
// {type: 'set', target: <obj>, prop: 'bar', value: 2}

proxied.foo;
// {type: 'get', target: <obj>, prop: 'bar'}

5
@Emissary 使用对象访问将再次触发代理陷阱,导致无限循环。 - Dan Prince
3
谢谢,但我无法使用 target[prop] = value 复制一个无限循环 - 你在哪个环境中运行?另外,代理不应该替换原始变量以便 "观察",否则 obj.bar = x 不会像这里所建议的那样起作用。 - Emissary
2
可能与不完整的代理支持有关。更深入的讨论请参见这里 - Dan Prince
2
@DanPrince Reflect.get确实会触发get拦截器。MDN - Mason
3
@Mason,你的观点很重要,所以我也想发表一下我的看法:无论是对象访问还是Reflect.get,如果在代理(proxy)上运行,都__会__触发陷阱(traps),但如果针对目标(target)对象运行,则__不会__被陷阱拦截。因此,这里的重要一点是,在陷阱中应该使用target,使用哪种访问方法则次要。 - GullerYA
显示剩余3条评论

19
免责声明:我是下面提到的 object-observer 库的作者。
我不建议使用 getter/setter 解决方案,它很复杂、不可扩展且难以维护。Backbone 就是用这种方式进行双向绑定的,而要使其正常工作所需要的样板代码相当冗长。
使用代理是实现你所需功能的最佳方式,只需在上面的示例中添加一些回调函数注册和管理,并在变化发生时执行它们即可。
至于 polyfill 库:其中一些/大多数采用了“dirty check”或轮询技术来实现,效率不高也不够优秀,Nirus 上面指出的 polyfill 就是这种情况。
我建议选择一些通过代理实现观察的库。有几个库可以选择,object-observer 就是其中之一:它针对这种用例进行编写,利用本机代理(Native Proxies)实现,提供深度树观察等功能。

1
浏览器支持怎么样?代理无法进行polyfill或转译,因此在生产环境中,我认为使用代理还不是一个好主意! - John Slegers
2
没错,这个实现只能在支持代理对象的环境下运行。然而,所有可下载的主流浏览器都已经支持了(Chrome、Firefox、Opera),实际上Edge也是如此。移动版本也都支持。所以我唯一能看到的问题就是IE pre-Edge - 不过,就我个人而言,我已经把它抛在了脑后。 - GullerYA
在企业界,放弃IE支持通常不是一个选项。 - John Slegers
4
作为一名企业员工,你正在推开敞开的门!这个项目有点想要表达我自己的意愿,同时也是我自己Web应用的助手,以及对任何类似于Angular <任何版本>/ React等的东西的极度不喜欢的表现。我现在正在使用它来编写与CustomElements密切集成的东西,并享受这个新世界。好的和坏的就是没有IE。 - GullerYA

14

@Dan Prince 的解决方案应该始终是首选.

以防万一,如果您希望支持相当老的浏览器,我建议您使用 GitHub 上可用的任何 polyfill 库或使用 Object.defineProperties API 在 IE 9 中模拟相同效果。

var obj = Object.defineProperties({}, {
    "foo":{
        get:function(){
            console.log("Get:"+this.value);
        },
        set:function(val){
            console.log("Set:"+val);
            this.value = val;
        }
    },

    "bar":{         
        get:function(){
            console.log("Get:"+this.value);
        },
        set:function(val){
            console.log("Set:"+val);
            this.value = val;
        }
    }
 });

注意: 这不是可扩展的解决方案。在使用以上API处理更大数据对象和计算密集型需求时,请做出明智的决策。


2

最简单的观察者,只关注任何对象的更改。

返回更改的属性和新值,以匹配 OP 的原始查询。

请注意,这需要使用双重赋值的方式。

/* Simplest Object Observer */
Object.observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })

var obj = {
  foo: 0,
  bar: 1
};

// Assignment after creation, keep the variable name, extend.
obj = Object.observe(obj, function(target, prop, changes) {
  console.log("Change detected!", prop, changes)
})

obj.baz = 2;

obj.foo = 'hello';

我相信它可以用于很多方面,适用于所有需要链式反应赋值的场景。


同样地,作为一个Object原型,它允许在单个调用中链接声明。 在这两个例子中,我们可以在observe调用中使用this,如下所示。
请注意,在这种情况下,我们必须先将Object包装在括号中。

/* Simplest Prototype Object Observer */
Object.prototype.observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })

const obj = ({
  foo: 0,
  bar: 1
}).observe(this, ({}, prop, changes) => console.log("Change detected!", prop, changes))

obj.baz = 2;

obj.foo = 'hello';


要实际设置观察对象上的值:

/* Simplest Observer function */
const observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })

let obj = {
  foo: 0,
  bar: 1
};

obj = observe(obj, (target, prop, changes) => {
  target[prop] = changes
  console.log("Change detected!", prop, changes)
})

obj.foo = -10 // Hot start

setInterval(() => obj.foo = obj.foo + 1, 3000)


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