在 JavaScript 中模拟 super

发布于 2024-12-14 04:18:47 字数 1031 浏览 2 评论 0 原文

基本上有一个很好的优雅机制来模拟 super ,其语法与以下之一一样简单

  • this.$super.prop()
  • this.$super .prop.apply(this,arguments);

要坚持的标准是:

  1. this.$super 必须是对原型的引用。即,如果我在运行时更改超级原型,则此更改将得到反映。这基本上意味着父级有一个新属性,那么应该在运行时通过 super 显示在所有子级上,就像对父级的硬编码引用会反映
  2. this.$super 的 更改一样.f.apply(this,arguments); 必须适用于递归调用。对于任何在沿着继承链向上进行多个 super 调用的继承链集,您一定不会遇到递归问题。
  3. 您不得对子级中的超级对象的引用进行硬编码。即 Base.prototype.f.apply(this,arguments); 打败了这一点。
  4. 您不得使用 X to JavaScript 编译器或 JavaScript 预处理器。
  5. 必须符合 ES5

简单的实现会是这样的。

var injectSuper = function (parent, child) {
  child.prototype.$super = parent.prototype;
};

但这打破了条件2

迄今为止我见过的最优雅的机制是 IvoWetzel 的 eval< /code> hack,这几乎是一个 JavaScript 预处理器,因此不符合标准 4。

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

  • this.$super.prop()
  • this.$super.prop.apply(this, arguments);

Criteria to uphold are :

  1. this.$super must be a reference to the prototype. i.e. if I change the super prototype at run-time this change will be reflected. This basically means it the parent has a new property then this should be shown at run-time on all children through super just like a hard coded reference to the parent would reflect changes
  2. this.$super.f.apply(this, arguments); must work for recursive calls. For any chained set of inheritance where multiple super calls are made as you go up the inheritance chain, you must not hit the recursive problem.
  3. You must not hardcode references to super objects in your children. I.e. Base.prototype.f.apply(this, arguments); defeats the point.
  4. You must not use a X to JavaScript compiler or JavaScript preprocessor.
  5. Must be ES5 compliant

The naive implementation would be something like this.

var injectSuper = function (parent, child) {
  child.prototype.$super = parent.prototype;
};

But this breaks condition 2.

The most elegant mechanism I've seen to date is IvoWetzel's eval hack, which is pretty much a JavaScript preprocessor and thus fails criteria 4.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

上课铃就是安魂曲 2024-12-21 04:18:47

我认为没有一种“免费”的方法可以解决你提到的“递归超级”问题。

我们不能搞乱this,因为这样做会迫使我们以非标准的方式改变原型,或者将我们提升到原型链上,从而丢失实例变量。因此,当我们执行超级操作时,必须知道“当前类”和“超类”,而不将该责任传递给 this 或其属性之一。

我们可以尝试做很多事情,但我认为会产生一些不良后果:

  • 在创建时向函数添加超级信息,使用arguments.calee或类似的邪恶方式访问它。
  • 调用 super 方法时添加额外信息

    $super(CurrentClass).method.call(this, 1,2,3)
    

    这迫使我们复制当前的类名(这样我们就可以在某些超级字典中查找它的超类),但至少它不像必须复制超类名那么糟糕(因为如果比与类自己的名称的内部耦合更糟糕)

    //普通Javascript需要超类名
    SuperClass.prototype.method.call(this, 1,2,3);
    

    虽然这远非理想,但至少有一些来自 2.x 的历史先例Python。 (他们“修复”了 3.0 的 super,因此它不再需要参数,但我不确定其中涉及多少魔力以及它对 JS 的可移植性)


编辑:工作 小提琴

var superPairs = [];
// An association list of baseClass -> parentClass

var injectSuper = function (parent, child) {
    superPairs.push({
        parent: parent,
        child: child
    });
};

function $super(baseClass, obj){
    for(var i=0; i < superPairs.length; i++){
        var p = superPairs[i];
        if(p.child === baseClass){
            return p.parent;
        }
    }
}

I don't think there is a "free" way out of the "recursive super" problem you mention.

We can't mess with the this because doing so would either force us to change prototypes in a nonstandard way, or move us up the proto chain, losing instance variables. Therefore the "current class" and "super class" must be known when we do the super-ing, without passing that responsibility to this or one of its properties.

There are many some things we could try doing but all I can think have some undesireable consequences:

  • Add super info to the functions at creation time, access it using arguments.calee or similar evilness.
  • Add extra info when calling the super method

    $super(CurrentClass).method.call(this, 1,2,3)
    

    This forces us to duplicate the current class name (so we can look up its superclass in some super dictionary) but at least it isn't as bad as having to duplicate the superclass name, (since coupling against the inheritance relationships if worse then the inner coupling with a class' own name)

    //Normal Javascript needs the superclass name
    SuperClass.prototype.method.call(this, 1,2,3);
    

    While this is far from ideal, there is at least some historical precedent from 2.x Python. (They "fixed" super for 3.0 so it doesn't require arguments anymore, but I am not sure how much magic that involved and how portable it would be to JS)


Edit: Working fiddle

var superPairs = [];
// An association list of baseClass -> parentClass

var injectSuper = function (parent, child) {
    superPairs.push({
        parent: parent,
        child: child
    });
};

function $super(baseClass, obj){
    for(var i=0; i < superPairs.length; i++){
        var p = superPairs[i];
        if(p.child === baseClass){
            return p.parent;
        }
    }
}
怪我入戏太深 2024-12-21 04:18:47

John Resig 发布了一个具有简单但出色的 super 支持的 ineherence 机制。
唯一的区别是 super 指向您调用它的基本方法。

请查看http://ejohn.org/blog/simple-javascript-inheritance/

John Resig posted an ineherence mechanism with simple but great super support.
The only difference is that super points to the base method from where you are calling it.

Take a look at http://ejohn.org/blog/simple-javascript-inheritance/.

空宴 2024-12-21 04:18:47

请注意,对于以下实现,当您位于通过 $super 调用的方法内部时,在父类中工作时对属性的访问永远不会解析为子类的方法或变量,除非您访问直接存储在对象本身上的成员(而不是附加到原型上)。这避免了一系列的混乱(读作微妙的错误)。

更新:
这是一个无需 __proto__ 即可工作的实现。问题是使用 $super 与父对象拥有的属性数量成线性关系。

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function () {
            var selfPrototype = self.constructor.prototype;
            var pp = Parent.prototype;
            for (var x in pp) {
                self[x] = pp[x];
            }
            try {
                return prop.apply(self, arguments);
            }
            finally {
                for (var x in selfPrototype) {
                    self[x] = selfPrototype[x];
                }
            }
        };
    };
}

以下实现适用于支持 __proto__ 属性的浏览器:

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function (/*arg1, arg2, ...*/) {
            var selfProto = self.__proto__;
            self.__proto__ = Parent.prototype;
            try {
                return prop.apply(self, arguments);
            }
            finally {
                self.__proto__ = selfProto;
            }
        };
    };
}

示例:

function A () {}
extend(A, {
    foo: function () {
        return "A1";
    }
});

function B () {}
extend(B, {
    foo: function () {
        return this.$super("foo")() + "_B1";
    }
}, A);

function C () {}
extend(C, {
    foo: function () {
        return this.$super("foo")() + "_C1";
    }
}, B);


var c = new C();
var res1 = c.foo();
B.prototype.foo = function () {
    return this.$super("foo")() + "_B2";
};
var res2 = c.foo();

alert(res1 + "\n" + res2);

Note that for the following implementation, when you are inside a method that is invoked via $super, access to properties while working in the parent class never resolve to the child class's methods or variables, unless you access a member that is stored directly on the object itself (as opposed to attached to the prototype). This avoids a slew of confusion (read as subtle bugs).

Update:
Here is an implementation that works without __proto__. The catch is that using $super is linear in the number of properties the parent object has.

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function () {
            var selfPrototype = self.constructor.prototype;
            var pp = Parent.prototype;
            for (var x in pp) {
                self[x] = pp[x];
            }
            try {
                return prop.apply(self, arguments);
            }
            finally {
                for (var x in selfPrototype) {
                    self[x] = selfPrototype[x];
                }
            }
        };
    };
}

The following implementation is for browsers that support the __proto__ property:

function extend (Child, prototype, /*optional*/Parent) {
    if (!Parent) {
        Parent = Object;
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    for (var x in prototype) {
        if (prototype.hasOwnProperty(x)) {
            Child.prototype[x] = prototype[x];
        }
    }
    Child.prototype.$super = function (propName) {
        var prop = Parent.prototype[propName];
        if (typeof prop !== "function") {
            return prop;
        }
        var self = this;
        return function (/*arg1, arg2, ...*/) {
            var selfProto = self.__proto__;
            self.__proto__ = Parent.prototype;
            try {
                return prop.apply(self, arguments);
            }
            finally {
                self.__proto__ = selfProto;
            }
        };
    };
}

Example:

function A () {}
extend(A, {
    foo: function () {
        return "A1";
    }
});

function B () {}
extend(B, {
    foo: function () {
        return this.$super("foo")() + "_B1";
    }
}, A);

function C () {}
extend(C, {
    foo: function () {
        return this.$super("foo")() + "_C1";
    }
}, B);


var c = new C();
var res1 = c.foo();
B.prototype.foo = function () {
    return this.$super("foo")() + "_B2";
};
var res2 = c.foo();

alert(res1 + "\n" + res2);
醉酒的小男人 2024-12-21 04:18:47

super 的主要困难在于,您需要找到我在此处所说的内容:包含进行 super 引用的方法的对象。这对于获得正确的语义是绝对必要的。显然,拥有 here 的原型也同样好,但这并没有多大区别。以下是静态解决方案:

// Simulated static super references (as proposed by Allen Wirfs-Brock)
// http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super

//------------------ Library

function addSuperReferencesTo(obj) {
    Object.getOwnPropertyNames(obj).forEach(function(key) {
        var value = obj[key];
        if (typeof value === "function" && value.name === "me") {
            value.super = Object.getPrototypeOf(obj);
        }
    });
}

function copyOwnFrom(target, source) {
    Object.getOwnPropertyNames(source).forEach(function(propName) {
        Object.defineProperty(target, propName,
            Object.getOwnPropertyDescriptor(source, propName));
    });
    return target;
};

function extends(subC, superC) {
    var subProto = Object.create(superC.prototype);
    // At the very least, we keep the "constructor" property
    // At most, we preserve additions that have already been made
    copyOwnFrom(subProto, subC.prototype);
    addSuperReferencesTo(subProto);
    subC.prototype = subProto;
};

//------------------ Example

function A(name) {
    this.name = name;
}
A.prototype.method = function () {
    return "A:"+this.name;
}

function B(name) {
    A.call(this, name);
}
// A named function expression allows a function to refer to itself
B.prototype.method = function me() {
    return "B"+me.super.method.call(this);
}
extends(B, A);

var b = new B("hello");
console.log(b.method()); // BA:hello

The main difficulty with super is that you need to find what I call here: the object that contains the method that makes the super reference. That is absolutely necessary to get the semantics right. Obviously, having the prototype of here is just as good, but that doesn’t make much of a difference. The following is a static solution:

// Simulated static super references (as proposed by Allen Wirfs-Brock)
// http://wiki.ecmascript.org/doku.php?id=harmony:object_initialiser_super

//------------------ Library

function addSuperReferencesTo(obj) {
    Object.getOwnPropertyNames(obj).forEach(function(key) {
        var value = obj[key];
        if (typeof value === "function" && value.name === "me") {
            value.super = Object.getPrototypeOf(obj);
        }
    });
}

function copyOwnFrom(target, source) {
    Object.getOwnPropertyNames(source).forEach(function(propName) {
        Object.defineProperty(target, propName,
            Object.getOwnPropertyDescriptor(source, propName));
    });
    return target;
};

function extends(subC, superC) {
    var subProto = Object.create(superC.prototype);
    // At the very least, we keep the "constructor" property
    // At most, we preserve additions that have already been made
    copyOwnFrom(subProto, subC.prototype);
    addSuperReferencesTo(subProto);
    subC.prototype = subProto;
};

//------------------ Example

function A(name) {
    this.name = name;
}
A.prototype.method = function () {
    return "A:"+this.name;
}

function B(name) {
    A.call(this, name);
}
// A named function expression allows a function to refer to itself
B.prototype.method = function me() {
    return "B"+me.super.method.call(this);
}
extends(B, A);

var b = new B("hello");
console.log(b.method()); // BA:hello
2024-12-21 04:18:47

JsFiddle

这有什么问题吗?

'use strict';

function Class() {}
Class.extend = function (constructor, definition) {
    var key, hasOwn = {}.hasOwnProperty, proto = this.prototype, temp, Extended;

    if (typeof constructor !== 'function') {
        temp = constructor;
        constructor = definition || function () {};
        definition = temp;
    }
    definition = definition || {};

    Extended = constructor;
    Extended.prototype = new this();

    for (key in definition) {
        if (hasOwn.call(definition, key)) {
            Extended.prototype[key] = definition[key];
        }
    }

    Extended.prototype.constructor = Extended;

    for (key in this) {
        if (hasOwn.call(this, key)) {
            Extended[key] = this[key];
        }
    }

    Extended.$super = proto;
    return Extended;
};

用法:

var A = Class.extend(function A () {}, {
    foo: function (n) { return n;}
});
var B = A.extend(function B () {}, {
    foo: function (n) {
        if (n > 100) return -1;
        return B.$super.foo.call(this, n+1);
    }
});
var C = B.extend(function C () {}, {
    foo: function (n) {
        return C.$super.foo.call(this, n+2);
    }
});

var c = new C();
document.write(c.foo(0) + '<br>'); //3
A.prototype.foo = function(n) { return -n; };
document.write(c.foo(0)); //-3

使用特权方法而不是公共方法的示例用法。

var A2 = Class.extend(function A2 () {
    this.foo = function (n) {
        return n;
    };
});
var B2 = A2.extend(function B2 () {
    B2.$super.constructor();
    this.foo = function (n) {
        if (n > 100) return -1;
        return B2.$super.foo.call(this, n+1);
    };
});
var C2 = B2.extend(function C2 () {
    C2.$super.constructor();
    this.foo = function (n) {
        return C2.$super.foo.call(this, n+2);
    };
});

//you must remember to constructor chain
//if you don't then C2.$super.foo === A2.prototype.foo

var c = new C2();
document.write(c.foo(0) + '<br>'); //3

JsFiddle:

What is wrong with this?

'use strict';

function Class() {}
Class.extend = function (constructor, definition) {
    var key, hasOwn = {}.hasOwnProperty, proto = this.prototype, temp, Extended;

    if (typeof constructor !== 'function') {
        temp = constructor;
        constructor = definition || function () {};
        definition = temp;
    }
    definition = definition || {};

    Extended = constructor;
    Extended.prototype = new this();

    for (key in definition) {
        if (hasOwn.call(definition, key)) {
            Extended.prototype[key] = definition[key];
        }
    }

    Extended.prototype.constructor = Extended;

    for (key in this) {
        if (hasOwn.call(this, key)) {
            Extended[key] = this[key];
        }
    }

    Extended.$super = proto;
    return Extended;
};

Usage:

var A = Class.extend(function A () {}, {
    foo: function (n) { return n;}
});
var B = A.extend(function B () {}, {
    foo: function (n) {
        if (n > 100) return -1;
        return B.$super.foo.call(this, n+1);
    }
});
var C = B.extend(function C () {}, {
    foo: function (n) {
        return C.$super.foo.call(this, n+2);
    }
});

var c = new C();
document.write(c.foo(0) + '<br>'); //3
A.prototype.foo = function(n) { return -n; };
document.write(c.foo(0)); //-3

Example usage with privileged methods instead of public methods.

var A2 = Class.extend(function A2 () {
    this.foo = function (n) {
        return n;
    };
});
var B2 = A2.extend(function B2 () {
    B2.$super.constructor();
    this.foo = function (n) {
        if (n > 100) return -1;
        return B2.$super.foo.call(this, n+1);
    };
});
var C2 = B2.extend(function C2 () {
    C2.$super.constructor();
    this.foo = function (n) {
        return C2.$super.foo.call(this, n+2);
    };
});

//you must remember to constructor chain
//if you don't then C2.$super.foo === A2.prototype.foo

var c = new C2();
document.write(c.foo(0) + '<br>'); //3
荒路情人 2024-12-21 04:18:47

本着完整性的精神(也感谢大家的这个帖子,它是一个很好的参考点!)我想加入这个实现。

如果我们承认没有很好的方法来满足上述所有标准,那么我认为这是 Salsify 团队的勇敢努力(我刚刚找到它)在这里找到。这是我见过的唯一实现,既避免了递归问题,又让 .super 成为对正确原型的引用,而无需预编译。

因此,我们不是打破标准 1,而是打破标准 5。

该技术取决于使用 Function.caller (不符合 es5,尽管它在浏览器中得到广泛支持,并且 es6 消除了未来的需求),但它给出了非常优雅的解决所有其他问题(我认为)。 .caller 让我们获取方法引用,从而让我们定位原型链中的位置,并使用 getter 返回正确的原型。它并不完美,但它与我在这个空间中看到的解决方案有很大不同,

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'

您也可以调整它以返回正确的方法(如原始帖子中所示),或者您可以消除用名称注释每个方法的需要,通过将名称传递给超级函数(而不是使用 getter)

这里是一个演示该技术的工作小提琴: jsfiddle< /a>

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

碍人泪离人颜 2024-12-21 04:18:47

看看 Classy 库;它使用 this.$super 提供类和继承以及对重写方法的访问

Have a look at the Classy library; it provides classes and inheritance and access to an overridden method using this.$super

千纸鹤带着心事 2024-12-21 04:18:47

对于那些不理解OP提出的递归问题的人,这里有一个例子:

function A () {}
A.prototype.foo = function (n) {
    return n;
};

function B () {}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.$super = A.prototype;
B.prototype.foo = function (n) {
    if (n > 100) return -1;
    return this.$super.foo.call(this, n+1);
};

function C () {}
C.prototype = new B();
C.prototype.constructor = C;
C.prototype.$super = B.prototype;
C.prototype.foo = function (n) {
    return this.$super.foo.call(this, n+2);
};


alert(new C().foo(0)); // alerts -1, not 3

原因:Javascript中的this是动态绑定的。

For those who do not understand the recursion problem the OP presents, here is an example:

function A () {}
A.prototype.foo = function (n) {
    return n;
};

function B () {}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.$super = A.prototype;
B.prototype.foo = function (n) {
    if (n > 100) return -1;
    return this.$super.foo.call(this, n+1);
};

function C () {}
C.prototype = new B();
C.prototype.constructor = C;
C.prototype.$super = B.prototype;
C.prototype.foo = function (n) {
    return this.$super.foo.call(this, n+2);
};


alert(new C().foo(0)); // alerts -1, not 3

The reason: this in Javascript is dynamically bound.

给妤﹃绝世温柔 2024-12-21 04:18:47

我想出了一种方法,允许您通过更改执行上下文来使用伪关键字 Super(我还没有在这里看到这种方法。)我发现我根本不满意的缺点是它不能将“Super”变量添加到方法的执行上下文中,而是将其替换为整个执行上下文,这意味着用该方法定义的任何私有方法都将变得不可用...

此方法与“eval hack”非常相似OP 提出,但它不对函数的源字符串,只需在当前执行上下文中使用 eval 重新声明该函数。让它变得更好一点,因为这两种方法都有相同的上述缺点。

非常简单的方法:

function extend(child, parent){

    var superify = function(/* Super */){
        // Make MakeClass scope unavailable.
        var child = undefined,
            parent = undefined,
            superify = null,
            parentSuper = undefined,
            oldProto = undefined,
            keys = undefined,
            i = undefined,
            len = undefined;

        // Make Super available to returned func.
        var Super = arguments[0];
        return function(/* func */){
            /* This redefines the function with the current execution context.
             * Meaning that when the returned function is called it will have all of the current scopes variables available to it, which right here is just "Super"
             * This has the unfortunate side effect of ripping the old execution context away from the method meaning that no private methods that may have been defined in the original scope are available to it.
             */
            return eval("("+ arguments[0] +")");
        };
    };

    var parentSuper = superify(parent.prototype);

    var oldProto = child.prototype;
    var keys = Object.getOwnPropertyNames(oldProto);
    child.prototype = Object.create(parent.prototype);
    Object.defineProperty(child.prototype, "constructor", {enumerable: false, value: child});

    for(var i = 0, len = keys.length; i<len; i++)
        if("function" === typeof oldProto[keys[i]])
            child.prototype[keys[i]] = parentSuper(oldProto[keys[i]]);
}

一个制作类的例子

function P(){}
P.prototype.logSomething = function(){console.log("Bro.");};

function C(){}
C.prototype.logSomething = function(){console.log("Cool story"); Super.logSomething.call(this);}

extend(C, P);

var test = new C();
test.logSomething(); // "Cool story" "Bro."

前面提到的缺点的一个例子。

(function(){
    function privateMethod(){console.log("In a private method");}

    function P(){};

    window.C = function C(){};
    C.prototype.privilagedMethod = function(){
        // This throws an error because when we call extend on this class this function gets redefined in a new scope where privateMethod is not available.
        privateMethod();
    }

    extend(C, P);
})()

var test = new C();
test.privilagedMethod(); // throws error

另请注意,此方法并不是“超级”子构造函数,这意味着 Super 对其不可用。我只是想解释这个概念,而不是创建一个工作库:)

另外,刚刚意识到我满足了OP的所有条件! (虽然确实应该有一个关于执行上下文的条件)

I came up with a way that will allow you to use a pseudo keyword Super by changing the execution context (A way I have yet to see be presented on here.) The drawback that I found that I'm not happy with at all is that it cannot add the "Super" variable to the method's execution context, but instead replaces it the entire execution context, this means that any private methods defined with the method become unavailable...

This method is very similar to the "eval hack" OP presented however it doesn't do any processing on the function's source string, just redeclares the function using eval in the current execution context. Making it a bit better as both of the methods have the same aforementioned drawback.

Very simple method:

function extend(child, parent){

    var superify = function(/* Super */){
        // Make MakeClass scope unavailable.
        var child = undefined,
            parent = undefined,
            superify = null,
            parentSuper = undefined,
            oldProto = undefined,
            keys = undefined,
            i = undefined,
            len = undefined;

        // Make Super available to returned func.
        var Super = arguments[0];
        return function(/* func */){
            /* This redefines the function with the current execution context.
             * Meaning that when the returned function is called it will have all of the current scopes variables available to it, which right here is just "Super"
             * This has the unfortunate side effect of ripping the old execution context away from the method meaning that no private methods that may have been defined in the original scope are available to it.
             */
            return eval("("+ arguments[0] +")");
        };
    };

    var parentSuper = superify(parent.prototype);

    var oldProto = child.prototype;
    var keys = Object.getOwnPropertyNames(oldProto);
    child.prototype = Object.create(parent.prototype);
    Object.defineProperty(child.prototype, "constructor", {enumerable: false, value: child});

    for(var i = 0, len = keys.length; i<len; i++)
        if("function" === typeof oldProto[keys[i]])
            child.prototype[keys[i]] = parentSuper(oldProto[keys[i]]);
}

An example of making a class

function P(){}
P.prototype.logSomething = function(){console.log("Bro.");};

function C(){}
C.prototype.logSomething = function(){console.log("Cool story"); Super.logSomething.call(this);}

extend(C, P);

var test = new C();
test.logSomething(); // "Cool story" "Bro."

An example of the drawback mentioned earlier.

(function(){
    function privateMethod(){console.log("In a private method");}

    function P(){};

    window.C = function C(){};
    C.prototype.privilagedMethod = function(){
        // This throws an error because when we call extend on this class this function gets redefined in a new scope where privateMethod is not available.
        privateMethod();
    }

    extend(C, P);
})()

var test = new C();
test.privilagedMethod(); // throws error

Also note that this method isn't "superifying" the child constructor meaning that Super isn't available to it. I just wanted to explain the concept, not make a working library :)

Also, just realized that I met all of OP's conditions! (Although there really should be a condition about execution context)

相守太难 2024-12-21 04:18:47

这是我的版本:lowclass

这是测试中的super意大利面汤示例.js 文件(编辑:制作成运行示例):

var SomeClass = Class((public, protected, private) => ({

    // default access is public, like C++ structs
    publicMethod() {
        console.log('base class publicMethod')
        protected(this).protectedMethod()
    },

    checkPrivateProp() {
        console.assert( private(this).lorem === 'foo' )
    },

    protected: {
        protectedMethod() {
            console.log('base class protectedMethod:', private(this).lorem)
            private(this).lorem = 'foo'
        },
    },

    private: {
        lorem: 'blah',
    },
}))

var SubClass = SomeClass.subclass((public, protected, private, _super) => ({

    publicMethod() {
        _super(this).publicMethod()
        console.log('extended a public method')
        private(this).lorem = 'baaaaz'
        this.checkPrivateProp()
    },

    checkPrivateProp() {
        _super(this).checkPrivateProp()
        console.assert( private(this).lorem === 'baaaaz' )
    },

    protected: {

        protectedMethod() {
            _super(this).protectedMethod()
            console.log('extended a protected method')
        },

    },

    private: {
        lorem: 'bar',
    },
}))

var GrandChildClass = SubClass.subclass((public, protected, private, _super) => ({

    test() {
        private(this).begin()
    },

    reallyBegin() {
        protected(this).reallyReallyBegin()
    },

    protected: {
        reallyReallyBegin() {
            _super(public(this)).publicMethod()
        },
    },

    private: {
        begin() {
            public(this).reallyBegin()
        },
    },
}))

var o = new GrandChildClass
o.test()

console.assert( typeof o.test === 'function' )
console.assert( o.reallyReallyBegin === undefined )
console.assert( o.begin === undefined )
<script> var module = { exports: {} } </script>
<script src="https://unpkg.com/[email protected]/index.js"></script>
<script> var Class = module.exports // get the export </script>

尝试无效的成员访问或无效使用 _super 将引发错误。

关于要求:

  1. this.$super 必须是对原型的引用。即,如果我在运行时更改超级原型,则此更改将得到反映。这基本上意味着父级有一个新属性,那么这应该在运行时通过 super 显示在所有子级上,就像对父级的硬编码引用会反映更改一样

    不,_super 帮助器不返回原型,仅返回带有复制描述符的对象,以避免修改受保护和私有原型。此外,从中复制描述符的原型保存在Class/subclass调用的范围内。拥有这个就太好了。 FWIW,本机类的行为相同。

  2. this.$super.f.apply(this, 参数);必须适用于递归调用。对于任何在沿着继承链向上进行多个 super 调用的继承链集,您一定不会遇到递归问题。

    是的,没问题。

  3. 您不得对子级中的超级对象进行硬编码引用。即 Base.prototype.f.apply(this,arguments);失败了。

    是的

  4. 您不得使用 X to JavaScript 编译器或 JavaScript 预处理器。

    是的,所有运行时

  5. 必须兼容 ES5

    是的,它包括一个基于 Babel 的构建步骤(某些类使用 Wea​​kMap,它被编译为非泄漏 ES5 形式)。我不认为这违反了要求 4,它只是允许我编写 ES6+,但它应该仍然可以在 ES5 中工作。诚然,我没有在 ES5 中对此进行太多测试,但如果您想尝试一下,我们绝对可以解决我端的任何构建问题,并且从您端来看,您应该能够在没有任何构建步骤的情况下使用它.

唯一未满足的要求是 1. 那就太好了。但更换原型也许是不好的做法。但实际上,我确实有一些用途,我想交换原型以实现元东西。 “如果能在本机 super 中拥有此功能(这是静态的 :( ),那就太好了,更不用说在此实现中了。

为了仔细检查要求 2,我将基本的递归测试添加到了我的 test.js 中,哪个有效(编辑:制作成运行示例):

const A = Class((public, protected, private) => ({
    foo: function (n) { return n }
}))

const B = A.subclass((public, protected, private, _super) => ({
    foo: function (n) {
        if (n > 100) return -1;
        return _super(this).foo(n+1);
    }
}))

const C = B.subclass((public, protected, private, _super) => ({
    foo: function (n) {
        return _super(this).foo(n+2);
    }
}))

var c = new C();
console.log( c.foo(0) === 3 )
<script> var module = { exports: {} } </script>
<script src="https://unpkg.com/[email protected]/index.js"></script>
<script> var Class = module.exports // get the export </script>

(对于这些小类来说,类头有点长。我有几个想法可以减少,如果不是所有的助手都需要预先)

Here's my version: lowclass

And here's the super spaghetti soup example from the test.js file (EDIT: made into running example):

var SomeClass = Class((public, protected, private) => ({

    // default access is public, like C++ structs
    publicMethod() {
        console.log('base class publicMethod')
        protected(this).protectedMethod()
    },

    checkPrivateProp() {
        console.assert( private(this).lorem === 'foo' )
    },

    protected: {
        protectedMethod() {
            console.log('base class protectedMethod:', private(this).lorem)
            private(this).lorem = 'foo'
        },
    },

    private: {
        lorem: 'blah',
    },
}))

var SubClass = SomeClass.subclass((public, protected, private, _super) => ({

    publicMethod() {
        _super(this).publicMethod()
        console.log('extended a public method')
        private(this).lorem = 'baaaaz'
        this.checkPrivateProp()
    },

    checkPrivateProp() {
        _super(this).checkPrivateProp()
        console.assert( private(this).lorem === 'baaaaz' )
    },

    protected: {

        protectedMethod() {
            _super(this).protectedMethod()
            console.log('extended a protected method')
        },

    },

    private: {
        lorem: 'bar',
    },
}))

var GrandChildClass = SubClass.subclass((public, protected, private, _super) => ({

    test() {
        private(this).begin()
    },

    reallyBegin() {
        protected(this).reallyReallyBegin()
    },

    protected: {
        reallyReallyBegin() {
            _super(public(this)).publicMethod()
        },
    },

    private: {
        begin() {
            public(this).reallyBegin()
        },
    },
}))

var o = new GrandChildClass
o.test()

console.assert( typeof o.test === 'function' )
console.assert( o.reallyReallyBegin === undefined )
console.assert( o.begin === undefined )
<script> var module = { exports: {} } </script>
<script src="https://unpkg.com/[email protected]/index.js"></script>
<script> var Class = module.exports // get the export </script>

Trying invalid member access or invalid use of _super will throw an error.

About the requirements:

  1. this.$super must be a reference to the prototype. i.e. if I change the super prototype at run-time this change will be reflected. This basically means it the parent has a new property then this should be shown at run-time on all children through super just like a hard coded reference to the parent would reflect changes

    No, the _super helper doesn't return the prototype, only an object with copied descriptors to avoid modification of the protected and private prototypes. Furthermore, the prototype from which the descriptors are copied from is held in the scope of the Class/subclass call. It would be neat to have this. FWIW, native classes behave the same.

  2. this.$super.f.apply(this, arguments); must work for recursive calls. For any chained set of inheritance where multiple super calls are made as you go up the inheritance chain, you must not hit the recursive problem.

    yep, no problem.

  3. You must not hardcode references to super objects in your children. I.e. Base.prototype.f.apply(this, arguments); defeats the point.

    yep

  4. You must not use a X to JavaScript compiler or JavaScript preprocessor.

    yep, all runtime

  5. Must be ES5 compliant

    Yes, it includes a Babel-based build step (f.e. lowclass uses WeakMap, which is compiled to a non-leaky ES5 form). I don't think this defeats requirement 4, it just allows me to write ES6+ but it should still work in ES5. Admittedly I haven't done much testing of this in ES5, but if you'd like to try it, we can definitely iron out any builds issues on my end, and from your end you should be able to consume it without any build steps.

The only requirement not met is 1. It would be nice. But maybe it is bad practice to be swapping out prototypes. But actually, I do have uses where I would like to swap out prototypes in order to achieve meta stuff. 'Twould be nice to have this feature with native super (which is static :( ), let alone in this implementation.

To double check requirement 2, I added the basic recursive test to my test.js, which works (EDIT: made into running example):

const A = Class((public, protected, private) => ({
    foo: function (n) { return n }
}))

const B = A.subclass((public, protected, private, _super) => ({
    foo: function (n) {
        if (n > 100) return -1;
        return _super(this).foo(n+1);
    }
}))

const C = B.subclass((public, protected, private, _super) => ({
    foo: function (n) {
        return _super(this).foo(n+2);
    }
}))

var c = new C();
console.log( c.foo(0) === 3 )
<script> var module = { exports: {} } </script>
<script src="https://unpkg.com/[email protected]/index.js"></script>
<script> var Class = module.exports // get the export </script>

(the class header is a bit long for these little classes. I have a couple ideas to make it possible to reduce that if not all the helpers are needed up front)

小红帽 2024-12-21 04:18:47

我想我有一个更简单的方法......

function Father(){
  this.word = "I'm the Father";

  this.say = function(){
     return this.word; // I'm the Father;
  }
}

function Sun(){
  Father.call(this); // Extend the Father

  this.word = "I'm the sun"; // Override I'm the Father;

  this.say = function(){ // Override I'm the Father;
    this.word = "I was changed"; // Change the word;
    return new Father().say.apply(this); // Call the super.say()
  }
}

var a = new Father();
var b = new Sun();

a.say() // I'm the father
b.ay() // I'm the sun
b.say() // I was changed

I think I have a more simple way....

function Father(){
  this.word = "I'm the Father";

  this.say = function(){
     return this.word; // I'm the Father;
  }
}

function Sun(){
  Father.call(this); // Extend the Father

  this.word = "I'm the sun"; // Override I'm the Father;

  this.say = function(){ // Override I'm the Father;
    this.word = "I was changed"; // Change the word;
    return new Father().say.apply(this); // Call the super.say()
  }
}

var a = new Father();
var b = new Sun();

a.say() // I'm the father
b.ay() // I'm the sun
b.say() // I was changed
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文