Как бы вы перегрузили оператор [] в 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]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.