【问题标题】:Is it possible to listen for changes to an object's attributes in JavaScript?是否可以在 JavaScript 中监听对象属性的变化?
【发布时间】:2008-10-27 16:49:47
【问题描述】:

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

在许多表单字段上设置事件侦听器开始减慢速度,并且为每次更改刷新整个表单对用户来说太重/太慢了。

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

更多信息:

  • 这是一个 jQuery 插件,因此我可以从该库构建的任何功能都会有所帮助,但不是必需的。
  • 我们的用户使用的是 IE6/7、Safari 和 FF2/3,所以如果可能但仅限于“现代”浏览器,我将不得不寻找不同的解决方案。

【问题讨论】:

    标签: javascript dom-events


    【解决方案1】:

    据我所知,对象属性更改不会触发任何事件(编辑:显然,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 的浏览器中:观察textareas 上的keyuppaste 事件(由于某种原因,它们不会在textareas 上触发input)。
    3. IE:form 上观察keyuppaste
    4. form 上观察change(这会在selects 上触发)。
    5. 对于keyuppaste 事件,通过将文本字段的value 与其defaultValue 进行比较,将字段的当前值与其默认值(加载页面时的值)进行比较

    编辑:这是我为防止未经修改的表单提交等而开发的示例代码:

    What is the best way to track changes in a form via javascript?

    【讨论】:

    • Object.observe() 已被弃用。为了你自己,尽量避免使用它。
    【解决方案2】:

    感谢 cmets 伙计们。我已经完成了以下操作:

    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');
    

    加入您的 cmets,我将在表单对象上使用事件委托来更新注册表并触发注册的观察方法。

    到目前为止,这对我来说效果很好......你们能看出这有什么问题吗?

    【讨论】:

      【解决方案3】:

      您可以将侦听器附加到容器(主体或表单),然后使用事件参数对更改做出反应。你得到了所有监听器的好处,但只需要为容器附加一个,而不是为每个元素附加一个。

      $('body').change(function(event){
          /* do whatever you want with event.target here */
          console.debug(event.target); /* assuming firebug */
       });
      

      event.target 包含被点击的元素。

      SitePoint 在这里对事件委托有一个很好的解释:

      JavaScript 事件委托是一种简单的技术,通过它您可以将单个事件处理程序添加到父元素,以避免必须将事件处理程序添加到多个子元素。

      【讨论】:

        【解决方案4】:

        Mozilla 引擎浏览器支持 Object.watch,但我不知道是否有跨浏览器兼容的等效项。

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

        【讨论】:

          【解决方案5】:

          对上一个答案的小修改:通过将 observable 代码移动到一个对象,可以对其进行抽象,并使用它通过 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;
          }
          

          见小提琴:https://jsfiddle.net/v2mcwpw7/3/

          【讨论】:

          • 有人可能会争辩说,我们可以通过 $.extend(true,this,ObservableProperties);ObservableProperties 对象直接绑定到业务对象,它看起来确实更好。但是,请注意范围。当我做的事情有点复杂时,我使用selfvariable(或单例),否则如果一切都是公开的,我会使用this。见this fiddle
          【解决方案6】:

          jQuery 真是太棒了。虽然你可以看看 ASP.NET AJAX Preview。

          有些功能只是 .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 完全兼容($ 没有问题)

          链接:Home pageVersion I currently use

          【讨论】:

            【解决方案7】:

            我正在寻找同样的东西并遇到了你的问题......没有一个答案能满足我的需求,所以我想出了这个我想分享的解决方案:

            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 的简化版,运行良好! 这里有一个更完整的example

            【讨论】:

              猜你喜欢
              • 2017-05-16
              • 2017-11-23
              • 2011-10-05
              • 2015-02-12
              • 2023-02-08
              • 1970-01-01
              • 1970-01-01
              • 2011-06-13
              相关资源
              最近更新 更多