在JavaScript中,监听对象属性的变化是否可能?

23

我正在开发一个非常繁琐的网络界面,它主要由JavaScript构建。它基本上是一个(非常)大的表单,有许多部分。每个部分都是基于表单的其他部分选项构建的。每当这些选项更改时,新值都会在“注册表”类型的对象中记录,并且其他部分相应地重新填充。

在许多表单字段上具有事件监听器开始减慢速度,而对于每个更改刷新整个表单会使用户体验变得过重/缓慢。

我想知道是否可能将侦听器添加到注册表对象的属性而不是表单元素上以加快速度?如果可以的话,您能否提供/指向一些示例代码?

更多信息:

  • 这是jQuery的插件,因此可以从该库中构建的任何功能都会有所帮助,但并非必需。
  • 我们的用户使用IE6/7、Safari和FF2/3,因此如果只有“现代”浏览器才支持某种方法,我将不得不找到另一种解决方案。
7个回答

6
据我所知,除了Object.watch之外,在对象属性更改时没有触发任何事件。
为什么不在可能的情况下尽可能使用事件委托呢?也就是说,对表单进行事件处理,而不是对单个表单元素进行事件处理,捕获事件并使其冒泡?
例如(我的 jQuery 有点生疏,请原谅我使用 Prototype,但我相信您可以轻松适应):
$(form).observe('change', function(e) {
    // To identify changed field, in Proto use e.element()
    // but I think in jQuery it's e.target (as it should be)
});

你也可以在文本框失去焦点之前捕获inputkeyuppaste事件。我的解决方案通常是:
  1. 基于Gecko/Webkit的浏览器:观察form上的input
  2. 同时在基于Webkit的浏览器中:观察textarea上的keyuppaste事件(它们不会因为某些原因在textarea上触发input)。
  3. IE:观察form上的keyuppaste
  4. 观察form上的change(这会在select上触发)。
  5. 对于keyuppaste事件,通过将文本字段的value与其defaultValue进行比较,将字段的当前值与其默认值(页面加载时的值)进行比较。

编辑:以下是我开发的防止未修改表单提交等操作的示例代码:

如何通过JavaScript跟踪表单中的更改最佳方式?


Object.observe()已被弃用。为了您自己的利益,请尽量避免使用它。 - andersfylling

6

感谢大家的意见。我选择了以下翻译:


var EntriesRegistry = (function(){

    var instance = null;

    function __constructor() {

        var
            self = this,
            observations = {};

        this.set = function(n,v)
        {
            self[n] = v;

            if( observations[n] )
                for( var i=0; i < observations[n].length; i++ )
                    observations[n][i].apply(null, [v, n]);

        }

        this.get = function(n)
        {
            return self[n];
        }

        this.observe = function(n,f)
        {

            if(observations[n] == undefined)
                observations[n] = [];

            observations[n].push(f);
        }

    }

    return new function(){
        this.getInstance = function(){
            if (instance == null)
            {
                instance = new __constructor();
                instance.constructor = null;
            }
            return instance;
        }
    }
})();

var entries = EntriesRegistry.getInstance();

var test = function(v){ alert(v); };

entries.set('bob', 'meh');

entries.get('bob');

entries.observe('seth', test);

entries.set('seth', 'dave');

考虑到您的评论,我将使用事件委托来更新注册表并触发已注册的观察方法。

到目前为止,这对我来说运行良好...你们认为有什么问题吗?


2
您可以将一个监听器附加到一个容器(如body或form),然后使用事件参数来对更改做出反应。您可以获得所有监听器的好处,但只需为容器附加一个监听器,而不是为每个元素附加一个监听器。
$('body').change(function(event){
    /* do whatever you want with event.target here */
    console.debug(event.target); /* assuming firebug */
 });

事件目标(event.target)保存了被点击的元素。 SitePoint 在这里对事件委托有一个不错的解释:
JavaScript 事件委托是一种简单的技术,通过在父元素上添加单个事件处理程序来避免必须为多个子元素添加事件处理程序。

1

基于 Mozilla 引擎的浏览器支持 Object.watch,但我不知道是否存在跨浏览器兼容的等效方法。

您是否使用 Firebug 对页面进行了分析以了解确切的缓慢原因,或者“大量事件处理程序”只是猜测?


1

对之前的答案进行小修改:将可观察代码移至对象中,可以将其抽象化,并使用jQuery的extend方法将其用于扩展其他对象。

ObservableProperties = {
    events : {},
    on : function(type, f)
    {
        if(!this.events[type]) this.events[type] = [];
        this.events[type].push({
            action: f,
            type: type,
            target: this
        });
    },
    trigger : function(type)
    {
        if (this.events[type]!==undefined)
        {
            for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
            {
                this.events[type][e].action(this.events[type][e]);
            }
        }
    },
    removeEventListener : function(type, f)
    {
        if(this.events[type])
        {
            for(var e = 0, imax = this.events[type].length ; e < imax ; ++e)
            {
                if(this.events[type][e].action == f)
                    this.events[type].splice(e, 1);
            }
        }
    }
};
Object.freeze(ObservableProperties);

var SomeBusinessObject = function (){
    self = $.extend(true,{},ObservableProperties);
    self.someAttr = 1000
    self.someMethod = function(){
        // some code
    }
    return self;
}

请查看这个 JSFiddle:https://jsfiddle.net/v2mcwpw7/3/


有人可能会认为我们可以通过执行$.extend(true,this,ObservableProperties)ObservableProperties对象直接绑定到业务对象上。这看起来更好。但是,要注意作用域。当我在作用域方面做一些复杂的事情(或单例模式),我使用一个self变量,否则如果所有内容都是公共的,我就使用this。请参见此fiddle - Sumi Straessle

0

jQuery 真是太棒了。虽然你也可以看看 ASP.NET AJAX 预览版。

一些功能只是 .js 文件,不依赖于 .NET。也许你会发现观察者模式的实现很有用。

var o = { foo: "Change this string" };

Sys.Observer.observe(o);

o.add_propertyChanged(function(sender, args) {
    var name = args.get_propertyName();
    alert("Property '" + name + "' was changed to '" + sender[name] + "'.");
});

o.setValue("foo", "New string value.");

此外,客户端模板已准备好用于一些有趣的场景。
最后需要注意的是,这与jQuery完全兼容($没有问题)。
链接:主页, 我目前使用的版本

0
我正在寻找同样的东西,然后看到了你的问题...没有一个答案能够满足我的需求,所以我想出了这个解决方案,现在想分享给大家:
var ObservedObject = function(){
    this.customAttribute = 0
    this.events = {}
    // your code...
}
ObservedObject.prototype.changeAttribute = function(v){
     this.customAttribute = v
     // your code...
     this.dispatchEvent('myEvent')
}
ObservedObject.prototype.addEventListener = function(type, f){
    if(!this.events[type]) this.events[type] = []
    this.events[type].push({
        action: f,
        type: type,
        target: this
    })
}
ObservedObject.prototype.dispatchEvent = function(type){
    for(var e = 0; e < this.events[type].length; ++e){
        this.events[type][e].action(this.events[type][e])
    }
}
ObservedObject.prototype.removeEventListener = function(type, f){
    if(this.events[type]) {
        for(var e = 0; e < this.events[type].length; ++e){
            if(this.events[type][e].action == f)
                this.events[type].splice(e, 1)
        }
    }
}

var myObj = new ObservedObject()

myObj.addEventListener('myEvent', function(e){// your code...})

这是DOM事件API的简化版本,运行良好!这里有一个更完整的示例


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