代码之家  ›  专栏  ›  技术社区  ›  Sean

将函数绑定到Javascript类中的事件时,“this”不起作用

  •  3
  • Sean  · 技术社区  · 16 年前

    首先,我知道我可以在实例化时复制“this”,但这在这里不起作用。

    基本上我在写一些东西来追踪人们与Youtube视频的互动。

    我一次只能拍一个视频。但我希望它也能在包含多个Youtube视频的页面上工作,因此我将代码转换为一个类,以便为页面上的每个视频创建一个新的实例。

    问题是当试图绑定到Youtube事件侦听器以进行状态更改时。对于“非类”代码,如下所示:

    var o = document.getElementById( id );
    o.addEventListener("onStateChange", "onPlayerStateChange" );
    

    (onPlayerStateChange是我用来跟踪视频中状态变化的函数)

    (我也知道addEventListener不能与MSIE一起工作,但我还不担心这个问题)

    this.o = document.getElementById( id );
    this.o.addEventListener("onStateChange", "this.onPlayerStateChange" );
    

    当它这样写的时候,这个.onPlayerStateChange永远不会被调用。我试过将“this”复制到另一个变量中,例如“me”,但这也不起作用。在此之前,onPlayerStateChange函数是在“this”范围内定义的:

    var me = this;
    this.o = document.getElementById( id );
    this.o.addEventListener("onStateChange", "me.onPlayerStateChange" );
    

    看看这里的其他类似问题,他们都在使用jQuery,我想如果我这样做的话,用这种方法可能会管用。但我不想使用jQuery,因为它将部署在随机的第三方站点上。我喜欢jQuery,但我不希望它成为使用它的必要条件。

    6 回复  |  直到 16 年前
        1
  •  3
  •   Anurag    16 年前

    你需要一个全球性的方式来访问 onPlayerStateChange 对象的方法。分配时 me var me = this; ,变量 仅在创建它的对象方法内部有效。但是,Youtube播放器API需要一个全局可访问的函数,因为实际调用来自Flash,它没有直接引用JavaScript函数。

    blog post 作者:jamescoglan,其中他讨论了一种与Youtube的JavaScript API通信并管理多个视频事件的好方法。

    http://github.com/AnuragMishra/YoutubePlayer

    function Player(id) {
        // id of the placeholder div that gets replaced
        // the <object> element in which the flash video resides will
        // replace the placeholder div and take over its id
        this.id = id;
    
        Player.instances.push(this);
    }
    
    Player.instances = [];
    

    将字符串作为回调传递时,请使用以下格式的字符串:

    "Player.dispatchEvent('playerId')"
    

    当flash播放器评估这个字符串时,它应该返回一个函数。该函数是最终接收回放事件id的回调。

    Player.dispatchEvent = function(id) {
        var player = ..; // search player object using id in "instances"
        return function(eventId) { // this is the callback that Flash talks to
            player.notify(eventId);
        };
    };
    

    onYoutubePlayerReady 函数被调用。在该方法中,设置用于侦听播放事件的事件处理程序。

    function onYouTubePlayerReady(id) {
        var player = ..; // find player in "instances"
    
        // replace <id> with player.id
        var callback = "YoutubePlayer.dispatchEvent({id})";
        callback = callback.replace("{id}", player.id);
    
        player.addEventListener('onStateChange', callback);
    }
    

    working example here.

        2
  •  3
  •   naikus    16 年前

    currying 为了达到这个目的。为此,你需要一个currying函数。这是我很久以前写的一封信

         /**
          * Changes the scope of function "fn" to the "scope" parameter specified or
          * if not, defaults to window scope. The scope of the function determines what
          * "this" inside "fn" evaluates to, inside the function "fn". Any additional arguments
          * specified in this are passed to the underlying "curried" function. If the underlying
          * function is already passed some arguments, the optional arguments are appended
          * to the argument array of the underlying function. To explain this lets take
          * the example below:
          *
          * You can pass any number of arguments that are passed to the underlying (curried)
          * function
          * @param {Function} fn The function to curry
          * @param {Object} scope The scope to be set inside the curried function, if
          * not specified, defaults to window
          * @param arguments {...} Any other optional arguments ot be passed to the curried function
          *
          */
         var curry = function(fn, scope /*, arguments */) {
            scope = scope || window;
            var actualArgs = arguments;
    
            return function() {
               var args = [];
               for(var j = 0; j < arguments.length; j++) {
                  args.push(arguments[j]);
               }
    
               for(var i = 2; i < actualArgs.length; i++) {
                  args.push(actualArgs[i]);
               }
    
               return fn.apply(scope, args);
            };
         };
    

    您可以使用它来处理其他函数,并在函数中维护“this”范围。 请在上查看这篇文章 currying

         this.o.addEventListener("onStateChange", curry(onPlayerStateChange, this));
    

    var curriedFunc = curry(onPlayerStateChange, this);
    this.o.addEventListener("onStateChange", "curriedFunc");
    

    编辑: 好吧,假设这是您创建的自定义类:

    function MyCustomClass() {
       var privateVar = "x"; // some variables;
       this.onPlayerStateChange = function() {  //instance method on your custom class
           // do something important
       }
    }
    

       var myCustom = new MyCustomClass(); // create a new instance of your custom class
       var curriedFunc = curry(myCustom.onplayerStageChange, myCustom); // curry its onplayerstateChange
       // now add it to your event handler
       o.addEventListener("onStateChange", "curriedFunc");
    
        3
  •  2
  •   Community Mohan Dere    9 年前

    您应该使用以下方法附加事件:

    this.o.addEventListener("statechange", this.onPlayerStateChange);
    

    为了 addEventListener on 前缀。

    标准 onStateChange 这是正确的。

    this post 来帮忙。

        4
  •  1
  •   David Tang    16 年前

    克劳德斯基部分正确,肖恩部分正确。您可以继续使用“onStateChange”作为事件名称,但不要将 this.onPlayerStateChange 在引语中,这样做会消除 this javascript将查找名为“this.onPlayerStateChange”的函数,而不是在其中查找“onPlayerStateChange”函数 对象。

    this.o.addEventListener("onStateChange", this.onPlayerStateChange);
    
        5
  •  1
  •   David Tang    16 年前

    在查看了youtubeapi之后,addEventListener似乎只接受事件处理程序函数的字符串。这意味着没有干净的方法为每个对象注册唯一的事件处理程序。

    另一种方法是为所有youtube状态更改注册一个全局处理程序,然后让该处理程序将状态更改传递给所有对象。假设您有一个“跟踪器”对象数组:

    function globalOnPlayerStateChange() {
        for (tracker in myTrackerObjects) {
            tracker.playerStateChange();
        }
    }
    

    getPlayerState 功能):

    function MyYoutubeTracker() {
        this.currentState = ...
    
        // Determine if state changed happened or not
        this.playerStateChange = function() {
            var newState = this.o.getPlayerState();
            if (newState != this.currentState) {
                // State has changed
                this.currentState = newState;
            }
        }
    
        // Register global event handler for this youtube object
        this.o.addEventListener("onStateChange", "globalOnPlayerStateChange");
    }
    
        6
  •  0
  •   Sean    16 年前

    在有Youtube视频的页面上,它们是使用swfobject注入的。\uytmeta对象存储每个视频的标题。这是可选的,但这是记录视频标题的唯一方法,因为Youtube的API没有提供给您。这意味着您必须预先知道标题,但要点很简单,如果您希望标题显示在我们的报告中,则必须创建以下对象:

    <div id='yt1'></div>
    
    <script src='youtube.js'></script>
    <script src='swfobject.js'></script>
    <script>
    var _ytmeta = {}
    _ytmeta.yt1 = { 'title': 'Moonwalking in Walmart' };
    
    var params = { allowScriptAccess: "always" };
    swfobject.embedSWF("http://www.youtube.com/v/gE1ZvCnwkYk?enablejsapi=1&playerapiid=yt1", "yt1", "425", "356", "8", null, null, params );
    </script>
    

    以下是youtube.js的内容:

    // we're storing each youtube object (video) in an array, and passing the array key into the class, so the class instance can refer to itself externally
    // this is necessary for two reasons
    // first, the event listener function we pass to Youtube has to be globally accessible, so passing "this.blah" doesn't work
    // it has to be passed as a string also, so putting "this" in quotes makes it lose its special meaning
    // second, when we create timeout functions, the meaning of "this" inside that function loses its scope, so we have to refer to the class externally from there too.
    
    // _yt is the global youtube array that stores each youtube object. yti is the array key, incremented automatically for each new object created
    var _yt = [], _yti = 0;
    
    // this is the function the youtube player calls once it's loaded. 
    // each time it's called, it creates a new object in the global array, and passes the array key into the class so the class can refer to itself externally
    function onYouTubePlayerReady( id ) {
      _yti++;
      _yt[ _yti ] = new _yta( id, _yti );
    }
    
    function _yta( id, i ) {
    
      if( !id || !i ) return;
    
      this.id = id;
      this.mytime;
      this.scrubTimer;
      this.startTimer;
      this.last = 'none';
      this.scrubbing = false;
    
      this.o = document.getElementById( this.id );
      this.o.addEventListener("onStateChange", "_yt["+i+"].onPlayerStateChange" );
    
      this.onPlayerStateChange = function( newState ) {
    
        // some events rely on a timer to determine what action was performed, we clear it on every state change.
        if( this.myTime != undefined ) clearTimeout( this.myTime );
    
        // pause - happens when clicking pause, or seeking
        // that's why a timeout is used, so if we're seeking, once it starts playing again, we log it as a seek and kill the timer that would have logged the pause
        // we're only giving it 2 seconds to start playing again though. that should be enough for most users.
        // if we happen to log a pause during the seek - so be it.
        if( newState == '2' ) {
          this.myTime = setTimeout( function() {
            _yt[i].videoLog('pause');
            _yt[i].last = 'pause';
            _yt[i].scrubbing = false;
            }, 2000 );
          if( this.scrubbing == false ){
            this.last = 'pre-scrub';
            this.scrubbing = true;
          }
        }
    
        // play
        else if( newState == '1' ) {
    
          switch( this.last ) {
    
            case 'none':
              this.killTimers();
              this.startTimer = setInterval( this.startRun, 200 );
              break;
    
            case 'pause':
              this.myTime = setTimeout( function() {
                _yt[i].videoLog('play');
                _yt[i].last = 'play';
              }, 2000 );
              break;
    
            case 'pre-scrub':
              this.killTimers();
              this.scrubTimer = setInterval( this.scrubRun, 200 );
              break;
          }
        }
    
        // end
        else if( newState == '0' ) {
          this.last = 'none';
          this.videoLog('end');
        }
      }
    
    
      // have to use external calls here because these are set as timeouts, which makes "this" change context (apparently)
      this.scrubRun = function() {
        _yt[i].videoLog('seek');
        _yt[i].killTimers();
        _yt[i].last = 'scrub';
        _yt[i].scrubbing = false;
      }
      this.startRun = function() {
        _yt[i].videoLog('play');
        _yt[i].killTimers();
        _yt[i].last = 'start';
      }
    
      this.killTimers = function() {
        if( this.startTimer ) {
          clearInterval( this.startTimer );
          this.startTimer = null;
        }
        if( this.scrubTimer ){
          clearInterval( this.scrubTimer );
          this.scrubTimer = null;
        }
      }
    
      this.videoLog = function( action ) {
        clicky.video( action, this.videoTime(), this.videoURL(), this.videoTitle());
      }
    
      this.videoTime = function() {
        return Math.round( this.o.getCurrentTime() );
      }
    
      this.videoURL = function() {
        return this.o.getVideoUrl().split('&')[0]; // remove any extra parameters - we just want the first one, which is the video ID.
      }
    
      this.videoTitle = function() {
        // titles have to be defined in an external object
        if( window['_ytmeta'] ) return window['_ytmeta'][ this.id ].title || '';
      }
    }
    

    希望将来有人会发现这一点很有帮助,因为让它工作是件很痛苦的事!

    推荐文章