2010-07-16 23 views
31

Có thể phân lớp và kế thừa từ các mảng javascript không?Mảng Javascript phân lớp phụ. TypeError: Array.prototype.toString không phải là chung

Tôi muốn có đối tượng mảng tùy chỉnh của riêng mình có tất cả các tính năng của một mảng, nhưng chứa các thuộc tính bổ sung. Tôi muốn sử dụng myobj instanceof CustomArray để thực hiện các hoạt động cụ thể nếu cá thể là CustomArray của tôi.

Sau khi cố gắng phân lớp và chạy vào một số vấn đề, tôi đã tìm thấy bài viết Dean Edwards cho biết làm điều này với đối tượng mảng không hoạt động đúng. Nó chỉ ra Internet Explorer không xử lý nó đúng cách. Nhưng tôi cũng đang tìm các vấn đề khác (chỉ được thử nghiệm trong Chrome cho đến nay).

Dưới đây là một số mẫu mã:

/** 
* Inherit the prototype methods from one constructor into another 
* Borrowed from Google Closure Library 
*/ 
function inherits(childCtor, parentCtor) { 
    function tempCtor() {}; 
    tempCtor.prototype = parentCtor.prototype; 
    childCtor.superClass_ = parentCtor.prototype; 
    childCtor.prototype = new tempCtor(); 
    childCtor.prototype.constructor = childCtor; 
}, 

// Custom class that extends Array class 
function CustomArray() { 
    Array.apply(this, arguments); 
} 
inherits(CustomArray,Array); 

array = new Array(1,2,3); 
custom = new CustomArray(1,2,3); 

Bước vào sau trong giao diện điều khiển của Chrome cho sản lượng này:

> custom 
[] 
> array 
[1, 2, 3] 
> custom.toString() 
TypeError: Array.prototype.toString is not generic 
> array.toString() 
"1,2,3" 
> custom.slice(1) 
[] 
> array.slice(1) 
[2, 3] 
> custom.push(1) 
1 
> custom.toString() 
TypeError: Array.prototype.toString is not generic 
> custom 
[1] 

Rõ ràng, các đối tượng không cư xử như vậy. Tôi có nên từ bỏ phương pháp này hay không, hoặc có cách nào để hoàn thành mục tiêu của tôi là myobj instanceof CustomArray?

+0

đang tạo trình bao bọc một giải pháp có thể chấp nhận? Vấn đề ở trên là 'Array.apply' đang trả về một đối tượng mới, bỏ qua ngữ cảnh' this' này. – Anurag

+0

@Anurag: Theo trình bao bọc, bạn có muốn thực hiện một việc như thế này không? Http: //perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/#wrappers_prototype_chain_injection Hoặc bạn có có cái gì khác trong tâm trí? – Tauren

Trả lời

31

Juriy Zaytsev (@kangax) vừa phát hành một bài viết rất hay về chủ đề này.

Anh ấy khám phá các giải pháp thay thế khác nhau như Dean Edwards iframe mượn kỹ thuật, tiện ích mở rộng đối tượng trực tiếp, tiện ích mở rộng mẫu và cách sử dụng thuộc tính accessor ECMAScript 5.

Cuối cùng không có triển khai hoàn hảo, mỗi người đều có những lợi ích và hạn chế riêng.

Chắc chắn đọc thực sự tốt:

+1

Bài viết tuyệt vời và thời gian hoàn hảo! Cảm ơn. – Tauren

+0

Ngay cả bài viết khó khăn cũng thực sự tuyệt vời, chúng ta nên tránh trả lời chỉ bằng một liên kết. @laggingreflex trả lời đã làm công việc đưa câu trả lời từ bài viết để StackOverflow. –

1

Tôi đã thử làm điều này trước đây; nói chung, nó chỉ không xảy ra. Tuy nhiên, bạn có thể giả mạo bằng cách áp dụng các phương thức Array.prototype nội bộ. Lớp này CustomArray, mặc dù chỉ được thử nghiệm trong Chrome, thực hiện cả tiêu chuẩn push và phương thức tùy chỉnh last. (Bằng cách nào đó phương pháp này không bao giờ thực sự xảy ra với tôi lúc xD)

function CustomArray() { 
    this.push = function() { 
     Array.prototype.push.apply(this, arguments); 
    } 
    this.last = function() { 
     return this[this.length - 1]; 
    } 
    this.push.apply(this, arguments); // implement "new CustomArray(1,2,3)" 
} 
a = new CustomArray(1,2,3); 
alert(a.last()); // 3 
a.push(4); 
alert(a.last()); // 4 

Bất kỳ phương pháp Mảng bạn dự định để kéo vào thực hiện tùy chỉnh của bạn sẽ phải được thực hiện bằng tay, mặc dù bạn có thể có thể chỉ là thông minh và sử dụng các vòng lặp, kể từ khi những gì xảy ra bên trong tùy chỉnh của chúng tôi push là khá chung chung.

+0

Cảm ơn, nhưng giải pháp này không thực sự tạo ra một đối tượng thực hiện như một mảng. Bạn có thể thêm các phương thức vào nó, giống như bạn đã làm với 'push', nhưng nó không xử lý thao tác chỉ mục trực tiếp. Ví dụ, làm 'a [5] = 6' sẽ không thay đổi độ dài giống như trong một mảng thực.Bài viết được liên kết trong câu trả lời @CMS đi qua tất cả các giải pháp có thể và chỉ ra các lỗi. – Tauren

+0

@Tauren - aha, tôi biết có điều gì đó rõ ràng tôi đã mất tích :) Bài viết gọn gàng - xấu hổ tôi đã không tìm thấy nó trước đây! – Matchu

0

Thanh toán này. Nó hoạt động như trong tất cả các trình duyệt hỗ trợ '__proto__'.

var getPrototypeOf = Object.getPrototypeOf || function(o){ 
    return o.__proto__; 
}; 
var setPrototypeOf = Object.setPrototypeOf || function(o, p){ 
    o.__proto__ = p; 
    return o; 
}; 

var CustomArray = function CustomArray() { 
    var array; 
    var isNew = this instanceof CustomArray; 
    var proto = isNew ? getPrototypeOf(this) : CustomArray.prototype; 
    switch (arguments.length) { 
     case 0: array = []; break; 
     case 1: array = isNew ? new Array(arguments[0]) : Array(arguments[0]); break; 
     case 2: array = [arguments[0], arguments[1]]; break; 
     case 3: array = [arguments[0], arguments[1], arguments[2]]; break; 
     default: array = new (Array.bind.apply(Array, [null].concat([].slice.call(arguments)))); 
    } 
    return setPrototypeOf(array, proto); 
}; 

CustomArray.prototype = Object.create(Array.prototype, { constructor: { value: CustomArray } }); 
CustomArray.prototype.append = function(var_args) { 
    var_args = this.concat.apply([], arguments);   
    this.push.apply(this, var_args); 

    return this; 
}; 
CustomArray.prototype.prepend = function(var_args) { 
    var_args = this.concat.apply([], arguments); 
    this.unshift.apply(this, var_args); 

    return this; 
}; 
["concat", "reverse", "slice", "splice", "sort", "filter", "map"].forEach(function(name) { 
    var _Array_func = this[name]; 
    CustomArray.prototype[name] = function() { 
     var result = _Array_func.apply(this, arguments); 
     return setPrototypeOf(result, getPrototypeOf(this)); 
    } 
}, Array.prototype); 

var array = new CustomArray(1, 2, 3); 
console.log(array.length, array[2]);//3, 3 
array.length = 2; 
console.log(array.length, array[2]);//2, undefined 
array[9] = 'qwe'; 
console.log(array.length, array[9]);//10, 'qwe' 
console.log(array+"", array instanceof Array, array instanceof CustomArray);//'1,2,,,,,,,,qwe', true, true 

array.append(4); 
console.log(array.join(""), array.length);//'12qwe4', 11 
+0

jsperf cho CustomArray này tại đây: 1. [ghi/đọc theo chỉ mục] (http://jsperf.com/custom-array-vs-array/2) 2. [for, forEach, map] (http://jsperf.com/sdngjkn/12) – termi

0

Tôi đã tạo mô-đun NPM đơn giản để giải quyết vấn đề này - inherit-array.Về cơ bản nó thực hiện như sau:

function toArraySubClassFactory(ArraySubClass) { 
    ArraySubClass.prototype = Object.assign(Object.create(Array.prototype), 
              ArraySubClass.prototype); 

    return function() { 
    var arr = [ ]; 
    arr.__proto__ = ArraySubClass.prototype; 

    ArraySubClass.apply(arr, arguments); 

    return arr; 
    }; 
}; 

Sau khi viết lớp SubArray riêng của bạn, bạn có thể làm cho nó kế thừa mảng như sau:

var SubArrayFactory = toArraySubClassFactory(SubArray); 

var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/) 
17

ES6

class SubArray extends Array { 
    constructor(...args) { 
     super(...args); 
    } 
    last() { 
     return this[this.length - 1]; 
    } 
} 
var sub = new SubArray(1, 2, 3); 
sub // [1, 2, 3] 
sub instanceof SubArray; // true 
sub instanceof Array; // true 

gốc trả lời: (Không được đề xuất, có thể gây ra performance issues)

Copy-dán từ article đề cập trong câu trả lời chấp nhận cho khả năng hiển thị

Sử dụng __proto__

function SubArray() { 
    var arr = [ ]; 
    arr.push.apply(arr, arguments); 
    arr.__proto__ = SubArray.prototype; 
    return arr; 
} 
SubArray.prototype = new Array; 

Bây giờ bạn có thể thêm các phương pháp của bạn để SubArray

SubArray.prototype.last = function() { 
    return this[this.length - 1]; 
}; 

Initialize như Mảng bình thường

var sub = new SubArray(1, 2, 3); 

Hoạt động giống như các mảng thông thường

sub instanceof SubArray; // true 
sub instanceof Array; // true 
+0

Điều này làm việc hoàn hảo và có ý nghĩa hình thành một điểm đứng mới bắt đầu. – antihero989

+0

nhưng Array.isArray có trả lại đúng không? (Tôi đã kiểm tra, và nó có) – Sam

+0

giải pháp tốt đẹp, tôi chỉ sửa đổi một phần. Thay vì xác định nguyên mẫu bên ngoài, bạn có thể làm điều đó bên trong hàm tạo với 'this.prototype = newArray' Không chắc chắn nếu thực thi xấu của nó mặc dù – yosefrow

0

Dưới đây là ví dụ đầy đủ sẽ hoạt động trên ie9 trở lên. Đối với < = ie8 bạn phải thực hiện giải pháp thay thế để Array.from, Array.isArray vv Ví dụ này:

  • Đặt Array phân lớp trong việc đóng cửa của riêng mình (hoặc Vùng tên) để tránh xung đột và ô nhiễm không gian tên.
  • Thừa kế tất cả các nguyên mẫu và thuộc tính từ lớp Array gốc.
  • Cho biết cách xác định thuộc tính bổ sung và phương thức thử nghiệm.

Nếu bạn có thể sử dụng ES6, bạn nên sử dụng phương pháp trả lại trễ phương pháp class SubArray extends Array.

Đây là yếu tố cần thiết để phân lớp và kế thừa từ Mảng. Dưới đây trích đoạn này là ví dụ đầy đủ.

///Collections functions as a namespace.  
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.  
var Collections = (function (_NativeArray) { 
    //__proto__ is deprecated but Object.xxxPrototypeOf isn't as widely supported. ' 
    var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; }); 
    var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });   

    function Array() {   
     var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();   
     setProtoOf(arr, getProtoOf(this));  
     return arr; 
    } 

    Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } }); 
    Array.from = _NativeArray.from; 
    Array.of = _NativeArray.of; 
    Array.isArray = _NativeArray.isArray; 

    return { //Methods to expose externally. 
     Array: Array 
    }; 
})(Array); 

Full dụ:

///Collections functions as a namespace.  
///_NativeArray to prevent naming conflicts. All references to Array in this closure are to the Array function declared inside.  
var Collections = (function (_NativeArray) { 
    //__proto__ is deprecated but Object.xxxPrototypeOf isn't as widely supported. ' 
    var setProtoOf = (Object.setPrototypeOf || function (ob, proto) { ob.__proto__ = proto; return ob; }); 
    var getProtoOf = (Object.getPrototypeOf || function (ob) { return ob.__proto__; });   

    function Array() {   
     var arr = new (Function.prototype.bind.apply(_NativeArray, [null].concat([].slice.call(arguments))))();   
     setProtoOf(arr, getProtoOf(this));//For any prototypes defined on this subclass such as 'last'    
     return arr; 
    } 

    //Restores inherited prototypes of 'arr' that were wiped out by 'setProtoOf(arr, getProtoOf(this))' as well as add static functions.  
    Array.prototype = Object.create(_NativeArray.prototype, { constructor: { value: Array } }); 
    Array.from = _NativeArray.from; 
    Array.of = _NativeArray.of; 
    Array.isArray = _NativeArray.isArray; 

    //Add some convenient properties. 
    Object.defineProperty(Array.prototype, "count", { get: function() { return this.length - 1; } }); 
    Object.defineProperty(Array.prototype, "last", { get: function() { return this[this.count]; }, set: function (value) { return this[this.count] = value; } }); 

    //Add some convenient Methods.   
    Array.prototype.insert = function (idx) { 
     this.splice.apply(this, [idx, 0].concat(Array.prototype.slice.call(arguments, 1))); 
     return this; 
    }; 
    Array.prototype.insertArr = function (idx) { 
     idx = Math.min(idx, this.length); 
     arguments.length > 1 && this.splice.apply(this, [idx, 0].concat([].pop.call(arguments))) && this.insert.apply(this, arguments); 
     return this; 
    }; 
    Array.prototype.removeAt = function (idx) { 
     var args = Array.from(arguments); 
     for (var i = 0; i < args.length; i++) { this.splice(+args[i], 1); } 
     return this; 
    }; 
    Array.prototype.remove = function (items) { 
     var args = Array.from(arguments); 
     for (var i = 0; i < args.length; i++) { 
      var idx = this.indexOf(args[i]); 
      while (idx !== -1) { 
       this.splice(idx, 1); 
       idx = this.indexOf(args[i]); 
      } 
     } 
     return this; 
    }; 

    return { //Methods to expose externally. 
     Array: Array 
    }; 
})(Array); 

Dưới đây là một số ví dụ sử dụng và kiểm tra.

var colarr = new Collections.Array("foo", "bar", "baz", "lorem", "ipsum", "lol", "cat"); 
var colfrom = Collections.Array.from(colarr.reverse().concat(["yo", "bro", "dog", "rofl", "heyyyy", "pepe"])); 
var colmoded = Collections.Array.from(colfrom).insertArr(0, ["tryin", "it", "out"]).insert(0, "Just").insert(4, "seems", 2, "work.").remove('cat','baz','ipsum','lorem','bar','foo'); 

colmoded; //["Just", "tryin", "it", "out", "seems", 2, "work.", "lol", "yo", "bro", "dog", "rofl", "heyyyy", "pepe"] 

colmoded instanceof Array; //true 
Các vấn đề liên quan