“This” anahtar kelimesi bir işlev içinde nasıl çalışır?


248

JavaScript'te ilginç bir durumla karşılaştım. Nesne değişmez gösterim kullanarak birkaç nesne tanımlayan bir yöntemle bir sınıf var. Bu nesnelerin içinde thisişaretçi kullanılıyor. Programın davranışından, thisişaretçinin değişmez tarafından oluşturulan nesneye değil, yöntemin çağrıldığı sınıfa başvurduğuna karar verdim .

Çalışmasını beklediğim gibi olsa da, bu keyfi görünüyor. Bu tanımlanmış davranış mı? Çapraz tarayıcı güvenli mi? "Spesifikasyon böyle söylüyor" un ötesinde olmasının nedeninin altında yatan herhangi bir neden var mı (örneğin, daha geniş bir tasarım kararı / felsefesinin bir sonucu mu?) Ayrıştırılmış kod örneği:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}

Ben de bunu yaptığımda olur var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons


bu gönderiye göz atın . Bu anahtar kelimenin çeşitli kullanım ve davranışları hakkında bazı iyi açıklamalara sahiptir.
Aşk Hasija

Yanıtlar:


558

Başka bir gönderimden yamyam oldum, işte bu konuda bilmek istediğinizden daha fazlası .

Başlamadan önce, Javascript hakkında akılda tutulması ve mantıklı olmadığında tekrarlamanız gereken en önemli şey. JavaScript (ES6 sınıfları yok classolan sözdizimsel şeker ). Bir şey sınıfa benziyorsa, bu akıllıca bir numaradır. Javascript'te nesneler ve işlevler var . (bu% 100 doğru değildir, fonksiyonlar sadece nesnelerdir, ancak bazen bunları ayrı şeyler olarak düşünmek yardımcı olabilir)

Bu değişken işlevler eklenmiştir. Eğer bir işlev çağırmak zaman, bu nasıl işleve çağırmak bağlı belirli bir değeri verilir. Buna genellikle çağrılma modeli denir.

Javascript'te işlevleri çağırmanın dört yolu vardır. İşlevi bir yöntem olarak , bir işlev olarak , bir yapıcı olarak ve uygula ile çağırabilirsiniz. .

Yöntem Olarak

Yöntem, bir nesneye bağlı bir işlevdir

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Bir yöntem olarak çağrıldığında, bu işlev / yöntemin bir parçası olduğu nesneye bağlanır. Bu örnekte, bu fooya bağlı olacaktır.

İşlev olarak

Tek başına bir işleviniz varsa, bu değişken "global" nesneye, neredeyse her zaman tarayıcı bağlamında pencere nesnesine bağlanır .

 var foo = function(){
    alert(this);
 }
 foo();

Sizi harekete geçiren bu olabilir , ama kendinizi kötü hissetmeyin. Birçok insan bunu kötü bir tasarım kararı olarak görüyor. Geri arama, bir yöntem olarak değil, bir işlev olarak çağrıldığından, bu nedenle neyin tutarsız bir davranış olduğunu görüyorsunuz.

Birçok insan, bu gibi bir şey yaparak problemin üstesinden gelir.

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Bir değişken tanımlamak o işaret eden bu . Kapatma (bir konu bütün kendi var) tutar o hala bir referansı vardır, bir geri arama çubuğu diyoruz eğer öyleyse, etrafta.

NOT: use strictİşlev olarak kullanılırsa, modda thisglobal'e bağlı değildir. (Öyle undefined).

Bir Yapıcı Olarak

Ayrıca bir işlevi yapıcı olarak da çağırabilirsiniz. Kullandığınız adlandırma kuralına (TestObject) dayanarak, bu da yaptığınız şey olabilir ve sizi harekete geçiren şeydir .

Bir işlevi yeni anahtar sözcükle Yapıcı olarak çağırırsınız.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Yapıcı olarak çağrıldığında, yeni bir Nesne oluşturulur ve bu , bu nesneye bağlanır. Yine, iç işlevleriniz varsa ve bunlar geri çağrı olarak kullanılıyorsa, bunları işlev olarak çağırırsınız ve bu genel nesneye bağlı olacaktır. Bu var = = bu hile / kalıbını kullanın.

Bazı insanlar yapıcı / yeni anahtar kelimenin sınıflara benzer bir şey yaratmanın bir yolu olarak Java / geleneksel OOP programcılarına atılan bir kemik olduğunu düşünür.

Uygula Yöntemi ile

Son olarak, her işlevin "uygula" adlı bir yöntemi vardır (evet, işlevler Javascript'teki nesnelerdir). Uygula Eğer değeri belirlemek sağlayan bu olacaktır ve ayrıca bağımsız değişkeni dizide girmesini sağlar. İşte işe yaramaz bir örnek.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

8
Not: Katı modda , işlev çağrıları için thisolacaktır undefined.
Miscreant

1
Bir işlev bildirimi, ör. işlev myfunction () {}, "yöntem" genel kapsam (pencere) olduğu "yöntem olarak" özel bir durumdur.
richard

1
@richard: Katı mod dışında ve thiskapsamla ilgisi yoktur. Küresel nesneyi kastediyorsunuz .
TJ Crowder

@ alan-storm. Durumunda "yapıcı olarak" dir this.confusing = 'hell yeah';aynı var confusing = 'hell yeah';? Yani her ikisi de izin verecek myObject.confusingmi? Sadece thisiç çalışma için özellikleri ve diğer değişkenleri oluşturmak için kullanabilmeniz iyi olmaz .
wunth

Ama sonra yine de işlerin fonksiyonun dışında yapılabileceğini ve değerin function Foo(thought){ this.confusing = thought; }var myObject = new Foo("hell yeah");
kurucuya aktarıldığını tahmin ediyorum

35

İşlev çağrıları

İşlevler yalnızca bir Nesne türüdür.

Tüm Function nesneleri, çağrıldıkları Function nesnesini yürüten çağrı ve uygulama yöntemlerine sahiptir.

Çağrıldığında, bu yöntemlerin ilk bağımsız değişkeni this, İşlevin yürütülmesi sırasında anahtar sözcük tarafından başvurulacak olan nesneyi belirtir - eğer , nullya undefinedda genel nesne window, için kullanılır this.

Böylece, bir Fonksiyon çağırmak ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... parantez ile - foo()- eşdeğerdir foo.call(undefined)ya foo.apply(undefined)olan etkili bir aynı foo.call(window)veya foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Ek bağımsız calldeğişkenler işlev çağrısına bağımsız değişken olarak iletilirken, tek bir ek bağımsız applydeğişken işlev çağrısının bağımsız değişkenlerini Array benzeri bir nesne olarak belirtebilir.

Böylece, veya foo(1, 2, 3)ile eşdeğerdir .foo.call(null, 1, 2, 3)foo.apply(null, [1, 2, 3])

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Bir işlev bir nesnenin özelliğiyse ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... işleve bir referansa nesne üzerinden erişmek ve onu parantez ile çağırmak - obj.foo()- foo.call(obj)veya ile eşdeğerdir foo.apply(obj).

Ancak, nesnelerin özellikleri olarak tutulan işlevler bu nesnelere "bağlı" değildir. Yukarıdaki tanımda görebileceğiniz objgibi, İşlevler yalnızca bir Nesne türü olduğundan, bunlara başvurulabilir (ve bu nedenle bir İşlev çağrısına başvurularak iletilebilir veya bir İşlev çağrısından başvuru ile döndürülebilir). Bir Fonksiyon için bir başvuru geçirilen zaman, kabul edildi hakkında ek bilgiler dan olduğunu onunla yapılır, şu olur nedenleri:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

İşlev referansımıza bazyapılan çağrı, çağrı için herhangi bir bağlam sağlamaz, bu nedenle etkili bir şekilde aynıdır baz.call(undefined), bu nedenle thisreferans alır window. Eğer bazait olduğunu bilmek istiyorsak obj, bir şekilde bazçağrıldığında bu bilgiyi sağlamalıyız ki bu ilk argüman callveya applykapanışların devreye girdiği yerdir .

Kapsam zincirleri

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Bir İşlev yürütüldüğünde, yeni bir kapsam oluşturur ve herhangi bir kapalı kapsama referans verir. Yukarıdaki örnekte anonim işlev oluşturulduğunda, oluşturulduğu kapsama, yani kapsamına bir referansı vardır bind. Bu "kapatma" olarak bilinir.

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Bir değişkene erişmeye çalıştığınızda, bu "kapsam zinciri" belirtilen ada sahip bir değişken bulmak için yürür - geçerli kapsam değişkeni içermiyorsa, zincirdeki bir sonraki kapsama bakarsınız ve küresel kapsam. Anonim işlev döndürüldüğünde ve bindyürütmeyi tamamladığında, anonim işlev hala bindkapsamının başvurusuna sahiptir , bu nedenle bindkapsamı "kaybolmaz".

Yukarıdakilerin tümü göz önüne alındığında, şimdi aşağıdaki örnekte kapsamın nasıl çalıştığını ve "ön sınır" ın etrafında belirli bir değere sahip bir işlevi geçirme tekniğinin neden thisiş denildiğinde sahip olacağını anlayabilmeniz gerekir:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"

"Bir Fonksiyona bir referans iletildiğinde, fonksiyonun nereden aktarıldığı hakkında ek bilgi taşınmaz " bunun için teşekkür ederim @insin.
Alex Marandon

9

Bu tanımlanmış davranış mı? Çapraz tarayıcı güvenli mi?

Evet. Ve evet.

Neden böyle olduğunun altında yatan bir neden var mı?

Anlamını thisçıkarmak oldukça basittir:

  1. Eğer thisbir yapıcı işlevi içinde kullanılır ve fonksiyon ile çağrıldı newanahtar kelime, thisoluşturulacak nesneyi ifade eder. thiskamusal yöntemlerde bile nesne anlamına gelmeye devam edecektir.
  2. Eğer thisiç içe dahil başka hiçbir yerde kullanılır korumalı fonksiyonları, bu (tarayıcıda durumunda pencere nesnedir) küresel kapsamı belirtir.

İkinci durum, açıkça bir tasarım hatasıdır, ancak kapaklar kullanarak etrafta çalışmak oldukça kolaydır.


4

Bu durumda iç thiskısım this, dış fonksiyonun değişkeni yerine global nesneye bağlanır . Dilin tasarlanma şekli budur.

İyi bir açıklama için Douglas Crockford'un "JavaScript: İyi Parçalar" bölümüne bakınız.


4

ECMAScript hakkında güzel bir eğitim buldum

Bu değer, yürütme bağlamıyla ilgili özel bir nesnedir. Bu nedenle, bir bağlam nesnesi olarak adlandırılabilir (yani, bağlamın yürütme bağlamının etkinleştirildiği bir nesne).

Herhangi bir nesne, bağlamın bu değeri olarak kullanılabilir.

a bu değer, yürütme bağlamının bir özelliğidir, ancak değişken nesnenin bir özelliğidir.

Bu özellik çok önemlidir, çünkü değişkenlerin aksine, bu değer hiçbir zaman tanımlayıcı çözümleme sürecine katılmaz. Yani buna bir koddan erişirken değeri doğrudan yürütme bağlamından ve herhangi bir kapsam zinciri araması olmadan alınır. Bunun değeri, içeriğe girerken yalnızca bir kez belirlenir.

Global bağlamda, bu değer global nesnenin kendisidir (yani, bu değer değişken nesneye eşittir)

Bir işlev bağlamında, her bir işlev çağrısındaki bu değer farklı olabilir

Referans Javascript-çekirdek ve Bölüm-3-bu


" Genel bağlamda, bu değer küresel nesnenin kendisidir (yani, bu değer değişken nesneye eşittir) ". Genel nesne (ES4) "değişken nesne" ve ES5 ortam kaydı gibi, küresel yürütme içeriği bir parçasıdır. Ancak bunlar küresel nesneye göre farklı varlıklardır (örneğin, bir ortam kaydına doğrudan atıfta bulunulamaz, spesifikasyon tarafından yasaklanmıştır, ancak küresel nesne olabilir).
RobG
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.