JavaScript'te [] operatörünü nasıl aşırı yüklersiniz?


86

Javascript'te [] operatörünü aşırı yüklemenin bir yolunu bulamıyorum. Orada bilen var mı?

Düşünüyordum da ...

MyClass.operator.lookup(index)
{
     return myArray[index];
}

yoksa doğru şeylere bakmıyor muyum?


3
Buradaki cevaplar yanlış, JS'deki diziler sadece anahtarları uint32 (- 1) değerlerine zorlanabilen ve prototiplerinde ekstra yöntemlere sahip nesnelerdir
Benjamin Gruenbaum

Sadece MyClassnesnenizi bir dizi yapın. Sen anahtarlarını ve değerlerini kopyalayabilirsiniz myArraysizin için var myObj = new MyClass()nesne.
jpaugh

hey, {} operatörünü aşırı yüklemek istiyorum, herhangi bir fikriniz var mı?
daniel assayag

Yanıtlar:


81

JavaScript'te operatörleri aşırı yükleyemezsiniz.

Bu edilmiş ECMAScript'e 4 için önerilen ancak reddedildi.

Yakında göreceğini sanmıyorum.


4
Bu, bazı tarayıcılardaki proxy'ler ile zaten yapılabilir ve bir noktada tüm tarayıcılara gelecektir. Bkz. Github.com/DavidBruant/ProxyArray
Tristan

O halde jQuery, [] veya .eq () kullanmanıza bağlı olarak nasıl farklı şeyler döndürür? stackoverflow.com/a/6993901/829305
Rikki

2
Şimdi bir Proxy ile yapabilirsiniz.
Eyal

Buna rağmen, "." yerine bir dizi olarak eriştiğiniz sürece, içlerinde semboller bulunan yöntemleri tanımlayabilirsiniz. Yani SmallTalk gibi şeyler eşler nasıl Object arg1: a arg2: b arg3: colduğu Object["arg1:arg2:arg3:"](a,b,c). Yani sahip olabilirsiniz myObject["[]"](1024): P
Dmitry

Bağlantı öldü :(
Gp2mv3

71

Bunu ES6 Proxy ile yapabilirsiniz ( tüm modern tarayıcılarda mevcuttur)

var handler = {
    get: function(target, name) {
        return "Hello, " + name;
    }
};
var proxy = new Proxy({}, handler);

console.log(proxy.world); // output: Hello, world

MDN ile ilgili ayrıntıları kontrol edin .


2
Bir dizin erişimcili kendi sınıfımızı oluşturmak için bunu nasıl kullanırız? yani kendi kurucumu kullanmak istiyorum, bir Proxy oluşturmak istemiyorum.
mpen

1
Bu gerçek bir aşırı yükleme değildir. Nesnenin kendisinde yöntemler çağırmak yerine, şimdi proxy yöntemlerini çağırıyorsunuz.
Pacerier

@Pacerier, alıcıda geri dönebilirsiniz target[name], OP sadece örnekleri gösteriyor
Valen

1
[]Operatörle de çalışır , btw:var key = 'world'; console.log(proxy[key]);
Klesun

15

Basit cevap, JavaScript'in köşeli parantezler aracılığıyla bir Nesnenin alt öğelerine erişime izin vermesidir.

Böylece sınıfınızı tanımlayabilirsiniz:

MyClass = function(){
    // Set some defaults that belong to the class via dot syntax or array syntax.
    this.some_property = 'my value is a string';
    this['another_property'] = 'i am also a string';
    this[0] = 1;
};

Daha sonra her iki sözdizimiyle sınıfınızın herhangi bir örneğindeki üyelere erişebileceksiniz.

foo = new MyClass();
foo.some_property;  // Returns 'my value is a string'
foo['some_property'];  // Returns 'my value is a string'
foo.another_property;  // Returns  'i am also a string'
foo['another_property'];  // Also returns 'i am also a string'
foo.0;  // Syntax Error
foo[0];  // Returns 1
foo['0'];  // Returns 1

2
Bunu performans nedenleriyle kesinlikle önermem, ancak buradaki sorunun tek gerçek çözümü budur. Belki de bunun mümkün olmadığını belirten bir düzenleme, bunu harika bir cevap haline getirebilir.
Milimetric

4
Bu değil soru istediği. Soru, foo['random']kodunuzun yapamayacağı bir şeyi yakalamanın bir yolunu soruyor .
Pacerier

9

Bir vekil kullanın. Cevaplarda başka bir yerde bahsedilmişti ama bence bu daha iyi bir örnek:

var handler = {
    get: function(target, name) {
        if (name in target) {
            return target[name];
        }
        if (name == 'length') {
            return Infinity;
        }
        return name * name;
    }
};
var p = new Proxy({}, handler);

p[4]; //returns 16, which is the square of 4.

Proxy'lerin bir ES6 özelliği olduğunu ve bu nedenle daha sınırlı bir tarayıcı desteğine sahip olduğunu (ve Babel de onları taklit edemez ) muhtemelen bahsetmeye değer .
jdehesa

8

Parantez operatörü aslında mülk erişim operatörü olduğundan, alıcılar ve ayarlayıcılarla ona bağlanabilirsiniz. IE için bunun yerine Object.defineProperty () kullanmanız gerekecektir. Misal:

var obj = {
    get attr() { alert("Getter called!"); return 1; },
    set attr(value) { alert("Setter called!"); return value; }
};

obj.attr = 123;

IE8 + için aynı:

Object.defineProperty("attr", {
    get: function() { alert("Getter called!"); return 1; },
    set: function(value) { alert("Setter called!"); return value; }
});

IE5-7 için onpropertychangeyalnızca DOM öğeleri için çalışan, ancak diğer nesneler için geçerli olmayan olay vardır .

Yöntemin dezavantajı, önceden tanımlanmış herhangi bir adı olmayan rastgele özelliğe değil, yalnızca önceden tanımlanmış özellik kümesine olan isteklere bağlanabilmenizdir.


Yaklaşımınızı jsfiddle.net'te gösterebilir misiniz? Çözümün ifadedeki herhangi bir anahtar için çalışacağını varsayıyorum, obj['any_key'] = 123;ancak kodunuzda gördüğüm şey herhangi bir (henüz bilinmeyen) anahtar için ayarlayıcı / alıcı tanımlamam gerekiyor. İmkansız.
dma_k

3
Eksi 1'i dengelemek için artı 1, çünkü bu yalnızca IE değildir.
Küre

Bu bir sınıf işlevi için yapılabilir mi? Bunun sözdizimini kendim bulmaya çalışıyorum.
Michael Hoffmann

7

Proxy'yi açıklandığı gibi kullanmanız gerekir, ancak sonuçta bir sınıf yapıcısına entegre edilebilir

return new Proxy(this, {
    set: function( target, name, value ) {
...}};

Bununla'. Daha sonra set ve get (ayrıca deleteProperty) işlevleri etkinleşir. Farklı görünen bir Proxy nesnesi elde etseniz de, çoğu zaman karşılaştırmayı sormak işe yarar (target.constructor === MyClass) sınıf türü vb. [Target.constructor.name'nin sınıf adı olduğu bir işlev olmasına rağmen metin (sadece biraz farklı çalışan şeylere bir örnek olarak)]


1
Bu, bir Backbone koleksiyonundaki [] öğesini aşırı yüklemek için iyi çalışır, böylece tek tek model nesneleri, diğer tüm özellikler için bir geçişle [] kullanılarak döndürülür.
justkt

5

Yani var ne olursa olsun = Sınıfım [4] gibi bir şey yapmayı umuyorsunuz; ? Öyleyse, basit cevap, Javascript'in şu anda operatör aşırı yüklemesini desteklememesidir.


1
Peki jQuery nasıl çalışır? JQuery nesnesinde $ ('. Foo']. Html () gibi bir yöntemi çağırabilir veya $ ['. Foo') [0] gibi ilk eşleşen dom öğesini elde edebilirsiniz
kagronick

1
jQuery bir işlevdir, $ işlevine bir parametre aktarırsınız. Bu nedenle () köşeli parantezler değil []
James Westgate

5

Bunu yapmanın sinsi bir yolu, dilin kendisini genişletmektir.

Aşama 1

özel bir indeksleme kuralı tanımlayın, buna "[]" diyelim.

var MyClass = function MyClass(n) {
    this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
    value: function(index) {
        return this.myArray[index];
    }
});

...

var foo = new MyClass(1024);
console.log(foo["[]"](0));

Adım 2

yeni bir eval uygulaması tanımlar. (bunu bu şekilde yapmayın, ancak bu bir kavram kanıtıdır).

var MyClass = function MyClass(length, defaultValue) {
    this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
    value: function(index) {
        return this.myArray[index];
    }
});

var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));

var mini_eval = function(program) {
    var esprima = require("esprima");
    var tokens = esprima.tokenize(program);
    
    if (tokens.length == 4) {    
        var types = tokens.map(a => a.type);
        var values = tokens.map(a => a.value);
        if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
            if (values[1] == '[' && values[3] == ']') {
                var target = eval(values[0]);
                var i = eval(values[2]);
                // higher priority than []                
                if (target.hasOwnProperty('[]')) {
                    return target['[]'](i);
                } else {
                    return target[i];
                }
                return eval(values[0])();
            } else {
                return undefined;
            }
        } else {
            return undefined;
        }
    } else {
        return undefined;
    }    
};

mini_eval("foo[33]");

yukarıdakiler daha karmaşık dizinler için işe yaramayacaktır, ancak daha güçlü ayrıştırma ile olabilir.

alternatif:

Kendi üst küme dilinizi oluşturmak yerine, notasyonunuzu mevcut dile göre derleyebilir ve sonra değerlendirebilirsiniz. Bu, ilk kullanımdan sonra ayrıştırma ek yükünü yerel olarak azaltır.

var compile = function(program) {
    var esprima = require("esprima");
    var tokens = esprima.tokenize(program);
    
    if (tokens.length == 4) {    
        var types = tokens.map(a => a.type);
        var values = tokens.map(a => a.value);
        if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
            if (values[1] == '[' && values[3] == ']') {
                var target = values[0];
                var i = values[2];
                // higher priority than []                
                return `
                    (${target}['[]']) 
                        ? ${target}['[]'](${i}) 
                        : ${target}[${i}]`
            } else {
                return 'undefined';
            }
        } else {
            return 'undefined';
        }
    } else {
        return 'undefined';
    }    
};

var result = compile("foo[0]");
console.log(result);
console.log(eval(result));

3
+1
Jus

1
Kesinlikle iğrenç. onu seviyorum.
SliceThePi

Zeka genellikle şu ya da bu şekilde olur. Yeterli kaynaklarla karşılığını alabilecek değerli bir egzersiz olmadığı anlamına gelmez. En tembel insanlar, sırf müsait olmasalar bile daha tanıdık ortamlarda çalışabilmek için kendi derleyicilerini ve çevirmenlerini yazan kişilerdir. Bununla birlikte, bölgede daha fazla deneyime sahip biri tarafından daha az aceleyle yazılırsa daha az iğrenç olurdu. Tüm önemsiz çözümler şu ya da bu şekilde iğrençtir, bizim işimiz ödünleşmeleri bilmek ve sonuçlarla başa çıkmaktır.
Dmitry

3

Vekil alabiliriz | yöntemleri doğrudan ayarlayın . Esinlenen bu .

class Foo {
    constructor(v) {
        this.data = v
        return new Proxy(this, {
            get: (obj, key) => {
                if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
                    return obj.data[key]
                else 
                    return obj[key]
            },
            set: (obj, key, value) => {
                if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
                    return obj.data[key] = value
                else 
                    return obj[key] = value
            }
        })
    }
}

var foo = new Foo([])

foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
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.