2010-08-10 30 views
1

我想寫一些JS複製jQuery的fadeIn和fadeOut函數。下面的代碼我到目前爲止:我在與​​第一setTimeout調用煩惱Javascript:如何將不同的對象傳遞給循環中創建的setTimeout處理程序?

function fadeIn(elem, d, callback) 
{ 

    var duration = d || 1000; 
    var steps = Math.floor(duration/50); 
    setOpacity(elem,0); 
    elem.style.display = ''; 
    for (var i = 1; i <= steps; i++) 
    { 
     console.log(i/steps + ', ' + (i/steps) * duration); 
     setTimeout('setOpacity("elem", '+(i/steps)+')', (i/steps) * duration); 
    } 
    if (callback) 
     setTimeout(callback,d); 
} 
function setOpacity(elem, level) 
{ 
    console.log(elem); 
    return; 
    elem.style.opacity = level; 
    elem.style.MozOpacity = level; 
    elem.style.KhtmlOpacity = level; 
    elem.style.filter = "alpha(opacity=" + (level * 100) + ");"; 
} 

- 我需要傳遞的對象「ELEM」(這是一個DOM元素)的功能setOpacity。傳遞'level'變量很好......但是,我收到了「elem沒有定義」的錯誤。我認爲這是因爲在任何setOpacity調用實際運行時,最初的fadeIn函數已經完成,因此變量elem不再存在。

爲了減輕這個問題,我試圖另一種方法:

setTimeout(function() { setOpacity(elem, (i/steps));}, (i/steps) * duration); 

麻煩現在是,當調用該函數時,(I /步)現在是總是1.05,而不是從0遞增到1

如何在正確提高不透明度級別的同時將有問題的對象傳遞給setOpacity?

+0

創建許多一個定時器,是不是效率的最佳實踐。如果值不是100%,您可能想重新考慮使用1超時並重新初始化它。 – epascarello 2010-08-10 03:43:55

回答

3

您的第一種方法是在運行時評估代碼。你很可能是正確的,它爲什麼失敗(elem不在代碼被評估的範圍內)。使用任何形式的eval()(和setTimeout(string, ...)eval()的一種形式)在Javascript中是一個不好的想法,創建一個函數要比第二種方法好得多。

要理解您的第二種方法失敗的原因,您需要了解範圍並明確關閉。創建該函數時,它會從fadeIn函數的作用域中獲取對i變量的引用。

當您稍後運行該功能時,它會使用該參考號從fadeIn的範圍引用回i。然而,當這種情況發生時,循環結束,所以你永遠只會得到i這個循環結束時的任何事情。

你應該做的是重新設計它,以便不是一次創建多個setTimeout(這是低效的),而是告訴你的setTimeout回調函數設置下一個超時(或者你可以使用setInterval)並且執行遞增如果你的值在回調函數中。

+1

+1用於設置超時回調中的超時。可以通過使用像'for(var i = 0; ....){var t = i; setTimeout(t/*這將始終採用當前循環索引,而不是最後一個*/...' – TheVillageIdiot 2010-08-10 06:21:22

9

你的「另一種方法」是正確的,這是通常的做法。

而對於i始終是一個常數的問題,這就是閉包的工作原理! 你看,當你創建這個函數,做一些與i(如function() { alert(i); }),該函數,正如他們所說,「捕獲」,或「結合」變量i,使變量i沒有後死亡該循環已完成,但繼續保持活躍狀態​​,並仍從該功能引用。

爲了證明這個概念,考慮下面的代碼:

var i = 5; 
var fn = function() { alert(i); }; 

fn(); // displays "5" 

i = 6; 
fn(); // displays "6" 

當它是用這種方式,這個概念變得有點更加明顯,不是嗎?由於您要更改循環中的變量,因此循環完成後,變量將保留最後一個值(1+steps) - 而這正是您的函數在開始執行時所看到的。

要解決這個問題,你必須創建另一個函數來返回一個函數。是的,我知道,有點令人興奮,但忍耐着我。考慮我的例子中的修訂版:

function createFn(theArgument) 
{ 
    return function() { alert(theArgument); }; 
} 

var i = 5; 
var fn = createFn(i); 

fn(); // displays "5" 

i = 6; 
fn(); // still displays "5". Voila! 

這工作,因爲fn功能不再結合變量i。相反,現在它綁定另一個變量 - theArgument,它與i無關,除了在調用createFn時具有相同的值。現在你可以改變你的所有想要的i - theArgument將無敵。

將此應用於你的代碼,這裏是你應該如何修改它:

function createTimeoutHandler(elemArg, iDivStepsArg) 
{ 
    return function() { setOpacity(elemArg, iDivStepsArg); }; 
} 

for (var i = 1; i <= steps; i++) 
{ 
    console.log(i/steps + ', ' + (i/steps) * duration); 
    setTimeout(createTimeoutHandler(elem, i/steps), (i/steps) * duration); 
} 
+0

感謝您的建議!就像thomasrutter所說的,這是否比他說的方式效率低? – Mala 2010-08-10 04:00:31

+0

至於創建多個setTimeouts,我完全同意:在每次迭代後重新初始化會更有效率。 – 2010-08-10 04:04:58

+0

好的,謝謝。+1,因爲你的解決方案工作,但我會接受thomasrutter的解決方案,因爲我會使用它:) – Mala 2010-08-10 04:09:51

相關問題