SORU
22 EYLÜL 2013, Pazar


JavaScript dizi oluşturma - garip sözdizimi

Es-tartışma e-posta listesi: aşağıdaki kodu karşılaştım

Array.apply(null, { length: 5 }).map(Number.call, Number);

Bu üretir

[0, 1, 2, 3, 4]

Neden bu kodu sonucudur? Burada neler oluyor?

CEVAP
22 EYLÜL 2013, Pazar


Anlayış bu "hack" anlayış gerektirir birkaç şey:

  1. Neden sadece Array(5).map(...) yapmıyoruz
  2. Nasıl Function.prototype.apply bağımsız işler
  3. Nasıl Array birden fazla bağımsız işler
  4. Nasıl Number bağımsız değişken olarak işler
  5. 32 ** nedir

Bu daha-daha-daha uzun olacak, bu yüzden javascript konularında ileri düzey, onlar değil. Baştan başlayacağız. Kemerlerinizi bağlayın!

1. Neden sadece Array(5).map değil mi?

Bir dizi ne, gerçekten? Normal değerleri göster hangi tamsayı tuşları içeren nesne. Diğer özel özellikler, örneğin length büyülü değişken vardır, ama sadece başka bir nesne gibi bir şey özünde, ** 35 normal bir harita değil. Hadi diziler ile biraz oynasak mı?

var arr = ['a', 'b', 'c'];
arr.hasOwnProperty(0); //true
arr[0]; //'a'
Object.keys(arr); //['0', '1', '2']
arr.length; //3, implies arr[3] === undefined

//we expand the array by 1 item
arr.length = 4;
arr[3]; //undefined
arr.hasOwnProperty(3); //false
Object.keys(arr); //['0', '1', '2']

Dizideki öğelerin sayısı arasındaki içsel fark var arr.length key=>value sayısı arr.length daha farklı olabilir hangi dizi var eşleştirmeleri.

arr.length ile dizi genişliyordeğilkey=>value yeni haritalama, dizi tanımsız değerler olduğunu, bu yüzden değilbu anahtar yok. Ve var olmayan bir özellik erişmeye çalıştığınızda ne olur? undefined olsun.

Şimdi kafamızı biraz Kaldır, ve arr.map gibi işlevleri bu özellikler üzerinde yürümek yok neden bakın. arr[3] sadece tanımsız ve anahtar varsa, bu dizi fonksiyonları sadece başka bir değer gibi geçer diye:

//just to remind you
arr; //['a', 'b', 'c', undefined];
arr.length; //4
arr[4] = 'e';

arr; //['a', 'b', 'c', undefined, 'e'];
arr.length; //5
Object.keys(arr); //['0', '1', '2', '4']

arr.map(function (item) { return item.toUpperCase() });
//["A", "B", "C", undefined, "E"]

Ben kasıtlı olarak daha fazla anahtar kendini asla var olduğunu kanıtlamak için bir yöntem çağrısı kullanılır: undefined.toUpperCase Çağrılırken bir hata kaldırdı olurdu, ama olmadı. Kanıtlamak içinbu:

arr[5] = undefined;
arr; //["a", "b", "c", undefined, "e", undefined]
arr.hasOwnProperty(5); //true
arr.map(function (item) { return item.toUpperCase() });
//TypeError: Cannot call method 'toUpperCase' of undefined

Array(N) işler Nasıl. ve şimdi benim noktaya mı geldik: Section 15.4.2.2 işlemini açıklar. Bir sürü saçma sapan şeyler umurumuzda bile değil, ama eğer sen yönetmek için okumak arasındaki çizgiler (veya sadece bana güven, ama yok), temelde aşağı kaynar bu

function Array(len) {
    var ret = [];
    ret.length = len;
    return ret;
}

(len geçerli bir gerçek spec işaretli olan) varsayımı altında faaliyet uınt32 ve değeri herhangi bir sayı)

Şimdi sen-ebilmek görmek neden yapıyor Array(5).map(...) yaramaz - bilmiyoruz tanımlamak len öğeleri dizisi, yok oluştur key => value eşlemeleri, biz sadece alter length özelliği.

En azından şimdi, ikinci büyülü bakayım:

2. Nasıl Function.prototype.apply çalışır

apply temel olarak bir dizi alır ve bir işlev çağrısı olarak göz önüne sermek ne argümanlar. Aşağıdaki hemen hemen aynı olduğu anlamına gelir:

function foo (a, b, c) {
    return a   b   c;
}
foo(0, 1, 2); //3
foo.apply(null, [0, 1, 2]); //3

Şimdi, sadece arguments özel değişken giriş yaparak apply nasıl çalıştığını görmek işlemini kolaylaştırmak edebiliriz:

function log () {
    console.log(arguments);
}

log.apply(null, ['mary', 'had', 'a', 'little', 'lamb']);
 //["mary", "had", "a", "little", "lamb"]

//arguments is a pseudo-array itself, so we can use it as well
(function () {
    log.apply(null, arguments);
})('mary', 'had', 'a', 'little', 'lamb');
 //["mary", "had", "a", "little", "lamb"]

//a NodeList, like the one returned from DOM methods, is also a pseudo-array
log.apply(null, document.getElementsByTagName('script'));
 //[script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script, script]

//carefully look at the following two
log.apply(null, Array(5));
//[undefined, undefined, undefined, undefined, undefined]
//note that the above are not undefined keys - but the value undefined itself!

log.apply(null, {length : 5});
//[undefined, undefined, undefined, undefined, undefined]

Kolay sondan ikinci örnekte: iddiamı kanıtlamak için

function ahaExclamationMark () {
    console.log(arguments.length);
    console.log(arguments.hasOwnProperty(0));
}

ahaExclamationMark.apply(null, Array(2)); //2, true

(Evet, kelime oyunu) kullanılır. key => value eşleme apply, geçtik dizideki bulunmayabilir, ama kesinlikle arguments değişken var. Son örnek, işleri aynı sebepten dolayıdır: anahtarları verdiğimiz nesne yok, ama arguments varlar.

Bu yüzden mi? Hadi Function.prototype.apply tanımlandığı ** 117, bak. Çoğunlukla şeyler umurumda değil, ama burada ilginç olan kısmı:

4=başlangıç
  • Len beni neden olsun [[]] değişkeni ile argArray iç yöntemi"". uzunluğu
  • Temelde anlamına gelir: argArray.length. Spec o zaman gelirleri için basit bir for döngü length öğeler, yapma list gelen değerleri (list bazı iç voodoo, ama aslında bir dizi). Çok, çok gevşek kod: açısından

    Function.prototype.apply = function (thisArg, argArray) {
        var len = argArray.length,
            argList = [];
    
        for (var i = 0; i < len; i  = 1) {
            argList[i] = argArray[i];
        }
    
        //yeah...
        superMagicalFunctionInvocation(this, thisArg, argList);
    };
    

    Bu durumda argArray bir taklit etmeliyiz length özelliği olan bir nesnedir. Ve şimdi değerleri tanımsızdır neden görebilirsiniz, ama anahtarlar yok, arguments açık değil: key=>value eşlemeleri oluşturmak.

    Vay be, yani bu önceki bölümü daha kısa olabilirdi. Ama biz bitirmek, bu yüzden sabırlı olun zaman pasta olacak! Ancak, aşağıdaki bölümden sonra kısa olacak (söz) ifadesi diseksiyon başlayabiliriz. Unuttun galiba, soruyu nasıl aşağıdaki gibi çalışır:

    Array.apply(null, { length: 5 }).map(Number.call, Number);
    

    3. Nasıl Array birden fazla bağımsız işler

    Bu kadar! Gördük ne olur zaman geçirmek length tartışma Array ama ifadesinde, verdiğimiz birçok şey gibi argümanlar (bir dizinin 5 undefined aslında). Section 15.4.2.1 bize ne anlatıyor. Son paragraf bizim için çok önemli ve ifadeligerçektengarip bir şekilde, ama aşağı kaynar:

    function Array () {
        var ret = [];
        ret.length = arguments.length;
    
        for (var i = 0; i < arguments.length; i  = 1) {
            ret[i] = arguments[i];
        }
    
        return ret;
    }
    
    Array(0, 1, 2); //[0, 1, 2]
    Array.apply(null, [0, 1, 2]); //[0, 1, 2]
    Array.apply(null, Array(2)); //[undefined, undefined]
    Array.apply(null, {length:2}); //[undefined, undefined]
    

    Tada! Birkaç tanımsız değerler dizisi var ve bu tanımsız değerler dizisi dönüyoruz.

    İfadenin ilk bölümü

    Son olarak, aşağıdaki deşifre edebiliriz:

    Array.apply(null, { length: 5 })
    

    Bir dizi 5 tanımsız değerleri içeren, tüm varlığı tuşları ile döndüren gördük.

    Şimdi, bu ifadenin ikinci bölümü için:

    [undefined, undefined, undefined, undefined, undefined].map(Number.call, Number)
    

    Bu çok belirsiz kesmek güvenmek değil kolay olmayan, karmaşık parçası olacak.

    4. Number giriş yapıyorlar

    Number(something) (section 15.7.1) sayısı something dönüştürür, ve hepsi bu. Bunu nasıl yaptığını biraz kıvrık, özellikle dizeleri durumda, ancak işlemi merak ediyorsanız section 9.3 olarak tanımlanır.

    5. Function.prototype.call oyunlar

    call apply'In kardeşi, section 15.3.4.4 tanımlanmış. Bağımsız değişken bir dizi almak yerine, sadece alınan bağımsız değişkenleri alır ve onları öne geçirir.

    İşler 91 ** birden fazla birlikte, garip 11 krank zincir ilginç:

    function log () {
        console.log(this, arguments);
    }
    log.call.call(log, {a:4}, {a:5});
    //{a:4}, [{a:5}]
    //^---^  ^-----^
    // this   arguments
    

    Bu ne kavramak kadar oldukça işe layıktır. log.call bir işlevi, fonksiyonu call başka bir yöntem eşdeğerdir, ve gibi, kendisini call bir yöntem de vardır:

    log.call === log.call.call; //true
    log.call === Function.call; //true
    

    call ne yapar? thisArg ve argümanlar için bir demet bir kabul eder, ve onun ana işlevi çağırır. apply (tekrar, çok gevşek kodu, işe yaramaz) o tanımlayabiliriz:

    Function.prototype.call = function (thisArg) {
        var args = arguments.slice(1); //I wish that'd work
        return this.apply(thisArg, args);
    };
    

    Hadi bu iner nasıl takip:

    log.call.call(log, {a:4}, {a:5});
      this = log.call
      thisArg = log
      args = [{a:4}, {a:5}]
    
      log.call.apply(log, [{a:4}, {a:5}])
    
        log.call({a:4}, {a:5})
          this = log
          thisArg = {a:4}
          args = [{a:5}]
    
          log.apply({a:4}, [{a:5}])
    

    Daha sonra bir bölümü ya da tüm .map

    Henüz bitmedi. En dizi yöntemleri için bir fonksiyon kaynağı ne oluyor: bir bakalım

    function log () {
        console.log(this, arguments);
    }
    
    var arr = ['a', 'b', 'c'];
    arr.forEach(log);
    //window, ['a', 0, ['a', 'b', 'c']]
    //window, ['b', 1, ['a', 'b', 'c']]
    //window, ['c', 2, ['a', 'b', 'c']]
    //^----^  ^-----------------------^
    // this         arguments
    

    this bir argüman kendimizi biz yok eğer doğru değilse, 105 ** varsayılan. Hangi argümanlar bizim geri verilir sırasını not edin, ve garip 11 yine tüm yol:

    arr.forEach(log.call, log);
    //'a', [0, ['a', 'b', 'c']]
    //'b', [1, ['a', 'b', 'c']]
    //'b', [2, ['a', 'b', 'c']]
    // ^    ^
    

    Hey hey...hadi biraz geçmişe gidelim bakalım. Burada neler oluyor? forEach tanımlandığı section 15.4.4.18, görüyoruz, aşağıdaki oldukça fazla olur:

    var callback = log.call,
        thisArg = log;
    
    for (var i = 0; i < arr.length; i  = 1) {
        callback.call(thisArg, arr[i], i, arr);
    }
    

    Yani, bu elde ederiz

    log.call.call(log, arr[i], i, arr);
    //After one `.call`, it cascades to:
    log.call(arr[i], i, arr);
    //Further cascading to:
    log(i, arr);
    

    Şimdi .map(Number.call, Number) nasıl çalıştığını görebiliriz:

    Number.call.call(Number, arr[i], i, arr);
    Number.call(arr[i], i, arr);
    Number(i, arr);
    

    Bir sayı için i, geçerli dizin, dönüşümü verir.

    Sonuç olarak,

    İfade

    Array.apply(null, { length: 5 }).map(Number.call, Number);
    

    Eserlerinde iki bölümden oluşur:

    var arr = Array.apply(null, { length: 5 }); //1
    arr.map(Number.call, Number); //2
    

    İlk bölümü 5 tanımsız öğeleri bir dizi oluşturur. İkinci dizi gider ve endeks öğe Endeksi: bir dizi sonuç alır

    [0, 1, 2, 3, 4]
    

    Bunu Paylaş:
    • Google+
    • E-Posta
    Etiketler:

    YORUMLAR

    SPONSOR VİDEO

    Rastgele Yazarlar

    • FILIPeeeK

      FILIPeeeK

      22 Mayıs 2006
    • Jonathan Leack

      Jonathan Lea

      26 ŞUBAT 2007
    • PaulGBelliveau

      PaulGBellive

      5 Mart 2009