【问题标题】:How would you overload the [] operator in javascript你将如何在 javascript 中重载 [] 运算符
【发布时间】:2010-12-15 05:52:09
【问题描述】:

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

我在想……

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

还是我看的不对。

【问题讨论】:

  • 这里的答案是错误的,JS 中的数组只是对象,其键可以强制转换为 uint32 ( - 1) 值,并且在其原型上有额外的方法
  • 只需将您的 MyClass 对象设为数组即可。您可以将 myArray 中的键和值复制到您的 var myObj = new MyClass() 对象中。
  • 嘿,我想重载 {} 运算符,知道吗?

标签: javascript operator-overloading


【解决方案1】:

您可以使用 ES6 代理(在 all 现代浏览器中可用)来做到这一点

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

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

查看MDN的详细信息。

【讨论】:

  • 我们如何使用它来创建我们自己的带有索引访问器的类?即我想使用自己的构造函数,我不想构造代理。
  • 这不是真正的重载。您现在调用的是代理的方法,而不是调用对象本身的方法。
  • @Pacerier 您可以在 getter 中返回 target[name],OP 只是显示示例
  • 也可以与 [] 运算符一起使用,顺便说一句:var key = 'world'; console.log(proxy[key]);
【解决方案2】:

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

它是proposed for ECMAScript 4,但被拒绝了。

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

【讨论】:

  • 这可能已经在某些浏览器中通过代理实现了,并且将在某个时候出现在所有浏览器中。见github.com/DavidBruant/ProxyArray
  • 那么jQuery如何根据你使用[]还是.eq()返回不同的东西呢? stackoverflow.com/a/6993901/829305
  • 您现在可以使用代理来完成。
  • 尽管您可以定义带有符号的方法,只要您将它们作为数组而不是“.”来访问。这就是 SmallTalk 将Object arg1: a arg2: b arg3: c 之类的东西映射为Object["arg1:arg2:arg3:"](a,b,c) 的方式。所以你可以有myObject["[]"](1024):P
  • 链接失效:(
【解决方案3】:

简单的答案是 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

【讨论】:

  • 出于性能原因,我绝对不会推荐这个,但它是这里问题的唯一实际解决方案。也许一个说明这是不可能的编辑会使这是一个很好的答案。
  • 不是问题想要的。问题是寻求一种方法来捕获您的代码无法做到的foo['random']
【解决方案4】:

使用代理。它在答案的其他地方提到过,但我认为这是一个更好的例子:

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.

【讨论】:

【解决方案5】:

由于括号运算符实际上是属性访问运算符,因此您可以使用 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。这是不可能的。
  • 加 1 来抵消负 1,因为这不仅仅是 IE。
  • 这可以用于类函数吗?我正在努力寻找自己的语法。
  • 似乎这实际上可以在任意属性上调用:var a = new Array(2);function trap_indexing(obj,index) {Object.defineProperty(obj,index,{get() {console.log("getting");return this['_shadow'+index];},set(p) {console.log("setting"); }});}trap_indexing(a,0);trap_indexing(a,1);trap_indexing(a,2);a[0] = 'barf';console.log(a[0]);a[1] = 'cat';console.log(a[1]);
【解决方案6】:

一种偷偷摸摸的方法是扩展语言本身。

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

第二步

定义一个新的 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));

【讨论】:

  • 聪明通常是这样或那样的。并不意味着这不是一个值得拥有足够资源的锻炼。最懒惰的人是那些编写自己的编译器和翻译器的人,以便他们可以在更熟悉的环境中工作,即使他们不可用。也就是说,如果它是由在该领域有更多经验的人不那么匆忙写的,那就不会那么恶心了。所有重要的解决方案都以某种方式令人作呕,我们的工作是了解权衡并应对后果。
【解决方案7】:

您需要按照说明使用代理,但它最终可以集成到一个类中构造函数

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

用“这个”。然后 set 和 get(也是 deleteProperty)函数将被触发。尽管您得到了一个看起来不同的代理对象,但它在大多数情况下可以询问比较( target.constructor === MyClass )它是类类型等[即使它是一个函数,其中 target.constructor.name 是类名文本(只是注意一个工作方式略有不同的例子。)]

【讨论】:

  • 这适用于重载 Backbone 集合上的 [],以便在使用 [] 时返回单个模型对象,并传递所有其他属性。
【解决方案8】:

我们可以代理get | set 方法直接。灵感来自this

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]

【讨论】:

    【解决方案9】:

    所以你希望做类似的事情 var 随便 = MyClassInstance[4]; ? 如果是这样,简单的答案是 Javascript 目前不支持运算符重载。

    【讨论】:

    • 那么 jQuery 是如何工作的。您可以调用 jQuery 对象上的方法,例如 $('.foo').html() 或获取第一个匹配的 dom 元素,例如 $('.foo')[0]
    • jQuery 是一个函数,您将参数传递给 $ 函数。因此是 () 括号,而不是 []
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 2021-08-01
    相关资源
    最近更新 更多