Emulate super in javascript

后端 未结 11 1127
生来不讨喜
生来不讨喜 2020-12-08 10:18

Basically is there a good elegant mechanism to emulate super with syntax that is as simple as one of the following

  • this.$super.prop()
11条回答
  •  [愿得一人]
    2020-12-08 11:07

    In the spirit of completeness (also thank you everyone for this thread it has been an excellent point of reference!) I wanted to toss in this implementation.

    If we are admitting that there is no good way of meeting all of the above criteria, then I think this is a valiant effort by the Salsify team (I just found it) found here. This is the only implementation I've seen that avoids the recursion problem but also lets .super be a reference to the correct prototype, without pre-compilation.

    So instead of breaking criteria 1, we break 5.

    the technique hinges on using Function.caller (not es5 compliant, though it is extensively supported in browsers and es6 removes future need), but it gives really elegant solution to all the other issues (I think). .caller lets us get the method reference which lets us locate where we are in the prototype chain, and uses a getter to return the correct prototype. Its not perfect but it is widely different solution than what I've seen in this space

    var Base = function() {};
    
    Base.extend = function(props) {
      var parent = this, Subclass = function(){ parent.apply(this, arguments) };
    
        Subclass.prototype = Object.create(parent.prototype);
    
        for(var k in props) {
            if( props.hasOwnProperty(k) ){
                Subclass.prototype[k] = props[k]
                if(typeof props[k] === 'function')
                    Subclass.prototype[k]._name = k
            }
        }
    
        for(var k in parent) 
            if( parent.hasOwnProperty(k)) Subclass[k] = parent[k]        
    
        Subclass.prototype.constructor = Subclass
        return Subclass;
    };
    
    Object.defineProperty(Base.prototype, "super", {
      get: function get() {
        var impl = get.caller,
            name = impl._name,
            foundImpl = this[name] === impl,
            proto = this;
    
        while (proto = Object.getPrototypeOf(proto)) {
          if (!proto[name]) break;
          else if (proto[name] === impl) foundImpl = true;
          else if (foundImpl)            return proto;
        }
    
        if (!foundImpl) throw "`super` may not be called outside a method implementation";
      }
    });
    
    var Parent = Base.extend({
      greet: function(x) {
        return x + " 2";
      }
    })
    
    var Child = Parent.extend({
      greet: function(x) {
        return this.super.greet.call(this, x + " 1" );
      }
    });
    
    var c = new Child
    c.greet('start ') // => 'start 1 2'
    

    you can also adjust this to return the correct method (as in the original post) or you can remove the need to annotate each method with the name, by passing in the name to a super function (instead of using a getter)

    here is a working fiddle demonstrating the technique: jsfiddle

提交回复
热议问题