子类化 Javascript 数组。 TypeError:Array.prototype.toString 不是通用的
Posted
技术标签:
【中文标题】子类化 Javascript 数组。 TypeError:Array.prototype.toString 不是通用的【英文标题】:Subclassing Javascript Arrays. TypeError: Array.prototype.toString is not generic 【发布时间】:2011-03-16 18:01:42 【问题描述】:是否可以从 javascript 数组继承和继承?
我想拥有自己的自定义 Array 对象,该对象具有 Array 的所有功能,但包含其他属性。如果实例是我的 CustomArray,我会使用 myobj instanceof CustomArray
执行特定操作。
在尝试子类化并遇到一些问题后,我发现这篇 Dean Edwards 文章表明对 Array 对象执行此操作无法正常工作。事实证明 Internet Explorer 无法正确处理它。但我也发现了其他问题(目前仅在 Chrome 中测试过)。
这里有一些示例代码:
/**
* 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);
在 Chrome 的控制台中输入以下内容会得到以下输出:
> 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]
显然,这些对象的行为不同。我应该放弃这种方法,还是有什么方法可以实现myobj instanceof CustomArray
的目标?
【问题讨论】:
创建包装器是一种可接受的解决方案吗?上面的问题是Array.apply
正在返回一个新对象,而忽略了this
上下文。
@Anurag: 包装器是指做这样的事情吗?perfectionkills.com/… 或者你还有别的想法吗?
【参考方案1】:
带有自定义构造函数的 ES6 最小可运行示例
如果您还想覆盖构造函数,则需要格外小心,因为某些方法将需要旧的构造函数。
使用How can I extend the Array class and keep its implementations 中提到的技术,我们可以达到:
#!/usr/bin/env node
const assert = require('assert');
class MyArray extends Array
constructor(nodes, myint)
super(...nodes);
this.myint = myint;
static get [Symbol.species]()
return Object.assign(function (...items)
return new MyArray(new Array(...items))
, MyArray);
inc() return this.myint + 1;
const my_array = new MyArray([2, 3, 5], 9);
assert(my_array[0] === 2);
assert(my_array[1] === 3);
assert(my_array[2] === 5);
assert(my_array.myint === 9);
assert(my_array.inc() === 10);
assert(my_array.toString() === '2,3,5');
my_slice = my_array.slice(1, 2);
assert(my_slice[0] === 3);
assert(my_slice.constructor === MyArray);
在没有Arrray
的情况下获取索引符号[]
已在以下位置询问:Implement Array-like behavior in JavaScript without using Array
在 Node.js v10.15.1 中测试。
【讨论】:
【参考方案2】:ES6
class SubArray extends Array
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
原答案:(不推荐,可能会导致performance issues)
从接受的答案中提到的article 复制粘贴以获得更多可见性
使用__proto__
function SubArray()
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
SubArray.prototype = new Array;
现在您可以将您的方法添加到SubArray
SubArray.prototype.last = function()
return this[this.length - 1];
;
像普通数组一样初始化
var sub = new SubArray(1, 2, 3);
表现得像普通数组
sub instanceof SubArray; // true
sub instanceof Array; // true
【讨论】:
这很有效,从初学者的角度来看很有意义。 但是 Array.isArray 返回 true 吗? (我检查过,确实如此) 不错的解决方案,我只修改一个部分。您可以在构造函数中使用this.prototype = newArray
而不是在外部定义原型,但不确定它是否是不好的做法
@yosefrow this
在函数内部实际上是调用函数的对象(new
只是一个空对象)并且对象没有 prototype
属性,他们的constructor
s 做。 (Checkout difference between __proto__ and prototype)。此外,由于我们从该 (SubArray
) 函数返回一个完全自构造的对象 (arr = []
),因此修改 this
无关紧要。
好的,感谢您的解释。我假设this.prototype
指的是返回的任何对象的原型。但现在我明白你的意思了,因为 this
根本没有被返回,所以你对 this
所做的事情无关紧要【参考方案3】:
这里有一个完整的例子,应该可以在 ie9 和更高版本上运行。对于
将 Array 子类置于其自己的闭包(或命名空间)中,以避免冲突和命名空间污染。 从原生 Array 类继承所有原型和属性。 展示了如何定义其他属性和原型方法。如果你可以使用 ES6,你应该使用 class SubArray extends Array
发布的方法 laggingreflex。
这是从数组继承和继承的要点。下面这段摘录是完整的例子。
///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);
完整示例:
///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);
这里有一些使用示例和测试。
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
【讨论】:
【参考方案4】:我创建了一个简单的 NPM 模块来解决这个问题 - inherit-array。它基本上做了以下工作:
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;
;
;
编写自己的SubArray
类后,可以使其继承Array,如下所示:
var SubArrayFactory = toArraySubClassFactory(SubArray);
var mySubArrayInstance = SubArrayFactory(/*whatever SubArray constructor takes*/)
【讨论】:
【参考方案5】:检查一下。它可以在所有支持 '__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
【讨论】:
这里 CustomArray 的 jsperf:1.write/read by index 2.for, forEach, map【参考方案6】:Juriy Zaytsev (@kangax) 就在今天发布了一篇关于这个主题的非常好的文章。
他探索了各种替代方案,例如 Dean Edwards iframe 借用技术、直接对象扩展、原型扩展以及 ECMAScript 5 访问器属性的使用。
最终没有完美的实现,每个都有自己的优点和缺点。
绝对是一本非常好的读物:
How ECMAScript 5 still does not allow to subclass an array【讨论】:
伟大的文章和完美的时机!谢谢。 即使文章很硬朗,确实很酷,我们应该避免只用链接来回答。 @laggingreflex answer 完成了将文章中的答案带到 *** 的工作。【参考方案7】:我以前尝试过做这种事情;一般来说,它只是不会发生。不过,您可以通过在内部应用 Array.prototype
方法来伪造它。这个CustomArray
类虽然只在 Chrome 中进行了测试,但同时实现了标准 push
和自定义方法 last
。 (不知何故,当时我从未真正想到过这种方法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
您打算引入自定义实现的任何 Array 方法都必须手动实现,尽管您可能很聪明并使用循环,因为在我们的自定义 push
中发生的事情非常通用。
【讨论】:
谢谢,但是这个解决方案并没有真正创建一个像数组一样执行的对象。您可以向其中添加方法,就像使用push
一样,但它不处理直接索引操作。例如,执行a[5]=6
不会像在真实数组中那样改变长度。 @CMS answer中链接的文章介绍了所有可能的解决方案并指出了缺陷。
@Tauren - 啊哈,我知道我遗漏了一些明显的东西 :) 整洁的文章 - 可惜我以前没有找到它!以上是关于子类化 Javascript 数组。 TypeError:Array.prototype.toString 不是通用的的主要内容,如果未能解决你的问题,请参考以下文章
子类化 UICollectionViewFlowLayout