如何在JavaScript中扩展数组对象

47

我想在JavaScript中扩展Array对象,添加一些用户友好的方法,例如使用Array.Add()代替Array.push()等。

我实现了三种方法来做到这一点。不幸的是,第三种方法没有起作用,我想问为什么?以及如何让它工作。

//------------- 1st way
Array.prototype.Add=function(element){
     this.push(element);
};

var list1 = new Array();
list1.Add("Hello world");
alert(list1[0]);

//------------- 2nd way
function Array2 () {
    //some other properties and methods
};

Array2.prototype = new Array;
Array2.prototype.Add = function(element){
  this.push(element);  
};

var list2 = new Array2;
list2.Add(123);
alert(list2[0]);

//------------- 3rd way
function Array3 () {
    this.prototype = new Array;
    this.Add = function(element){
      this.push(element);  
    };
};

var list3 = new Array3;
list3.Add(456);  //push is not a function
alert(list3[0]); // undefined

在第三种方法中,我想在Array3类内部扩展Array对象。 如何做到这一点,以免出现“push不是一个函数”和“undefined”?

这里我添加了第四种方法。

//------------- 4th way
function Array4 () {
    //some other properties and methods
    this.Add = function(element){
        this.push(element);
    };
 };
Array4.prototype = new Array();

var list4 = new Array4();
list4.Add(789);
alert(list4[0]);

我又不得不使用 prototype 了。 我希望避免在类构造函数之外使用额外的行,如 Array4.prototype。 我想要一个紧凑定义的类,所有部分都在一个地方。 但我认为我别无选择。


如果您向数组添加一个方法,将会破坏对数组的 foreach() 循环。 - SpacedMonkey
你有研究过 CoffeeScript 吗?我会在我的回答中更新一个例子。 - SMathew
我不会像第一个例子那样向Array.prototype添加方法。那只是一个测试。我将创建一个类来扩展Array对象,例如jsArray。jsArray对象将是数组,但具有更多功能。 - demosthenes
我今天看到了 CoffeeScript。我不喜欢它的语法。 - demosthenes
如果有人使用我的自定义js库,可以调整它的foreach()方法,不包含枚举的最后两个元素,因为那是对象的类型和长度。 - demosthenes
在你的所有示例中,你都将Array扩展为一个新类型(例如Array3)。有没有理由不像OP的第一个示例那样简单地扩展Array?Array.prototype.add = Array.prototype.push; - trusktr
8个回答

39

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

1
虽然大多数JavaScript实现都支持使用__proto__,但它直到被标记为es6的遗留特性之后才成为标准功能。此外,如果过度使用,它可能会导致性能问题。通常情况下,使用它是一个不好的主意。 - Insomniac
@TBotV63 同意。在 ES6 中,你可以简单地扩展一个数组类。我已经更新了我的答案。 - laggingreflex
2
你应该将constructor中的值传递给super,像这样:constructor(...items) {super(...items) } - Kevin Beal
那不是问题,我只是不想迭代新属性。 - shivshankar
为了添加一个非可迭代属性,请使用以下方式:Object.defineProperty(this, 'weight', {writable:true, value:1234}),而不是在第一次设置时(即在构造函数中)使用 this.weight = 1234 - 12Me21
显示剩余3条评论

32

方法名称应该使用小写字母。在构造函数中不应修改原型。

function Array3() { };
Array3.prototype = new Array;
Array3.prototype.add = Array3.prototype.push

在 CoffeeScript 中

class Array3 extends Array
   add: (item)->
     @push(item) 

如果您不喜欢那种语法,并且必须在构造函数内部扩展它, 您唯一的选择是:

// define this once somewhere
// you can also change this to accept multiple arguments 
function extend(x, y){
    for(var key in y) {
        if (y.hasOwnProperty(key)) {
            x[key] = y[key];
        }
    }
    return x;
}


function Array3() { 
   extend(this, Array.prototype);
   extend(this, {
      Add: function(item) {
        return this.push(item)
      }

   });
};

你也可以这样做

ArrayExtenstions = {
   Add: function() {

   }
}
extend(ArrayExtenstions, Array.prototype);



function Array3() { }
Array3.prototype = ArrayExtenstions;

在旧时代,'prototype.js' 曾经有一个 Class.create 方法。你可以将所有内容包装在这样的方法中。

var Array3 = Class.create(Array, {
    construct: function() {

    },    
    Add: function() {

    }
});

想要了解更多相关信息和如何实现,请查看prototype.js源代码


是的,这是第二种方法。我故意使用Add而不是add,因为我打算编写一些扩展方法,如Visual Basic外观。 - demosthenes
好的,我无法在构造函数内使用原型。有没有办法在构造函数内扩展Array对象? - demosthenes
1
是的,但如果您必须实例化大量对象,则会很昂贵。请参见http://api.jquery.com/jQuery.extend/以获取示例实现。使用该技术,您可以编写以下函数Array3(){extend(this,{add:function(item){return this.push(item)}})}; - SMathew
1
哦,你还可以使用'Object.defineProperty'来设置getter和setter。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty - SMathew
它的语法对我来说是新的东西。我感觉JavaScript更加熟悉。 - demosthenes
在第一个示例中,为什么是 Array3.prototype = new Array; 而不是带括号的 new Array();?谢谢。 - pilau

4

前段时间我读了由jQuery的创始人约翰·雷西格(John Resig)所写的书籍《Javascript忍者》。他提出了一种用普通JS对象来模仿类数组方法的方法,基本上只需要length属性。

var obj = {
    length: 0, //only length is required to mimic an Array
    add: function(elem){
        Array.prototype.push.call(this, elem);
    },
    filter: function(callback) {
        return Array.prototype.filter.call(this, callback); //or provide your own implemetation
    }
};

obj.add('a');
obj.add('b');
console.log(obj.length); //2
console.log(obj[0], obj[1]); //'a', 'b'

我并不是说这种方法好还是不好。这是一种原始的 Array 操作方式。它的好处在于你不需要扩展 Array prototype
请记住,obj 是一个普通的 object,而不是一个 Array。因此,obj instanceof Array 将返回 false。把 obj 看作一个外观(façade)。
如果你对该代码感兴趣,请阅读摘录 列表 4.10 模拟类数组方法

3
你也可以在ES6中使用以下方式:

你也可以在ES6中使用以下方式:

Object.assign(Array.prototype, {
    unique() {
      return this.filter((value, index, array) => {
        return array.indexOf(value) === index;
      });
    }
});

结果:

let x = [0,1,2,3,2,3];
let y = x.unique();
console.log(y); // => [0,1,2,3]

3
这会扩展数组原型,不应该这样做(因为会影响整个网站)。 - fregante
2
除非您想影响整个网站,否则这是很好的。 - Goddard

2
在你的第三个例子中,你只是为对象Array3创建了一个名为prototype的新属性。当你执行new Array3(应该是new Array3())时,你将该对象实例化到变量list3中。因此,Add方法不起作用,因为this,也就是所讨论的对象,没有一个有效的push方法。希望你能理解。
编辑:查看了解JavaScript上下文,了解更多关于this的知识。

我明白了,这个 this.prototype 被视为 Array3 的属性,谢谢。 - demosthenes
1
还有不要忘记在实例化对象时添加 ()Array 也是一个对象,因此使用 new Array()(或者通常只用 [])来实例化。这样做是因为浏览器很聪明。 - elclanrs

0
你是否在尝试做比只是为 "push" 添加一个名为 "Add" 的别名更复杂的事情?
如果不是,最好避免这样做。我建议不要这样做的原因是Array是内置的javascript类型,修改它会导致所有脚本Array类型都具有您的新 "Add" 方法。与第三方名称冲突的潜力很高,并且可能会导致第三方脚本放弃其方法而选择您的方法。
我的一般规则是,如果不存在帮助函数来处理数组,则仅在极其必要时才扩展数组。

目前我正在学习一些关于JS的新知识。我想编写一些扩展方法,使其看起来像Visual Basic核心函数。这对我在编写Web项目时更加适合和容易。 - demosthenes
听起来是一个很好的练习!正是你需要学习更多JS的东西。 - elclanrs
太棒了,祝你好运。我只是不希望你遇到一种常见的错误,即扩展内置类型并破坏第三方扩展,然后花费数小时尝试弄清楚为什么库无法正常工作(我以前曾经这样做过 :S )。 - duyker
不,我不会使用任何其他第三方API。它将是独立的。 - demosthenes

0

在JavaScript中,您无法扩展Array对象。

相反,您可以定义一个包含一系列操作数组的函数的对象,并将这些函数注入到该数组实例中并返回这个新的数组实例。您不应该更改Array.prototype以包含您自定义函数的列表。

例如:

function MyArray() {
  var tmp_array = Object.create(Array.prototype);
  tmp_array = (Array.apply(tmp_array, arguments) || tmp_array);
  //Now extend tmp_array
  for( var meth in MyArray.prototype )
    if(MyArray.prototype.hasOwnProperty(meth))
      tmp_array[meth] = MyArray.prototype[meth];
  return (tmp_array);
}
//Now define the prototype chain.
MyArray.prototype = {
  customFunction: function() { return "blah blah"; },
  customMetaData: "Blah Blah",
}

这只是一个示例代码,您可以根据自己的需要进行修改和使用。但我建议您遵循的基本概念仍然是相同的。


1
我认为将prototype赋值是一种不好的做法。更好的方法是赋值现有prototype对象的属性。 - jchook
1
你说你不能扩展数组对象,但承认向Array.prototype添加函数是可能的...你的回答对我来说似乎是矛盾的。 - Purefan
1
@Purefan - 这可能是矛盾的,但它有助于理解Javascript做了什么(以及没有做什么)-这对于来自JS世界之外的人来说是违反直觉的。 - Katinka Hesselink

0
var SubArray = function() {                                           
    var arrInst = new Array(...arguments); // spread arguments object
    /* Object.getPrototypeOf(arrInst) === Array.prototype */
    Object.setPrototypeOf(arrInst, SubArray.prototype);     //redirectionA
    return arrInst; // now instanceof SubArray
};

SubArray.prototype = {
    // SubArray.prototype.constructor = SubArray;
    constructor: SubArray,

    // methods avilable for all instances of SubArray
    add: function(element){return this.push(element);},
    ...
};

Object.setPrototypeOf(SubArray.prototype, Array.prototype); //redirectionB

var subArr = new SubArray(1, 2);
subArr.add(3); subArr[2]; // 3

答案是一个紧凑的解决方案,在所有支持的浏览器中都能按预期工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接