2010-01-08 81 views
83

我剛開始使用原型JavaScript,並且無法在範圍更改時從原型函數內部保留對主對象的引用this 。讓我來說明我的意思(我使用jQuery在這裏):在JavaScript原型函數中保留對「this」的引用

MyClass = function() { 
    this.element = $('#element'); 
    this.myValue = 'something'; 

    // some more code 
} 

MyClass.prototype.myfunc = function() { 
    // at this point, "this" refers to the instance of MyClass 

    this.element.click(function() { 
    // at this point, "this" refers to the DOM element 
    // but what if I want to access the original "this.myValue"? 
    }); 
} 

new MyClass(); 

我知道我可以通過在myfunc開始做這個保存到主對象的引用:

var myThis = this; 

然後使用myThis.myValue訪問主對象的屬性。但是當我在MyClass上有大量的原型函數時會發生什麼?我必須在每個開頭保存對this的引用嗎?似乎應該有一個更清潔的方式。並且怎麼樣這樣的情況:

MyClass = function() { 
    this.elements $('.elements'); 
    this.myValue = 'something'; 

    this.elements.each(this.doSomething); 
} 

MyClass.prototype.doSomething = function() { 
    // operate on the element 
} 

new MyClass(); 

在這種情況下,我不能創建與var myThis = this;主要對象的引用,因爲thisdoSomething範圍內,即使原來的值是jQuery對象,而不是一個MyClass對象。

有人建議我使用全局變量來保存對原始this的引用,但對我來說這似乎是一個非常糟糕的主意。我不想污染全局命名空間,這似乎會阻止我實例化兩個不同的對象,而不會相互干擾。

有什麼建議嗎?有沒有乾淨的方式來做我以後的事情?或者是我的整個設計模式有缺陷?

回答

60

對於維護上下文中設置的範圍內,bind方法是非常有用的,它現在最近發佈的ECMAScript 5th Edition規範的一部分,這一功能的實現很簡單(只有8行長):

// The .bind method from Prototype.js 
if (!Function.prototype.bind) { // check if native implementation available 
    Function.prototype.bind = function(){ 
    var fn = this, args = Array.prototype.slice.call(arguments), 
     object = args.shift(); 
    return function(){ 
     return fn.apply(object, 
     args.concat(Array.prototype.slice.call(arguments))); 
    }; 
    }; 
} 

而且你可以使用它,在你的例子是這樣的:

MyClass.prototype.myfunc = function() { 

    this.element.click((function() { 
    // ... 
    }).bind(this)); 
}; 

又如:

var obj = { 
    test: 'obj test', 
    fx: function() { 
    alert(this.test + '\n' + Array.prototype.slice.call(arguments).join()); 
    } 
}; 

var test = "Global test"; 
var fx1 = obj.fx; 
var fx2 = obj.fx.bind(obj, 1, 2, 3); 

fx1(1,2); 
fx2(4, 5); 

在我們可以看到更多的bind行爲的第二個例子。

它基本上會生成一個新函數,它將負責調用我們的函數,保留定義爲bind的第一個參數的函數上下文(this值)。

其餘的參數只是傳遞給我們的函數。

注意在這個例子中,該功能fx1,調用沒有任何對象上下文obj.method()),只是作爲一個簡單的函數調用,在這種類型invokation中,this關鍵字內部將指全局對象,它將提醒「全球測試」。

現在,fx2bind方法生成的新函數,它會調用我們的函數來保存上下文並正確傳遞參數,它會提醒「obj測試1,2,3,4,5」,因爲我們調用它添加兩個額外的參數,它已經有綁定前三個。

+0

我真的很喜歡這個功能,但是在jQuery環境中,我傾向於在給定現有jQuery.bind方法的情況下命名它(即使沒有實際的命名衝突)。 –

+0

@Rob,創建一個與'Function.prototype.bind'相同的'jQuery.bind'方法是很簡單的,檢查這個簡單的實現:http://jsbin.com/aqavo/edit雖然我會考慮更改它的名字,它可能會導致與事件/綁定方法混淆... – CMS

+2

我強烈建議堅持使用名稱'Function.prototype.bind'。它現在是語言的標準化部分;它不會消失。 – bobince

1

由於您使用jQuery的,但值得注意的是this已經被jQuery的本身維護:

$("li").each(function(j,o){ 
    $("span", o).each(function(x,y){ 
    alert(o + " " + y); 
    }); 
}); 

在這個例子中,o代表li,而y代表孩子span。並與$.click(),您可以從event對象獲取範圍:

$("li").click(function(e){ 
    $("span", this).each(function(i,o){ 
    alert(e.target + " " + o); 
    }); 
}); 

其中e.target代表li,並o代表孩子span

10

對於你最後MyClass例如,你可以這樣做:

var myThis=this; 
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); }); 

在傳遞給each功能,this指的是jQuery對象,因爲你已經知道了。如果在該函數內部,則從myThis獲得doSomething函數,然後使用參數數組(請參閱apply functionarguments variable)在該函數上調用apply方法,然後this將在doSomething中設置爲myThis

+1

這是行不通的,到了this.doSomething的時候,'this'已經被jQuery取代了其中的一個元素。 –

+0

是的,我最初發布時遇到了兩個問題。我編輯它,現在它應該工作。 (抱歉...) – icktoofay

0

另一個解決方案(和我最喜歡jQuery的方式)是使用jQuery提供的'e.data'來傳遞'this'。然後,你可以這樣做:

this.element.bind('click', this, function(e) { 
    e.data.myValue; //e.data now references the 'this' that you want 
}); 
0

您可以創建到該對象的引用,或者您可以使用with (this)方法。當你使用事件處理程序並且你無法傳入參考時,後者非常有用。

MyClass = function() { 
    // More code here ... 
} 

MyClass.prototype.myfunc = function() {  
    // Create a reference 
    var obj = this; 
    this.element.click(function() { 
     // "obj" refers to the original class instance    
     with (this){ 
      // "this" now also refers to the original class instance 
     } 
    }); 

} 
+4

由於含糊不清和其他問題,避免使用'with'語句。 –

+0

當然,如果你可以避免使用它,那麼通過一切方法使用一個更簡單的方法,但是當所有其他方法都失敗時它仍然有效且有用。 –

+0

-1:'with'不會改變'this'的值 – Bergi

7

我意識到這是一個古老的線程,但我有一個解決方案,更優雅,而且除了一個事實,即它不是一般做,因爲我注意到有一些缺點。

考慮以下幾點:

var f=function(){ 
    var context=this; 
}  

f.prototype.test=function(){ 
    return context; 
}  

var fn=new f(); 
fn.test(); 
// should return undefined because the prototype definition 
// took place outside the scope where 'context' is available 

在功能上面我們定義一個局部變量(上下文)。然後我們添加了一個返回局部變量的原型函數(test)。正如你可能預測到的,當我們創建這個函數的一個實例,然後執行測試方法時,它不會返回局部變量,因爲當我們將原型函數定義爲主函數的成員時,它不在局部變量被定義。 這是創建函數然後添加原型的常見問題 - 您無法訪問在主函數範圍內創建的任何內容。

要創建的局部變量的範圍內的方法,我們需要直接將它們定義爲成員函數,擺脫原型參考:

var f=function(){ 
    var context=this;  

    this.test=function(){ 
     console.log(context); 
     return context; 
    }; 
}  

var fn=new(f); 
fn.test(); 
//should return an object that correctly references 'this' 
//in the context of that function;  

fn.test().test().test(); 
//proving that 'this' is the correct reference; 

你可能會擔心,因爲方法不是通過原型創建的,不同的實例可能並不是真正的數據分離。爲了證明它們是這樣的,請考慮:

var f=function(val){ 
    var self=this; 
    this.chain=function(){ 
     return self; 
    };  

    this.checkval=function(){ 
     return val; 
    }; 
}  

var fn1=new f('first value'); 
var fn2=new f('second value');  

fn1.checkval(); 
fn1.chain().chain().checkval(); 
// returns 'first value' indicating that not only does the initiated value remain untouched, 
// one can use the internally stored context reference rigorously without losing sight of local variables.  

fn2.checkval(); 
fn2.chain().chain().checkval(); 
// the fact that this set of tests returns 'second value' 
// proves that they are really referencing separate instances 

另一種使用此方法的方法是創建單例。多數情況下,我們的javascript函數沒有被多次實例化。如果你知道你永遠不需要同一個函數的第二個實例,那麼有一個簡寫的方法來創建它們。被警告,但是:皮棉會抱怨說,這是一個奇怪的建築,並質疑你的關鍵字「新」的使用:

fn=new function(val){ 
    var self=this; 
    this.chain=function(){ 
     return self; 
    };   

    this.checkval=function(){ 
     return val; 
    }; 
}  

fn.checkval(); 
fn.chain().chain().checkval(); 

Pro的: 使用這種方法來創建函數對象的好處是豐富。

  • 它使您的代碼更易於閱讀,因爲它以一種使視覺更易於遵循的方式縮進函數對象的方法。
  • 它允許訪問本地定義的變量只有在最初以這種方式定義方法即使您以後添加原型的功能,甚至成員函數的函數對象,它不能訪問的局部變量和任何功能或您在該級別存儲的數據仍然安全且無法從其他地方訪問。
  • 它允許一個簡單而直接的方式來定義單身人士。
  • 它允許您存儲對「this」的引用並無限期地維護該引用。

反對的: 有一些缺點,使用這種方法。我不假裝是全面:)

  • 因爲方法被定義爲成員的對象,而不是原型 - 繼承可以使用成員的定義,但不是典型的定義來實現。 這實際上是不正確的。通過採取行動f.constructor.prototype可以實現同樣的原型繼承。
+0

這是一個很好的方法,但在某些情況下可能會出現另一個更微妙的問題。當你用你的構造函數返回一個方法時,'new'運算符不再返回原型鏈。也就是說,這不是被隱藏或覆蓋的問題 - 它不在那裏。任何你在原型鏈上擁有的成員 - 比如來自超類的成員都不在了。 – dhimes

+0

@dhimes - 實際上你不能訪問原型鏈的唯一原因是你不再有權訪問構造函數。除非我們通過' .constructor'屬性訪問它。測試這個證明:'a = new function(){}; a.constructor.prototype.b = function(){console.log('in .b');}; a.b();' – Nemesarial

相關問題