Як би ви перевантажили оператор [] у javascript


85

Здається, я не можу знайти спосіб перевантажити оператор [] у javascript. Хтось там знає?

Я думав про лінії ...

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

або я не дивлюся на правильні речі.


3
Відповіді тут помилкові, масиви в JS - це просто об’єкти, ключі яких примусово придатні до значень uint32 (- 1) і мають додаткові методи на своєму прототипі
Бенджамін Груенбаум

Просто зробіть свій MyClassоб’єкт масивом. Ви можете скопіювати ключі та значення з myArrayвашого var myObj = new MyClass()об’єкта.
jpaugh

привіт, я хотів би перевантажити оператор {}, будь-яка ідея?
daniel assayag

Відповіді:


81

Ви не можете перевантажувати оператори в JavaScript.

Він був запропонований для ECMAScript 4, але відхилений.

Я не думаю, що ви побачите це найближчим часом.


4
Це може бути здійснено з проксі-серверами в деяких браузерах і в якийсь момент з’явиться у всіх браузерах. Дивіться github.com/DavidBruant/ProxyArray
Трістан

Тоді як jQuery повертає різні речі залежно від того, використовуєте ви [] або .eq ()? stackoverflow.com/a/6993901/829305
Ріккі,

2
Ви можете зробити це зараз за допомогою проксі.
Еял,

хоча ви можете визначити методи із символами в них, якщо ви отримуєте до них доступ як масив, а не як ".". Ось як SmallTalk відображає такі речі Object arg1: a arg2: b arg3: cяк Object["arg1:arg2:arg3:"](a,b,c). Отже, ви можете мати myObject["[]"](1024): P
Дмитро

Посилання мертве :(
Gp2mv3

69

Ви можете зробити це за допомогою ES6 Proxy (доступний у всіх сучасних браузерах)

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]в геттер, ОП лише показує приклади
Вален

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

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;але те, що я бачу у вашому коді, мені потрібно визначити setter / getter для будь-якого (ще невідомого) ключа. Це неможливо.
dma_k

3
плюс 1 для компенсації мінус 1, оскільки це не тільки IE.
куля

Чи можна це зробити для функції класу? Я намагаюся знайти синтаксис для цього самостійно.
Майкл Хоффманн

7

Вам потрібно використовувати Proxy, як пояснено, але в кінцевому підсумку його можна інтегрувати в конструктор класів

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

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


1
Це добре працює для перевантаження [] колекції Backbone, щоб окремі об'єкти моделі поверталися при використанні [] з проходом для всіх інших властивостей.
justkt

5

Отже, ви сподіваєтесь зробити щось на зразок var whatever = 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.