Как бы вы перегрузили оператор [] в javascript

85

Кажется, я не могу найти способ перегрузить оператор [] в javascript. Кто-нибудь знает?

Я думал о ...

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

или я смотрю не на то, что нужно.

хитрый
источник
3
Ответы здесь неверны. Массивы в JS - это просто объекты, ключи которых приводятся к значениям uint32 (- 1) и имеют дополнительные методы в своем прототипе
Бенджамин Грюнбаум
Просто сделайте свой MyClassобъект массивом. Вы можете скопировать ключи и значения из myArrayв свой var myObj = new MyClass()объект.
jpaugh
Эй, я бы хотел перегрузить оператор {}, есть идеи?
Дэниел Ассаяг

Ответы:

81

Вы не можете перегружать операторы в JavaScript.

Он был предложен для ECMAScript 4, но отклонен.

Не думаю, что вы увидите это в ближайшее время.

Питер Бейли
источник
4
Это может быть выполнено с помощью прокси-серверов в некоторых браузерах, и в какой-то момент будет доступно для всех браузеров. См. Github.com/DavidBruant/ProxyArray
Тристан,
Тогда как jQuery возвращает разные вещи в зависимости от того, используете ли вы [] или .eq ()? stackoverflow.com/a/6993901/829305
Рикки
2
Теперь вы можете сделать это с помощью прокси.
Эяль
хотя вы можете определять методы с символами в них, если вы обращаетесь к ним как к массиву, а не через ".". Вот как SmallTalk отображает такие вещи, как Object arg1: a arg2: b arg3: cas Object["arg1:arg2:arg3:"](a,b,c). Итак, вы можете иметь myObject["[]"](1024): P
Дмитрий
Ссылка мертвая :(
Gp2mv3
69

Вы можете сделать это с помощью прокси ES6 (доступен во всех современных браузерах)

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

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

Подробности читайте на MDN .

средний Джо
источник
2
Как бы мы использовали это для создания нашего собственного класса с средством доступа к индексу? т.е. я хочу использовать свой собственный конструктор, я не хочу создавать прокси.
mpen
1
Это не настоящая перегрузка. Вместо вызова методов самого объекта вы теперь вызываете методы прокси.
Pacerier
@Pacerier, вы можете вернуть его target[name]в получателе, OP просто показывает примеры
Вален
1
Работает и с []оператором, кстати:var key = 'world'; console.log(proxy[key]);
Клесун
15

Простой ответ заключается в том, что JavaScript разрешает доступ к дочерним элементам объекта через квадратные скобки.

Итак, вы можете определить свой класс:

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;
};

После этого вы сможете получить доступ к членам любых экземпляров вашего класса с любым синтаксисом.

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
Я бы определенно не рекомендовал это по соображениям производительности, но это единственное реальное решение проблемы здесь. Возможно, это будет отличным ответом на правку, в которой говорится, что это невозможно.
Milimetric 01
4
Это не то , что хочет , чтобы вопрос. Вопрос заключается в том, чтобы выяснить, foo['random']чего не может сделать ваш код.
Pacerier
9

Используйте прокси. Об этом упоминалось в другом месте в ответах, но я думаю, что это лучший пример:

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.
Эяль
источник
Вероятно, стоит упомянуть, что прокси являются функцией ES6 и, следовательно, имеют более ограниченную поддержку браузераBabel также не может их подделать ).
jdehesa
8

Поскольку оператор скобок на самом деле является оператором доступа к свойствам, вы можете подключиться к нему с помощью геттеров и сеттеров. Для IE вам придется вместо этого использовать Object.defineProperty (). Пример:

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

obj.attr = 123;

То же самое для IE8 +:

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

Для IE5-7 есть onpropertychangeтолько событие, которое работает для элементов DOM, но не для других объектов.

Недостатком этого метода является то, что вы можете подключать запросы только к предопределенному набору свойств, а не к произвольному свойству без предопределенного имени.

кстеп
источник
Не могли бы вы продемонстрировать свой подход на jsfiddle.net ? Я предполагаю, что это решение должно работать для любого ключа в выражении, obj['any_key'] = 123;но то, что я вижу в вашем коде, мне нужно определить установщик / получатель для любого (еще не известного) ключа. Это невозможно.
dma_k 05
3
плюс 1, чтобы компенсировать минус 1, потому что это не только IE.
orb
Можно ли это сделать для функции класса? Я изо всех сил пытаюсь найти синтаксис для этого самостоятельно.
Майкл Хоффманн,
7

Вам необходимо использовать прокси, как описано, но в конечном итоге его можно интегрировать в конструктор класса.

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

с этим'. Затем сработают функции set и get (также deleteProperty). Хотя вы получаете объект Proxy, который кажется другим, он по большей части работает, чтобы запросить сравнение (target.constructor === MyClass), это тип класса и т.д. [даже если это функция, где target.constructor.name - это имя класса в текст (просто отмечу пример того, что работает немного по-другому.)]

Мастер Джеймс
источник
1
Это хорошо работает для перегрузки [] в коллекции Backbone, так что отдельные объекты модели возвращаются при использовании [] с передачей для всех других свойств.
Justkt
5

Итак, вы надеетесь сделать что-то вроде var any = MyClassInstance [4]; ? Если это так, простой ответ заключается в том, что Javascript в настоящее время не поддерживает перегрузку операторов.

Мэтт Молнар
источник
1
Итак, как работает jQuery. Вы можете вызвать метод объекта jQuery, например $ ('. Foo'). Html (), или получить первый соответствующий элемент dom, например $ ('. Foo') [0]
kagronick
1
jQuery - это функция, вы передаете параметр функции $. Следовательно, скобки (), а не []
Джеймс Вестгейт
5

один из хитрых способов сделать это - расширить сам язык.

шаг 1

определите пользовательское соглашение об индексировании, назовем его «[]».

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));

шаг 2

определить новую реализацию eval. (не делайте этого, но это доказательство концепции).

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]");

указанное выше не будет работать для более сложных индексов, но может быть при более сильном синтаксическом анализе.

альтернатива:

вместо того, чтобы прибегать к созданию собственного надмножества языка, вы можете вместо этого скомпилировать свою нотацию на существующий язык, а затем оценить ее. Это сокращает накладные расходы на синтаксический анализ до нативного после первого использования.

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));
Дмитрий
источник
1
Вы кривые +1
Jus
совершенно отвратительно. я люблю это.
SliceThePi
Ум обычно так или иначе. Это не значит, что это не стоящее упражнение, которое окупается с помощью достаточных ресурсов. Самые ленивые люди - это те, кто пишет свои собственные компиляторы и переводчики, чтобы они могли работать в более знакомой среде, даже если они недоступны. Тем не менее, было бы менее отвратительно, если бы это было написано в меньшей спешке кем-то с большим опытом в этой области. Все нетривиальные решения так или иначе отвратительны, наша работа - знать компромиссы и справляться с последствиями.
Дмитрий
3

Мы можем получить через прокси | установить методы напрямую. Вдохновленный этим .

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]
Джабер
источник