Làm thế nào bạn sẽ nạp chồng toán tử [] trong javascript


85

Tôi dường như không thể tìm ra cách nạp chồng toán tử [] trong javascript. Có ai biết không?

Tôi đang suy nghĩ về ...

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

hay là tôi không nhìn vào những điều đúng đắn.


3
Những câu trả lời ở đây là sai, Mảng trong JS chỉ là đối tượng có phím coercable để uint32 (- 1) giá trị và có phương thức bổ sung trên nguyên mẫu của họ
Benjamin Gruenbaum

Chỉ cần biến MyClassđối tượng của bạn thành một mảng. Bạn có thể sao chép các khóa và giá trị từ đối tượng myArraycủa mình var myObj = new MyClass().
jpaugh

này, tôi muốn quá tải toán tử {}, có ý kiến ​​gì không?
daniel assayag

Câu trả lời:


81

Bạn không thể quá tải các toán tử trong JavaScript.

Nó đã được đề xuất cho ECMAScript 4 nhưng bị từ chối.

Tôi không nghĩ bạn sẽ sớm thấy nó.


4
Điều này có thể làm được với proxy trong một số trình duyệt và sẽ đến với tất cả các trình duyệt vào một thời điểm nào đó. Xem github.com/DavidBruant/ProxyArray
Tristan

Sau đó, làm thế nào để jQuery trả về những thứ khác nhau phụ thuộc vào việc bạn sử dụng [] hay .eq ()? stackoverflow.com/a/6993901/829305
Rikki,

2
Bạn có thể làm điều đó ngay bây giờ với một Proxy.
Eyal

mặc dù bạn có thể xác định các phương thức với các ký hiệu trong đó miễn là bạn truy cập chúng dưới dạng một mảng thay vì bằng ".". Đó là cách Smalltalk bản đồ những thứ như Object arg1: a arg2: b arg3: cnhư Object["arg1:arg2:arg3:"](a,b,c). Vì vậy, bạn có thể có myObject["[]"](1024): P
Dmitry

Liên kết đã chết :(
Gp2mv3

69

Bạn có thể làm điều này với ES6 Proxy (có sẵn trong tất cả các trình duyệt hiện đại)

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

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

Kiểm tra chi tiết trên MDN .


2
Làm thế nào chúng ta sẽ sử dụng điều này để tạo lớp của riêng chúng ta với một trình truy cập chỉ mục? tức là tôi muốn sử dụng phương thức khởi tạo của riêng mình, tôi không muốn tạo một Proxy.
mpen

1
Đây không phải là quá tải đúng. Thay vì gọi các phương thức trên chính đối tượng, bây giờ bạn đang gọi các phương thức của proxy.
Pacerier

@Pacerier bạn có thể trả lại target[name]trong getter, OP chỉ hiển thị các ví dụ
Valen

1
Hoạt động với []nhà điều hành cũng vậy, btw:var key = 'world'; console.log(proxy[key]);
Klesun

15

Câu trả lời đơn giản là JavaScript cho phép truy cập vào phần con của một Đối tượng thông qua dấu ngoặc vuông.

Vì vậy, bạn có thể xác định lớp của mình:

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;
};

Sau đó, bạn sẽ có thể truy cập các thành viên trên bất kỳ trường hợp nào trong lớp của bạn bằng một trong hai cú pháp.

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
Tôi chắc chắn sẽ không đề xuất điều này vì lý do hiệu suất, nhưng đó là giải pháp thực tế duy nhất cho vấn đề ở đây. Có lẽ một bản chỉnh sửa nói rằng không thể thực hiện được sẽ khiến câu trả lời này trở thành một câu trả lời tuyệt vời.
Milimetric

4
Đây không phải là những gì câu hỏi muốn. Câu hỏi là yêu cầu một cách để nắm bắt foo['random']điều mà mã của bạn không thể làm được.
Pacerier

9

Sử dụng proxy. Nó đã được đề cập ở những nơi khác trong câu trả lời nhưng tôi nghĩ rằng đây là một ví dụ tốt hơn:

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.

Có lẽ điều đáng nói là proxy là một tính năng của ES6 và do đó hỗ trợ trình duyệt hạn chế hơn (và Babel cũng không thể giả mạo chúng ).
jdehesa

8

Vì toán tử dấu ngoặc thực sự là toán tử truy cập thuộc tính, bạn có thể kết nối nó với getters và setters. Đối với IE, bạn sẽ phải sử dụng Object.defineProperty () để thay thế. Thí dụ:

var obj = {
    get attr() { alert("Getter called!"); return 1; },
    set attr(value) { alert("Setter called!"); return value; }
};

obj.attr = 123;

Tương tự cho IE8 +:

Object.defineProperty("attr", {
    get: function() { alert("Getter called!"); return 1; },
    set: function(value) { alert("Setter called!"); return value; }
});

Đối với IE5-7, onpropertychangechỉ có sự kiện, hoạt động cho các phần tử DOM, nhưng không hoạt động cho các đối tượng khác.

Hạn chế của phương pháp này là bạn chỉ có thể kết nối các yêu cầu đối với tập thuộc tính được xác định trước, không phải thuộc tính tùy ý mà không có bất kỳ tên được xác định trước nào.


Bạn có thể vui lòng giới thiệu cách tiếp cận của mình trên jsfiddle.net không? Tôi giả sử rằng giải pháp sẽ hoạt động cho bất kỳ khóa nào trong biểu thức obj['any_key'] = 123;nhưng những gì tôi thấy trong mã của bạn, tôi cần xác định setter / getter cho bất kỳ khóa nào (chưa được biết đến). Đó là điều không thể.
dma_k

3
cộng 1 để bù trừ 1 vì đây không phải chỉ IE.
orb

Điều này có thể được thực hiện cho một hàm lớp không? Tôi đang đấu tranh để tự mình tìm ra cú pháp cho điều đó.
Michael Hoffmann

7

Bạn cần phải sử dụng Proxy như được giải thích, nhưng nó cuối cùng có thể được tích hợp vào một lớp học xây dựng

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

Với cái này'. Sau đó, các chức năng set và get (cũng deleteProperty) sẽ kích hoạt. Mặc dù bạn nhận được một đối tượng Proxy có vẻ khác nhưng nó hoạt động để yêu cầu so sánh (target.constructor === MyClass) đó là loại lớp, v.v. [mặc dù đó là một hàm trong đó target.constructor.name là tên lớp trong văn bản (chỉ cần lưu ý một ví dụ về những thứ hoạt động hơi khác một chút.)]


1
Điều này hoạt động tốt khi nạp chồng [] trên bộ sưu tập Backbone để các đối tượng mô hình riêng lẻ được trả về khi sử dụng [] với một chuyển tiếp cho tất cả các thuộc tính khác.
justkt

5

Vì vậy, bạn đang hy vọng làm một cái gì đó như var anything = MyClassInstance [4]; ? Nếu vậy, câu trả lời đơn giản là Javascript hiện không hỗ trợ nạp chồng toán tử.


1
Vậy jQuery hoạt động như thế nào. Bạn có thể gọi một phương thức trên đối tượng jQuery như $ html () hoặc được những yếu tố phù hợp dom đầu tiên như $ [0] ( 'foo. '). (' Foo.')
kagronick

1
jQuery là một hàm, bạn đang truyền một tham số cho hàm $. Do đó dấu ngoặc (), không phải []
James Westgate

5

một cách lén lút để làm điều này là mở rộng ngôn ngữ của chính nó.

bước 1

xác định quy ước lập chỉ mục tùy chỉnh, hãy gọi nó là "[]".

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

bước 2

xác định một triển khai eval mới. (không làm theo cách này, nhưng đó là một bằng chứng về khái niệm).

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]");

ở trên sẽ không hoạt động đối với các chỉ mục phức tạp hơn nhưng nó có thể được phân tích cú pháp mạnh hơn.

thay thế:

thay vì dùng đến việc tạo ngôn ngữ superset của riêng bạn, bạn có thể biên dịch ký hiệu của mình sang ngôn ngữ hiện có, sau đó đánh giá nó. Điều này làm giảm chi phí phân tích cú pháp thành gốc sau lần đầu tiên bạn sử dụng nó.

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
Bạn bị cong +1
Jus

hoàn toàn ghê tởm. tôi thích nó.
SliceThePi,

Sự khôn ngoan thường là theo cách này hay cách khác. Không có nghĩa đó không phải là một bài tập đáng giá có thể mang lại hiệu quả với đủ nguồn lực. Những người lười nhất là những người viết các trình biên dịch và dịch thuật của riêng họ để họ có thể làm việc trong các môi trường quen thuộc hơn, ngay cả khi họ không có sẵn. Điều đó nói rằng, sẽ đỡ ghê tởm hơn nếu nó được viết một cách ít vội vàng bởi một người có kinh nghiệm hơn trong lĩnh vực này. Tất cả các giải pháp tầm thường đều đáng kinh tởm theo cách này hay cách khác, công việc của chúng ta là biết đánh đổi và đối phó với hậu quả.
Dmitry

3

Chúng tôi có thể nhận proxy | thiết lập các phương thức trực tiếp. Lấy cảm hứng từ điều này .

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]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.