2010-07-28 59 views
3

首先,我知道我可以在實例上覆制「this」,但在這裏不起作用。「this」在將函數綁定到Javascript類中的事件時不起作用

基本上我正在寫些東西來跟蹤與Youtube視頻互動的人。

我一次只有一個視頻可以正常工作。但我希望它能夠在包含多個Youtube視頻的頁面上工作,因此我將代碼轉換爲一個類,以便爲頁面上的每個視頻創建一個新的實例。

問題是當試圖綁定到Youtube事件偵聽器的狀態更改。對於「無級」的代碼,它看起來像這樣:

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

(onPlayerStateChange是我寫來跟蹤視頻的狀態變化的功能)

(我也知道的addEventListener不會與MSIE合作,但我並不擔心這一點)

但是當我在一個類內部時,我必須使用「this」來引用該類中的另一個函數。代碼如下:

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

當它這樣寫,this.onPlayerStateChange永遠不會被調用。我試圖將「this」複製到另一個變量中,例如「我」,但這也行不通。 onPlayerStateChange函數在「this」範圍內定義:

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

任何見解?

在這裏看看其他類似的問題,他們都使用jQuery,我認爲這樣做可能會工作,如果我這樣做。但我不想使用jQuery,因爲這將會部署在隨機的第三方網站上。我喜歡jQuery,但我不希望它成爲使用它的必要條件。

+0

假設'this'實際上指向你認爲當'var me = this;'執行時,你能不能把this.onPlayerStateChange分配給一個變量,然後將_that_傳遞給'使用addEventListener()'?例如:'var callback = this.onPlayerStateChange; //稍後this.addEventListener(「onStateChange」,回調);' – 2010-07-28 23:31:26

+0

我也試過了。我忘記在我的文章中加入這個內容,我將其添加進來,但收到答案後您無法編輯帖子:\ – Sean 2010-07-28 23:57:14

回答

0

好吧,我得到了這一切工作。這是一個醜陋的黑客,但它的作品。基本上,我將類的每個新實例都存儲在一個數組中,並且將數組鍵(1,2等)傳遞到類中,因此它可以在幾個關鍵位置根據需要在外部引用它自己。

我需要該類在外部引用自己的位置是我傳遞給addEventListener的字符串,並且在一些setTimeout函數中,「this」顯然丟失了它的上下文(就我而言,無論如何,因爲唯一的這樣我可以他們的工作是不斷變化的「本」使用外部引用來代替。

下面是完整的代碼。

在已Youtube視頻的頁面,他們正在使用SWFObject注入該_ytmeta對象存儲的標題爲每個視頻都是可選的,但是它是登錄視頻標題的唯一方式,因爲Youtube的API不會提供給你,這意味着你必須事先知道標題,但是點i 。只不過,如果你想要的標題在我們的報告中顯示,你必須創建該對象:

<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> 

所以我們包括SWFObject的JavaScript代碼,還有youtube.js文件,該文件託管在我們的服務器上,幷包含在您要跟蹤視頻的頁面上。

這裏是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 || ''; 
    } 
} 

希望有人將來會有所幫助,因爲它是在屁股疼痛嚴重得到它的工作!

謝謝大家誰發佈了他們的想法在這裏。 :)

+0

你可能想結賬這個[包裝庫](http://github.com/AnuragMishra/YoutubePlayer)我昨天寫的基於詹姆斯的想法鏈接在我的答案。它將全局範圍最小化爲​​一個自包含的對象 - 「YoutubePlayer」。 – Anurag 2010-07-29 22:22:59

2

您應該使用下列附加的事件:

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

addEventListener,你並不需要添加 on前綴。

我張貼以上是正確的標準的javascript,但由於這種把它傳遞給YT Flash對象,它預計onStateChange這是正確的。

HTH

編輯:嘗試在this post的方法來幫助。

+0

但是,爲什麼它在非課程代碼中工作?據我所知,onStateChange不是「標準」Javascript,它是Youtube爲他們的API創建的。因此你必須引用全名。 – Sean 2010-07-28 23:30:02

+1

@Sean - 你能發佈更多圍繞此調用的代碼嗎? – TheCloudlessSky 2010-07-29 00:55:29

+0

今天晚上我已經完成了,但如果我明天從你的所有建議中得不到它,我會發布完整的代碼供人們分析。謝謝你的幫助! – Sean 2010-07-29 03:19:25

1

TheCloudlessSky部分是正確的,肖恩是部分正確的。您可以繼續使用「onStateChange」作爲事件名稱,但不要將this.onPlayerStateChange放在引號中 - 這樣做會刪除this的特殊含義,javascript將查找名爲「this.onPlayerStateChange」的函數,而不是查找函數「 onPlayerStateChange「在this對象內。

this.o.addEventListener("onStateChange", this.onPlayerStateChange); 
+0

我也試過。它不起作用,事實上,我不能得到任何函數作爲第二個參數,除非它在引號中,我知道它不是通常的工作方式。這導致我認爲這是Youtube的API ... argh。 我想出了一個嚴重的解決方案,目前正在部分工作。如果我充分發揮作用,我會在這裏發佈代碼供其他人查看。 – Sean 2010-07-29 00:33:56

3

您需要一種全局方式來訪問您的對象的onPlayerStateChange方法。當您將me指定爲var me = this;時,變量me僅在其創建的對象方法內有效。然而,Youtube播放器API需要一個全局可訪問的函數,因爲實際的調用來自Flash,並且沒有直接引用您的JavaScript函數。

James Coglan發現了一個非常有幫助的blog post,其中他討論了一種與Youtube的JavaScript API進行通信並管理多個視頻事件的好方法。

我發佈了一個JavaScript包裝庫,使用他的想法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播放器evals此字符串,它應該返回的功能。該函數是將最終接收回放事件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); 
    }; 
}; 

當flash播放器加載視頻時,會調用全局函數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.

+0

謝謝,這很有幫助。這只是一個處理全局回調函數的痛苦,因爲我的所有變量都是類實例的局部變量。基於這裏的另一個評論,看起來這是我必須採取的路線。 – Sean 2010-07-29 03:35:45

1

看YouTube API取得後,它看起來像的addEventListener只接受該事件處理函數的字符串。這意味着沒有乾淨的方法來爲每個對象註冊唯一的事件處理程序。

另一種方法是註冊所有YouTube狀態變化的全球處理器,然後讓該處理器通過狀態變化到所有對象。假設你有「跟蹤器」對象的數組:

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

每個跟蹤對象然後可通過本身弄清楚的狀態變化是否實際發生(使用API​​的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"); 
} 
+0

嗯......是的,那可能會奏效。這一整天摔跤後,我的大腦非常疲憊。明天我會試試這個,併發回結果。謝謝! – Sean 2010-07-29 03:18:01

3

您可以使用一個名爲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); 
     }; 
    }; 

你可以用它來討好其他功能和維護功能,裏面的「這個」範圍。 看看這篇文章對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 
    } 
} 

您創建一個全球性的水平MyCustomClass的一個實例

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"); 
+0

第二個參數必須是一個字符串,並且在Flash Player評估時可以全局訪問。 Currying絕對是解決這個問題的好方法,你可以很容易地將它應用到字符串中。 – Anurag 2010-07-29 05:35:55

+0

是的,你是對的。我更新了我的答案以使用柯里和字符串 – naikus 2010-07-29 06:24:49

+1

柯里格是一個有趣的想法,謝謝你。但是,它不起作用,因爲您創建的「var」對於類的實例是本地的。傳遞給Youtube的addEventListener的函數必須在全局級別可用。我開始做的是讓每個實例成爲數組中的一個項目,並將其數組鍵傳遞給該類,以便我可以將外部函數_yta [yto] .onPlayerStateChange傳遞給addEventListener。 _yta是我的實例數組,yto是傳入實例本身的數組鍵,所以它可以在外部引用它自己。尚未完成,但進展順利。 – Sean 2010-07-29 16:57:14

相關問題