您如何在javascript中重载[]运算符


85

我似乎找不到在JavaScript中重载[]运算符的方法。外面有人知道吗?

我在想...

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

还是我没有看正确的东西。


3
这里的答案是错误的,JS中的数组只是对象,其键可强制为uint32(-1)值,并且在其原型上具有其他方法
Benjamin Gruenbaum

只需将您的MyClass对象设置为数组即可。您可以将键和值从复制myArray到您的var myObj = new MyClass()对象。
jpaugh

嘿,我想重载{}运算符,知道吗?
丹尼尔·萨谢格

Answers:


81

您不能在JavaScript中重载运算符。

它是针对ECMAScript 4提出的,但被拒绝。

我认为您不会很快看到它。


4
这可能已经可以在某些浏览器中使用代理来实现,并且将在某些时候出现在所有浏览器中。见github.com/DavidBruant/ProxyArray
Tristan

那么jQuery如何根据您使用[]还是.eq()返回不同的东西?stackoverflow.com/a/6993901/829305
Rikki 2015年

2
您现在可以使用代理进行操作。
艾尔(Eyal)

尽管您可以定义它们中带有符号的方法,只要您将它们作为数组而不是使用“。”来访问即可。这就是SmallTalk映射Object arg1: a arg2: b arg3: cas的方式Object["arg1:arg2:arg3:"](a,b,c)。这样您就可以拥有myObject["[]"](1024):P
德米特里(Dmitry)

链接已死:(
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 '16

1
这不是真正的重载。现在,您不再调用对象本身的方法,而是调用代理的方法。
Pacerier's

@Pacerier,您可以target[name]在getter中返回,OP只是显示示例
Valen

1
[]顺便说一句,操作员也可以使用:var key = 'world'; console.log(proxy[key]);
Klesun

15

简单的答案是JavaScript允许通过方括号访问Object的子级。

因此,您可以定义您的课程:

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 '17

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

由于方括号运算符实际上是属性访问运算符,因此可以将其与getter和setter挂钩。对于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 2013年

3
加1抵消负1,因为这不仅是IE。
orb 2015年

可以针对类函数执行此操作吗?我正在努力自己寻找语法。
Michael Hoffmann'3

7

您需要按照说明使用Proxy,但最终可以将其集成到类构造函数中

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

有了这个'。然后将触发set和get(也deleteProperty)函数。尽管您获得了一个看起来不同的Proxy对象,但它在大多数情况下都可以要求进行比较(target.constructor === MyClass),它是类的类型,等等。文字(只是举例,说明工作原理略有不同。)]


1
这对于在Backbone集合上重载[]效果很好,以便使用[]并为所有其他属性传递时返回单个模型对象。
justkt

5

因此,您希望像var what = MyClassInstance [4];这样的操作。?如果是这样,简单的答案是Javascript当前不支持运算符重载。


1
那么jQuery是如何工作的。您可以在jQuery对象上调用类似$('。foo')。html()的方法,或获取第一个匹配的dom元素,例如$('。foo')[0]
kagronick

1
jQuery是一个函数,您正在将参数传递给$函数。因此()括号,而不是[]
James Westgate

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步

定义一个新的评估实现。(不要这样做,但这是概念的证明)。

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 '19

聪明通常以一种或另一种方式。这并不意味着可以用足够的资源获得回报不是一个值得的练习。最懒惰的人是编写自己的编译器和翻译器的人,即使他们不可用,他们也可以在更熟悉的环境中工作。话虽如此,如果该领域有更多经验的人不着急地写它,那将不会那么令人作呕。所有非平凡的解决方案都以一种或另一种方式令人恶心,我们的工作是了解各种折衷方案并应对后果。
德米特里(Dmitry)

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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.