คุณจะโอเวอร์โหลดโอเปอเรเตอร์ [] ในจาวาสคริปต์ได้อย่างไร


86

ฉันไม่สามารถหาวิธีในการโอเวอร์โหลดโอเปอเรเตอร์ [] ในจาวาสคริปต์ได้ มีใครรู้บ้าง?

ฉันกำลังคิดเกี่ยวกับ ...

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

หรือฉันไม่ได้มองสิ่งที่ถูกต้อง


3
คำตอบที่นี่ผิด Arrays ใน JS เป็นเพียงวัตถุที่มีคีย์บังคับให้ใช้ค่า uint32 (- 1) และมีวิธีการพิเศษในต้นแบบ
Benjamin Gruenbaum

เพียงแค่ทำให้MyClassวัตถุของคุณเป็นอาร์เรย์ คุณสามารถคัดลอกคีย์และค่าจากmyArrayไปยังvar myObj = new MyClass()วัตถุของคุณ
jpaugh

เดี๋ยวก่อนฉันต้องการตัวดำเนินการ {} มากเกินไปมีความคิดอย่างไร
daniel assayag

คำตอบ:


81

คุณไม่สามารถโอเวอร์โหลดโอเปอเรเตอร์ใน JavaScript ได้

ได้รับการเสนอสำหรับ ECMAScript 4แต่ถูกปฏิเสธ

ฉันไม่คิดว่าคุณจะได้เห็นมันเร็ว ๆ นี้


4
สิ่งนี้อาจทำได้กับพร็อกซีในบางเบราว์เซอร์แล้วและจะมาถึงเบราว์เซอร์ทั้งหมดในบางจุด ดูgithub.com/DavidBruant/ProxyArray
Tristan

แล้ว jQuery จะส่งคืนสิ่งที่แตกต่างกันอย่างไรขึ้นอยู่กับว่าคุณใช้ [] หรือ .eq ()? stackoverflow.com/a/6993901/829305
Rikki

2
คุณสามารถทำได้ทันทีด้วย Proxy
Eyal

แม้ว่าคุณจะสามารถกำหนดวิธีการด้วยสัญลักษณ์ได้ตราบใดที่คุณเข้าถึงเป็นอาร์เรย์แทนที่จะใช้ "." นั่นเป็นวิธีที่ SmallTalk แผนที่สิ่งที่ต้องการเป็นObject arg1: a arg2: b arg3: c Object["arg1:arg2:arg3:"](a,b,c)ดังนั้นคุณสามารถมีmyObject["[]"](1024): P
Dmitry

ลิงค์หมดแล้ว :(
Gp2mv3

71

คุณสามารถทำได้ด้วย 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
เราจะใช้สิ่งนี้เพื่อสร้างคลาสของเราเองด้วยตัวเข้าถึงดัชนีได้อย่างไร เช่นฉันต้องการใช้ตัวสร้างของตัวเองฉันไม่ต้องการสร้าง Proxy
mpen

1
นี่ไม่ใช่การบรรทุกเกินจริง แทนที่จะเรียกใช้เมธอดบนอ็อบเจ็กต์ตอนนี้คุณกำลังเรียกใช้เมธอดของพร็อกซี
Pacerier

@Pacerier คุณสามารถกลับมาได้target[name]ใน getter OP กำลังแสดงตัวอย่าง
Valen

1
ทำงานร่วมกับ[]ตัวดำเนินการได้เช่นกัน btw: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

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

เนื่องจากตัวดำเนินการวงเล็บเป็นตัวดำเนินการเข้าถึงคุณสมบัติคุณสามารถเชื่อมต่อกับ getters และ setters สำหรับ 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 เท่านั้น
โคจร

สามารถทำได้สำหรับฟังก์ชันคลาสหรือไม่? ฉันกำลังดิ้นรนเพื่อค้นหาไวยากรณ์สำหรับสิ่งนั้นด้วยตัวเอง
Michael Hoffmann

7

คุณต้องใช้ Proxy ตามที่อธิบายไว้ แต่ในที่สุดก็สามารถรวมเข้ากับตัวสร้างคลาสได้

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

ด้วยสิ่งนี้'. จากนั้นฟังก์ชัน set and get (รวมถึง deleteProperty) จะเริ่มทำงาน แม้ว่าคุณจะได้รับวัตถุ Proxy ซึ่งดูเหมือนว่าจะแตกต่างกัน แต่ส่วนใหญ่ทำงานเพื่อขอการเปรียบเทียบ (target.constructor === MyClass) เป็นประเภทคลาสเป็นต้น [แม้ว่าจะเป็นฟังก์ชันที่ target.constructor.name เป็นชื่อคลาสใน ข้อความ (เพียงแค่สังเกตตัวอย่างของสิ่งที่ทำงานแตกต่างกันเล็กน้อย)]


1
วิธีนี้ใช้ได้ดีกับการโอเวอร์โหลด [] บนคอลเลกชัน Backbone เพื่อให้อ็อบเจ็กต์แต่ละโมเดลถูกส่งคืนเมื่อใช้ [] พร้อมพาส - ทรูสำหรับคุณสมบัติอื่น
justkt

5

ดังนั้นคุณหวังว่าจะทำบางอย่างเช่น var อะไรก็ได้ = 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

กำหนดการใช้งาน 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));

3
คุณ
จัส

1
น่าขยะแขยงอย่างยิ่ง ฉันรักมัน.
SliceThePi

ความฉลาดมักจะไม่ทางใดก็ทางหนึ่ง ไม่ได้หมายความว่าไม่ใช่การออกกำลังกายที่คุ้มค่าที่สามารถใช้ทรัพยากรเพียงพอ คนที่ขี้เกียจที่สุดคือคนที่เขียนคอมไพเลอร์และนักแปลของตัวเองเพียงเพื่อให้พวกเขาสามารถทำงานในสภาพแวดล้อมที่คุ้นเคยมากขึ้นแม้ว่าจะไม่พร้อมใช้งานก็ตาม จะว่าไปแล้วมันจะน่าขยะแขยงน้อยลงถ้ามันถูกเขียนโดยคนที่มีประสบการณ์ในพื้นที่มากกว่าโดยไม่รีบร้อน การแก้ปัญหาที่ไม่สำคัญทั้งหมดเป็นสิ่งที่น่ารังเกียจไม่ทางใดก็ทางหนึ่งงานของเราคือการรู้ถึงการแลกเปลี่ยนและรับมือกับผลที่ตามมา
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]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.