Why inheriting from Array is difficult to implement in ES5?

后端 未结 1 1171
暖寄归人
暖寄归人 2021-01-12 03:49

With prototype inheritance in ES5, it looks not trivial to inherit from Array and get the expected behavior, like updating .length automatically wh

相关标签:
1条回答
  • 2021-01-12 04:21

    The key thing about Array is that a real array object is an Array Exotic Object. An exotic object is an object that has behavior that could not be achived using standard JS language features, though in ES6 Proxy allows much more ability for user code to create exotic-like objects.

    When subclassing a constructor that returns an exotic object like Array, the subclassing method needs to be done in such a way that the object created is actually an exotic object. When you do something like

    function ArraySubclass(){}
    ArraySubclass.prototype = Object.create(Array.prototype);
    

    then

    (new ArraySubclass()) instanceof Array
    

    because the prototype matches up, but the object returned by new ArraySubclass is just a normal object that happens to have Array.prototype in its prototype chain. But you'll notice that

    Array.isArray(new ArraySubclass()); // false
    

    because the object isn't a real exotic. In this case

    new ArraySubclass()
    

    is identical to doing

    var obj = Object.create(ArraySubclass.prototype);
    ArraySubclass.call(obj);
    

    So in ES5 how do you extend Array? You need to create an exotic object, but you also need to ensure that the exotic object has your ArraySubclass.prototype object in its prototype chain. That is where ES5 hit it issues, because in vanilla ES5, there is no way to change an existing object's prototype. With the __proto__ extension that many engines added you could get the correct Array subclassing behavior with code like

    var obj = new Array();
    obj.__proto__ = ArraySubclass.prototype;
    ArraySubclass.call(obj);
    

    Say you wanted to generalize the pattern above, how would you do it?

    function makeSubclass(baseConstructor, childConstructor){
        var obj = new baseConstructor();
        obj.__proto__ = childConstructor.prototype;
        return obj;
    }
    
    function ArraySubclass(){
        var arr = makeSubclass(Array, ArraySubclass); 
    
        // do initialization stuff and use 'arr' like 'this'
    
        return arr;
    }
    ArraySubclass.prototype = Object.create(Array.prototype);
    

    so that works in ES5 + __proto__, but what about as things get more complicated? What if you want to subclass ArraySubclass? You'd have to be able to change the second the second parameter of makeSubclass. But how do we do that? What is the actual goal here? When you do something like

    new ArraySubclass()
    

    it is the value passed to new that we care about as that second parameter, and it is that constructor's prototype that should be getting passed along. There is no nice avenue in ES5 to accomplish this.

    This is where ES6 classes have a benefit.

    class ArraySubclass extends Array {
      constructor(){
        super();
      }
    }
    

    The key thing is that when super() runs, it knows that ArraySubclass is the child class. When super() calls new Array, it also passes along an extra hidden parameter that says "hey, when you create this array, set its prototype to ArraySubclass.prototype. If there are many levels of inheritance, it will pass along the child-most prototype so that the returned exotic object is a real exotic while also making sure it has the correct prototype.

    Not only does this mean that things are constructed properly, but it means that engines can create the object with the correct prototype value up front. Mutating an object's __proto__ value after creating is a well-known deoptimization point because of the ways engines process and track objects.

    0 讨论(0)
提交回复
热议问题