【问题标题】:Is this a reasonable way to 'subclass' a javascript array?这是“子类化”javascript数组的合理方法吗?
【发布时间】:2011-01-21 16:12:27
【问题描述】:

我意识到,严格来说,这不是数组类型的子类化,但它会以人们期望的方式工作,还是我仍然会遇到 .length 等问题?如果可以选择正常的子类化,有什么我不会有的缺点吗?

        function Vector()
        {
            var vector = [];
            vector.sum = function()
            {
                sum = 0.0;
                for(i = 0; i < this.length; i++)
                {
                    sum += this[i];
                }
                return sum;
            }            
            return vector;
        }

        v = Vector();
        v.push(1); v.push(2);
        console.log(v.sum());

【问题讨论】:

  • 您打算实例化多少个这些 Vector 数组? 2?数百?
  • 你忘记了一个变量。 var vector = [];.

标签: javascript arrays subclass


【解决方案1】:

我会像这样在适当的向量类型中包装一个数组:

window.Vector = function Vector() {
  this.data = [];
}

Vector.prototype.push = function push() {
  Array.prototype.push.apply(this.data, arguments);
}

Vector.prototype.sum = function sum() {
  for(var i = 0, s=0.0, len=this.data.length; i < len; s += this.data[i++]);
  return s;
}

var vector1 = new Vector();
vector1.push(1); vector1.push(2);
console.log(vector1.sum());

或者,您可以在数组上构建新的原型函数,然后只使用普通数组。

如果您与命名数组一致,例如它们都以小写 v 开头,或者类似的东西清楚地标记它们为 aw 向量而不是普通数组,并且您对向量特定的原型函数执行相同操作,那么它应该相当容易跟踪。

Array.prototype.vSum = function vSum() {
  for(var i = 0, s=0.0, len=this.length; i < len; s += this[i++]);
  return s;
}

var vector1 = [];
vector1.push(1); vector1.push(2);
console.log(vector1.vSum());

【讨论】:

  • 看起来这对我最有效;谢谢。你得到饼干。 :)
  • 请记住,在 Array 上构建新的原型函数会将这些方法添加到所有创建的数组中。这可能不是您想要的,因此需要考虑。
  • 不幸的是,这并不能让您调用 vector1[0] 并返回 1。
【解决方案2】:

编辑——我最初写道,你可以像任何其他对象一样子类化一个数组,这是错误的。每天学些新东西。这是一个很好的讨论

http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

在这种情况下,合成会更好吗?即只需创建一个 Vector 对象,并让它由一个数组支持。这似乎是你正在走的路,你只需要将 push 和任何其他方法添加到原型中。

【讨论】:

【解决方案3】:

现在你可以使用 ES6 类的子类化:

    class Vector extends Array {
      sum(){
        return this.reduce((total, value) => total + value)
      }
    }
    
    let v2 = new Vector();
    v2.push(1);
    v2.push(2);
    console.log(v2.sum());
    console.log(v2.length);
    v2.length = 0;
    console.log(v2.length);
    console.log(v2);

【讨论】:

  • 试试 console.log(v2.length)
【解决方案4】:

只是包装器的另一个例子。使用 .bind 玩得开心。

var _Array = function _Array() {
    if ( !( this instanceof _Array ) ) {
        return new _Array();
    };
};

_Array.prototype.push = function() {
    var apContextBound = Array.prototype.push,
        pushItAgainst = Function.prototype.apply.bind( apContextBound );

    pushItAgainst( this, arguments );
};

_Array.prototype.pushPushItRealGood = function() {
    var apContextBound = Array.prototype.push,
        pushItAgainst = Function.prototype.apply.bind( apContextBound );

    pushItAgainst( this, arguments );
};

_Array.prototype.typeof = (function() { return ( Object.prototype.toString.call( [] ) ); }());

【讨论】:

    【解决方案5】:

    @hvgotcodes 的答案有一个awesome link。我只是想在这里总结一下结论。

    Wrappers. Prototype chain injection

    这似乎是从文章中扩展数组的最佳方法。

    可以使用包装器...在其中扩充对象的原型链,而不是对象本身。

    function SubArray() {
      var arr = [ ];
      arr.push.apply(arr, arguments);
      arr.__proto__ = SubArray.prototype;
      return arr;
    }
    SubArray.prototype = new Array;
    
    // Add custom functions here to SubArray.prototype.
    SubArray.prototype.last = function() {
      return this[this.length - 1];
    };
    
    var sub = new SubArray(1, 2, 3);
    
    sub instanceof SubArray; // true
    sub instanceof Array; // true
    

    不幸的是,这个方法使用了arr.__proto__,IE 8 不支持,我必须支持的浏览器。

    Wrappers. Direct property injection.

    这个方法比上面的稍微慢一点,但是在IE 8-下有效。

    包装器方法避免设置继承或模拟长度/索引关系。相反,类似工厂的函数可以创建一个普通的 Array 对象,然后直接使用任何自定义方法对其进行扩充。由于返回的对象是一个数组,它保持适当的长度/索引关系,以及“数组”的 [[Class]]。它自然也继承自 Array.prototype。

    function makeSubArray() {
      var arr = [ ];
      arr.push.apply(arr, arguments);
    
      // Add custom functions here to arr.
      arr.last = function() {
        return this[this.length - 1];
      };
      return arr;
    }
    
    var sub = makeSubArray(1, 2, 3);
    sub instanceof Array; // true
    
    sub.length; // 3
    sub.last(); // 3
    

    【讨论】:

      【解决方案6】:

      有一种方式看起来和感觉都像原型继承,但只有一个方式不同。

      首先让我们看一下在javascript中实现原型继承的standard ways之一:

      var MyClass = function(bar){
          this.foo = bar;
      };
      
      MyClass.prototype.awesomeMethod = function(){
          alert("I'm awesome")
      };
      
      // extends MyClass
      var MySubClass = function(bar){
          MyClass.call(this, bar); // <- call super constructor
      }
      
      // which happens here
      MySubClass.prototype = Object.create(MyClass.prototype);  // prototype object with MyClass as its prototype
      // allows us to still walk up the prototype chain as expected
      Object.defineProperty(MySubClass.prototype, "constructor", {
          enumerable: false,  // this is merely a preference, but worth considering, it won't affect the inheritance aspect
          value: MySubClass
      });
      
      // place extended/overridden methods here
      MySubClass.prototype.superAwesomeMethod = function(){
          alert("I'm super awesome!");
      };
      
      var testInstance = new MySubClass("hello");
      alert(testInstance instanceof MyClass); // true
      alert(testInstance instanceof MySubClass); // true
      

      下一个例子只是包装了上面的结构以保持一切干净。乍一看,似乎有一个轻微的调整可以创造奇迹。然而,真正发生的只是子类的每个实例都不是使用 Array 原型作为构造模板,而是使用 Array 的实例 - 所以子类的原型被挂在一个完全加载的对象的末尾传递数组的ducktype - 然后复制它。如果您仍然在这里看到一些奇怪的东西并且让您感到困扰,我不确定我能否更好地解释它 - 所以它的工作原理可能是另一个问题的好话题。 :)

      var extend = function(child, parent, optionalArgs){ //...
          if(parent.toString() === "function "+parent.name+"() { [native code] }"){
              optionalArgs = [parent].concat(Array.prototype.slice.call(arguments, 2));
              child.prototype = Object.create(new parent.bind.apply(null, optionalArgs));
          }else{
              child.prototype = Object.create(parent.prototype);
          }
          Object.defineProperties(child.prototype, {
              constructor: {enumerable: false, value: child},
              _super_: {enumerable: false, value: parent}  // merely for convenience (for future use), its not used here because our prototype is already constructed!
          });
      };
      var Vector = (function(){
          // we can extend Vector prototype here because functions are hoisted
          // so it keeps the extend declaration close to the class declaration
          // where we would expect to see it
          extend(Vector, Array);
      
          function Vector(){
              // from here on out we are an instance of Array as well as an instance of Vector
      
              // not needed here
              // this._super_.call(this, arguments);  // applies parent constructor (in this case Array, but we already did it during prototyping, so use this when extending your own classes)
      
              // construct a Vector as needed from arguments
              this.push.apply(this, arguments);
          }
      
          // just in case the prototype description warrants a closure
          (function(){
              var _Vector = this;
      
              _Vector.sum = function sum(){
                  var i=0, s=0.0, l=this.length;
                  while(i<l){
                      s = s + this[i++];
                  }
                  return s;
              };
          }).call(Vector.prototype);
      
          return Vector;
      })();
      
      var a = new Vector(1,2,3);                         // 1,2,3
      var b = new Vector(4,5,6,7);                       // 4,5,6,7
      alert(a instanceof Array && a instanceof Vector);  // true
      alert(a === b);                                    // false
      alert(a.length);                                   // 3
      alert(b.length);                                   // 4
      alert(a.sum());                                    // 6
      alert(b.sum());                                    // 22
      

      很快我们将拥有类和在 ES6 中扩展原生类的能力,但这可能还需要一年时间。同时,我希望这对某人有所帮助。

      【讨论】:

        【解决方案7】:
        function SubArray(arrayToInitWith){
          Array.call(this);
          var subArrayInstance = this;
          subArrayInstance.length = arrayToInitWith.length;
          arrayToInitWith.forEach(function(e, i){
            subArrayInstance[i] = e;
          });
        }
        
        SubArray.prototype = Object.create(Array.prototype);
        SubArray.prototype.specialMethod = function(){alert("baz");};
        
        var subclassedArray = new SubArray(["Some", "old", "values"]);
        

        【讨论】:

          猜你喜欢
          • 2010-09-07
          • 1970-01-01
          • 2011-04-18
          • 1970-01-01
          • 1970-01-01
          • 2017-03-21
          • 1970-01-01
          • 2011-01-26
          • 1970-01-01
          相关资源
          最近更新 更多