如何在类声明上实现伪经典继承? [英] How to achieve pseudo-classical inheritance right on the class declaration?

查看:87
本文介绍了如何在类声明上实现伪经典继承?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


注意:



正如答案所说,问题中提出的代码 NOT 由于问题和评论中描述的一些问题,确实实现了继承(否则它会成为答案而不是问题......)。它的工作原理与 假的 继承(甚至不是原型)一样。







  • 摘要



    简而言之,使它与我们编写一般的OO语言而不是javascript类似,但保持继承是正确的。


  • 故事



    。我建议你仔细阅读。该图显示了我们:


    1. 每个构造函数都有一个名为 prototype 的属性,它指向构造函数的原型对象。

    2. 每个原型都有一个名为构造函数的属性,它指向原型对象的构造函数。

    3. 我们从构造函数创建一个实例。但是实例实际上继承自原型,而不是构造函数。

    这个是非常有用的信息。传统上我们总是首先创建一个构造函数,然后我们设置它的 prototype 属性。但是,这些信息告诉我们,我们可以先创建一个原型对象,然后在其上定义构造函数属性。



    例如,传统上我们可以写:

     函数ABC(键,值){
    this.key = key ;
    this.value = value;
    }

    ABC.prototype.what = function(){
    alert(what);
    };

    然而,使用我们新发现的知识,我们可能会写同样的事情:

      var ABC = CLASS({
    构造函数:函数(键,值){
    this.key = key;
    this .value = value;
    },
    what:function(){
    alert(what);
    }
    });

    函数CLASS(prototype){
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    返回构造函数;
    }

    正如您所见,封装很容易在JavaScript中实现。你需要做的就是侧身思考。然而,继承是一个不同的问题。你需要做更多的工作来实现继承。



    继承和超级



    看看如何 augment 实现继承

      function augment(body){
    var base = typeof this ===function ? this.prototype:这个;
    var prototype = Object.create(base);
    body.apply(prototype,arrayFrom(arguments,1).concat(base));
    if(!ownPropertyOf(prototype,constructor))返回原型;
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    返回构造函数;
    }

    请注意,最后三行与<$ c $相同c> CLASS 来自上一节:

      function CLASS(prototype){
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    返回构造函数;
    }

    这告诉我们,一旦我们有了原型对象,我们需要做的就是得到它的构造函数属性并返回它。



    前三行扩充用于:


    1. 获取基类原型。

    2. 使用 Object.create 创建派生类原型。

    3. 使用指定的属性填充派生类原型。

    这就是JavaScript中继承的全部内容。如果你想创建自己的经典继承模式,那么你应该考虑相同的行。



    拥抱真正的原型继承



    每个值得他们学习的JavaScript程序员都会告诉你原型继承优于经典继承。然而,来自具有经典继承的语言的新手总是试图在原型继承之上实现经典继承,并且它们通常会失败。



    它们失败不是因为它不可能在原型继承之上实现经典继承,但是因为要在原型继承之上实现经典继承,首先需要理解真正的原型继承有效



    然而,一旦你理解了真正的原型继承你我永远不想回到古典遗产。我也尝试 实现作为新手在原型继承之上的经典继承。现在,我理解真正的原型继承是如何工作的,但是我编写了这样的代码:

      function extend(self,body){
    var base = typeof self ===function? self.prototype:self;
    var prototype = Object.create(base,{new:{value:create}});
    返回body.call(prototype,base),prototype;

    function create(){
    var self = Object.create(prototype);
    返回prototype.hasOwnProperty(constructor)&&
    prototype.constructor.apply(self,arguments),self;
    }
    }

    以上扩展功能非常类似于 augment 。但是,它不返回构造函数,而是返回原型对象。这实际上是一个非常巧妙的技巧,它允许继承静态属性。您可以使用 extend 创建一个类,如下所示:

      var Abc = extend(Object,function(){
    this.constructor = function(key,value){
    this.value = 333 + Number(value);
    this.key = key;
    };

    this.what = function(){
    alert(what);
    };
    });

    继承就是这么简单:

      var Xyz = extend(abc,function(base){
    this.constructor = function(key,value){
    base.constructor.call(this,key,价值);
    };

    this.that = function(){
    alert(that);
    };
    });

    但请记住, extend 不会返回构造函数。它返回原型对象。这意味着您无法使用 new 关键字来创建类的实例。相反,您需要使用 new 作为方法,如下所示:

      var x = Xyz.new(x,123); 
    var y = Xyz.new(y,456);
    var it = Abc.new(it,789);

    这实际上是一件好事。 new 关键字是考虑 有害我强烈建议你停止使用它。例如,不可能使用使用 new 关键字应用。但是,可以使用 apply new 方法,如下所示:

      var it = Abc.new.apply(null,[it,789]); 

    由于 abc Xyz 不是构造函数,我们不能使用 instanceof 来测试对象是否是 Abc的实例 Xyz 。然而,这不是问题,因为JavaScript有一个名为 isPrototypeOf ,它测试一个对象是否是另一个对象的原型:

      alert(x.key +:+ x.value +; isAbc:+ Abc.isPrototypeOf(x)); 
    alert(y.key +:+ y.value +; isAbc:+ Abc.isPrototypeOf(y));

    alert(it.key +:+ it.value +; isAbc:+ Abc.isPrototypeOf(it));
    alert(it.key +:+ it.value +; isXyz:+ xyz.isPrototypeOf(it));

    实际上 isPrototypeOf 比<更强大 instanceof 因为它允许我们测试一个类是否扩展另一个类:

     警报(Abc.isPrototypeOf(XYZ)); // true 

    除了这个小改动之外,其他一切都像以前一样工作:

      x.what(); 
    y.that();

    it.what();
    it.that(); //会抛出;它不是Xyz,没有那种方法

    自己看演示: http://jsfiddle.net/Jee96/



    还有什么其他的真正的原型继承提供?真正的原型继承的一个最大优点是普通属性和静态属性之间没有区别,允许你编写如下代码:

      var Xyz = extend(abc,function(base){
    this.empty = this.new();

    this.constructor = function(key,value){
    base.constructor.call(this,key,value);
    };

    this.that = function(){
    alert(that);
    };
    });

    请注意,我们可以通过调用 this.new 。如果尚未定义 this.constructor ,则返回一个新的未初始化实例。否则它返回一个新的初始化实例。



    另外因为 Xyz 是我们可以访问的原型对象 Xyz.empty 直接(即 Xyz 的静态属性) 。这也意味着静态属性会自动继承,并且与普通属性没有区别。



    最后,因为类可以从类定义中访问,因为这个,您可以使用 extend 创建嵌套类,这些类继承自嵌套在其中的类,如下所示:

      var ClassA = extend(Object,function(){
    var ClassB = extend(this,function(){
    // class definition
    });

    //类定义的其余部分

    alert(this.isPrototypeOf(ClassB)); // true
    } );

    自己看演示: http://jsfiddle.net/Jee96/1/


    Note:

    As the answers tell, the code proposed in the question does NOT really achieve inheritance(otherwise it becomes an answer rather than a question .. ) due to some issues described in the question and in my comments. It works as expected as a fake of inheritance(and not even prototypal).


    • Summary

      In short, make it as similar as we are writing a general OO language rather than javascript, but keep the inheritance be correct.

    • The story

      Object.create is a good way to achieve prototypal inheritance, but it's a bit confusing to a typed brain and new fans.

      There are various ways that we can write javascript code more like we are writing other OO languages with the pseudo-classical pattern. As it's pseudo-classical, we must deal with the underlying prototypal inheritance of javascript correctly.

      What I want to find is an approach that the pseudo-classical inheritance can be achieved right on the class declaration. The code for demonstration is put at the rear of the post, it works as expected, however, there are some annoying things:

      1. I cannot get rid of return in the class declaration or the inheritance won't work.

      2. I have no way except pass this in the class declaration to make the returning closure know what is this.

      3. I also want to get rid of function (instance, _super) {, but not yet have a good idea.

      4. The static(own properties) of a class are not inherited.

      A solution would be more of some syntactic sugar than the existing frameworks, a good pattern is applicable.


    The _extends function:

    function _extends(baseType) {
        return function (definition) {
            var caller=arguments.callee.caller;
            var instance=this;
    
            if(!(instance instanceof baseType)) {
                (caller.prototype=new baseType()).constructor=caller;
                instance=new caller();
            }
    
            var _super=function () {
                baseType.apply(instance, arguments);
            };
    
            definition(instance, _super);
            return instance;
        };
    }
    

    The Abc class:

    function Abc(key, value) {
        return _extends(Object).call(this, function (instance, _super) {
            instance.What=function () {
                alert('What');
            };
    
            instance.getValue=function () {
                return 333+Number(value);
            };
    
            instance.Value=instance.getValue();
            instance.Key=key;
        });
    }
    

    The Xyz class:

    function Xyz(key, value) {
        return _extends(Abc).call(this, function (instance, _super) {
            _super(key, value);
    
            instance.That=function () {
                alert('That');
            };
        });
    }
    

    Example code:

    var x=new Xyz('x', '123');
    alert([x.Key, x.Value].join(': ')+'; isAbc: '+(x instanceof Abc));
    
    var y=new Xyz('y', '456');
    alert([y.Key, y.Value].join(': ')+'; isAbc: '+(y instanceof Abc));
    
    var it=new Abc('it', '789');
    alert([it.Key, it.Value].join(': ')+'; isAbc: '+(it instanceof Abc));
    alert([it.Key, it.Value].join(': ')+'; isXyz: '+(it instanceof Xyz));
    
    x.What();
    y.That();
    
    it.What();
    it.That(); // will throw; it is not Xyz and does not have That method
    

    解决方案

    No. No. No. This won't do. You're doing inheritance in JavaScript all wrong. Your code gives me migraines.

    Creating a Pseudo-Classical Inheritance Pattern in JavaScript

    If you want something similar to classes in JavaScript then there are a lot of libraries out there which provide it to you. For example using augment you could restructure your code as follows:

    var augment = require("augment");
    
    var ABC = augment(Object, function () {
        this.constructor = function (key, value) {
            this.key = key;
            this.value = value;
        };
    
        this.what = function () {
            alert("what");
        };
    });
    
    var XYZ = augment(ABC, function (base) {
        this.constructor = function (key, value) {
            base.constructor.call(this, key, value);
        };
    
        this.that = function () {
            alert("that");
        };
    });
    

    I don't know about you but to me this looks a lot like classical inheritance in C++ or Java. If this solves your problem, great! If is doesn't then continue reading.

    Prototype-Class Isomorphism

    In a lot of ways prototypes are similar to classes. In fact prototypes and classes are so similar that we can use prototypes to model classes. First let's take a look at how prototypal inheritance really works:

    The above picture was taken from the following answer. I suggest you read it carefully. The diagram shows us:

    1. Every constructor has a property called prototype which points to the prototype object of the constructor function.
    2. Every prototype has a property called constructor which points to the constructor function of the prototype object.
    3. We create an instance from a constructor function. However the instance actually inherits from the prototype, not the constructor.

    This is very useful information. Traditionally we've always created a constructor function first and then we've set its prototype properties. However this information shows us that we may create a prototype object first and then define the constructor property on it instead.

    For example, traditionally we may write:

    function ABC(key, value) {
        this.key = key;
        this.value = value;
    }
    
    ABC.prototype.what = function() {
        alert("what");
    };
    

    However using our newfound knowledge we may write the same thing as:

    var ABC = CLASS({
        constructor: function (key, value) {
            this.key = key;
            this.value = value;
        },
        what: function () {
            alert("what");
        }
    });
    
    function CLASS(prototype) {
        var constructor = prototype.constructor;
        constructor.prototype = prototype;
        return constructor;
    }
    

    As you can see encapsulation is easy to achieve in JavaScript. All you need to do is think sideways. Inheritance however is a different issue. You need to do a little more work to achieve inheritance.

    Inheritance and Super

    Take a look at how augment achieves inheritance:

    function augment(body) {
        var base = typeof this === "function" ? this.prototype : this;
        var prototype = Object.create(base);
        body.apply(prototype, arrayFrom(arguments, 1).concat(base));
        if (!ownPropertyOf(prototype, "constructor")) return prototype;
        var constructor = prototype.constructor;
        constructor.prototype = prototype;
        return constructor;
    }
    

    Notice that the last three lines are the same as that of CLASS from the previous section:

    function CLASS(prototype) {
        var constructor = prototype.constructor;
        constructor.prototype = prototype;
        return constructor;
    }
    

    This tells us that once we have a prototype object all we need to do is get its constructor property and return it.

    The first three lines of augment are used to:

    1. Get the base class prototype.
    2. Create a derived class prototype using Object.create.
    3. Populate the derived class prototype with the specified properties.

    That's all that there is to inheritance in JavaScript. If you want to create your own classical inheritance pattern then you should be thinking along the same lines.

    Embracing True Prototypal Inheritance

    Every JavaScript programmer worth their salt will tell you that prototypal inheritance is better than classical inheritance. Nevertheless newbies who come from a language with classical inheritance always try to implement classical inheritance on top of prototypal inheritance, and they usually fail.

    They fail not because it's not possible to implement classical inheritance on top of prototypal inheritance but because to implement classical inheritance on top of prototypal inheritance you first need to understand how true prototypal inheritance works.

    However once you understand true prototypal inheritance you'll never want to go back to classical inheritance. I too tried to implement classical inheritance on top of prototypal inheritance as a newbie. Now that I understand how true prototypal inheritance works however I write code like this:

    function extend(self, body) {
        var base = typeof self === "function" ? self.prototype : self;
        var prototype = Object.create(base, {new: {value: create}});
        return body.call(prototype, base), prototype;
    
        function create() {
            var self = Object.create(prototype);
            return prototype.hasOwnProperty("constructor") &&
                prototype.constructor.apply(self, arguments), self;
        }
    }
    

    The above extend function is very similar to augment. However instead of returning the constructor function it returns the prototype object. This is actually a very neat trick which allows static properties to be inherited. You can create a class using extend as follows:

    var Abc = extend(Object, function () {
        this.constructor = function (key, value) {
            this.value = 333 + Number(value);
            this.key = key;
        };
    
        this.what = function () {
            alert("what");
        };
    });
    

    Inheritance is just as simple:

    var Xyz = extend(Abc, function (base) {
        this.constructor = function (key, value) {
            base.constructor.call(this, key, value);
        };
    
        this.that = function () {
            alert("that");
        };
    });
    

    Remember however that extend does not return the constructor function. It returns the prototype object. This means that you can't use the new keyword to create an instance of the class. Instead you need to use new as a method, as follows:

    var x = Xyz.new("x", "123");
    var y = Xyz.new("y", "456");
    var it = Abc.new("it", "789");
    

    This is actually a good thing. The new keyword is considered harmful and I strongly recommend you to stop using it. For example it's not possible to use apply with the new keyword. However it is possible to use apply with the new method as follows:

    var it = Abc.new.apply(null, ["it", "789"]);
    

    Since Abc and Xyz are not constructor functions we can't use instanceof to test whether an object is an instance of Abc or Xyz. However that's not a problem because JavaScript has a method called isPrototypeOf which tests whether an object is a prototype of another object:

    alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
    alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));
    
    alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
    alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));
    

    In fact isPrototypeOf is more powerful than instanceof because it allows us to test whether one class extends another class:

    alert(Abc.isPrototypeOf(Xyz)); // true
    

    Besides this minor change everything else works just like it did before:

    x.what();
    y.that();
    
    it.what();
    it.that(); // will throw; it is not Xyz and does not have that method
    

    See the demo for yourself: http://jsfiddle.net/Jee96/

    What else does true prototypal inheritance offer? One of the biggest advantages of true prototypal inheritance is that there's no distinction between normal properties and static properties allowing you to write code like this:

    var Xyz = extend(Abc, function (base) {
        this.empty = this.new();
    
        this.constructor = function (key, value) {
            base.constructor.call(this, key, value);
        };
    
        this.that = function () {
            alert("that");
        };
    });
    

    Notice that we can create instances of the class from within the class itself by calling this.new. If this.constructor is not yet defined then it returns a new uninitialized instance. Otherwise it returns a new initialized instance.

    In addition because Xyz is the prototype object we can access Xyz.empty directly (i.e. empty is a static property of Xyz). This also means that static properties are automatically inherited and are no different from normal properties.

    Finally, because the class is accessible from within the class definition as this, you can created nested classes which inherit from the class which they are nested within by using extend as follows:

    var ClassA = extend(Object, function () {
        var ClassB = extend(this, function () {
            // class definition
        });
    
        // rest of the class definition
    
        alert(this.isPrototypeOf(ClassB)); // true
    });
    

    See the demo for yourself: http://jsfiddle.net/Jee96/1/

    这篇关于如何在类声明上实现伪经典继承?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆