子类化 Javascript 数组。类型错误:Array.prototype.toString 不是通用的

IT技术 javascript inheritance
2021-03-04 13:40:38

是否可以从 javascript 数组子类化和继承?

我想拥有自己的自定义 Array 对象,该对象具有 Array 的所有功能,但包含其他属性。myobj instanceof CustomArray如果实例是我的 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

6个回答

Juriy Zaytsev ( @kangax ) 今天刚刚发布了一篇关于这个主题的非常好的文章。

他探索了各种替代方案,例如 Dean Edwards iframe 借用技术、直接对象扩展、原型扩展和 ECMAScript 5 访问器属性的使用。

最后没有完美的实现,每个实现都有自己的优点和缺点。

绝对是一本非常好的读物:

即使是强硬的文章确实很酷,我们应该避免只用一个链接来回答。@laggingreflex answer 完成了将文章中的答案带到 StackOverflow 的工作。
2021-05-01 13:40:38
伟大的文章和完美的时机!谢谢。
2021-05-07 13:40:38

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

原答案:(不推荐,可能会导致性能问题

已接受的答案中提到的文章复制粘贴以获得更多可见性

使用 __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
这非常有效,并且从初学者的角度来看是有意义的。
2021-04-30 13:40:38
不错的解决方案,我只修改了一部分。您可以在构造函数内部进行,而不是在外部定义原型,但this.prototype = newArray不确定是否是不好的做法
2021-05-05 13:40:38
函数this内的@yosefrow实际上是调用该函数的对象(new它只是一个空对象)并且对象没有prototype属性,它们的constructors 有。__proto__ 和prototype 之间的结帐差异)。此外,由于我们arr = []从 ( SubArray) 函数返回一个完全自构造的对象 ( ) ,因此修改this并不重要。
2021-05-09 13:40:38
好的,谢谢解释。我假设它this.prototype指的是返回的任何对象的原型。但现在我明白你的意思了,因为this根本没有被退回,这与你做什么无关this
2021-05-10 13:40:38
但是 Array.isArray 返回 true 吗?(我检查过,确实如此)
2021-05-12 13:40:38

我以前尝试过做这种事情;一般来说,它不会发生。但是,您可以通过在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非常通用。

@Tauren - 啊哈,我知道我遗漏了一些明显的东西:) 整洁的文章 - 可惜我之前没有找到它!
2021-04-27 13:40:38
谢谢,但是这个解决方案并没有真正创建一个像数组一样执行的对象。您可以向其中添加方法,就像使用 一样push,但它不处理直接索引操作。例如,这样做a[5]=6不会像在真正的数组中那样改变长度。@CMS 答案中链接的文章详细介绍了所有可能的解决方案并指出了缺陷。
2021-05-11 13:40:38

结帐这个。它在所有支持“ __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
jsperf 用于此 CustomArray: 1.按索引写入/读取2. for, forEach, map
2021-05-07 13:40:38

这是一个应该在 ie9 及更高版本上工作的完整示例。对于 <=ie8,您必须实现 Array.from、Array.isArray 等的替代方案。此示例:

  • 将 Array 子类放在它自己的闭包(或命名空间)中以避免冲突和命名空间污染。
  • 从本机 Array 类继承所有原型和属性。
  • 展示如何定义额外的属性和原型方法。

如果你可以使用 ES6,你应该使用class SubArray extends Arraylaggingreflex 发布方法。

这是从 Arrays 子类化和继承的基本要素。在此摘录下方是完整示例。

///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