首页 文章

原型继承优于经典的好处?

提问于
浏览
250

所以这些年来我终于停止了我的脚,并决定“正确”学习JavaScript . 语言设计中最令人头疼的元素之一是它的继承实现 . 有Ruby经验,我很高兴看到闭包和动态打字;但是对于我的生活来说,无法弄清楚使用其他实例进行继承的对象实例会带来什么好处 .

5 回答

  • 2

    我知道这个答案迟了3年但我真的认为目前的答案没有提供关于how prototypal inheritance is better than classical inheritance的足够信息 .

    首先让我们看看JavaScript程序员在保护原型继承时所说的最常见的参数(我从当前的答案池中获取这些参数):

    • 很简单 .

    • 它很强大 .

    • 它导致更小,更少冗余的代码 .

    • 对于动态语言来说更好's dynamic and hence it' .

    现在这些论点都是有效的,但是没有人为解释原因而烦恼 . 这就像告诉孩子学习数学很重要 . 当然可以,但孩子肯定不在乎;而且你不能说像数学这样的孩子说这很重要 .

    我认为原型继承的问题在于它是从JavaScript的角度来解释的 . 我喜欢JavaScript,但JavaScript中的原型继承是错误的 . 与经典继承不同,有两种原型继承模式:

    • 原型继承的原型模式 .

    • 原型继承的构造函数模式 .

    不幸的是,JavaScript使用原型继承的构造函数模式 . 这是因为在创建JavaScript时,Brendan Eich(JS的创建者)希望它看起来像Java(具有经典继承):

    我们正在推动它作为Java的小兄弟,因为像Visual Basic这样的补充语言当时是微软语言家族的C语言 .

    这很糟糕,因为当人们在JavaScript中使用构造函数时,他们会想到从其他构造函数继承的构造函数 . 这是错的 . 在原型继承中,对象继承自其他对象 . 构造函数永远不会出现 . 这让大多数人感到困惑 .

    来自像Java这样具有经典继承的语言的人变得更加困惑,因为尽管构造函数看起来像类,但它们的行为并不像类 . 如Douglas Crockford所述:

    这种间接性旨在使语言对经过专业训练的程序员更熟悉,但未能做到这一点,正如我们从Java程序员对JavaScript的非常低级的看法中看到的那样 . JavaScript的构造函数模式并没有吸引经典人群 . 它还掩盖了JavaScript真正的原型性质 . 因此,很少有程序员知道如何有效地使用该语言 .

    你有它 . 直接从马的嘴里 .

    True Prototypal Inheritance

    原型继承完全是关于对象的 . 对象从其他对象继承属性 . 这里的所有都是它的 . 有两种使用原型继承创建对象的方法:

    • 创建一个全新的对象 .

    • 克隆现有对象并对其进行扩展 .

    Note: JavaScript提供了两种克隆对象的方法 - delegationconcatenation . 从此以后,我将使用"clone"这个词专门通过委托来引用继承,而单词"copy"则通过连接专门引用继承 .

    足够的谈话 . 我们来看一些例子 . 假设我有一个半径为 5 的圆:

    var circle = {
        radius: 5
    };
    

    我们可以从半径计算圆的面积和周长:

    circle.area = function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    };
    
    circle.circumference = function () {
        return 2 * Math.PI * this.radius;
    };
    

    现在我想创建另一个半径为 10 的圆 . 一种方法是:

    var circle2 = {
        radius: 10,
        area: circle.area,
        circumference: circle.circumference
    };
    

    但是,JavaScript提供了更好的方法 - delegation . Crockford的Object.create函数用于执行此操作:

    var circle2 = Object.create(circle);
    circle2.radius = 10;
    

    就这样 . 您刚刚在JavaScript中进行了原型继承 . 那不简单吗?你拿一个物体,克隆它,改变你需要的东西,然后嘿嘿 - 你得到了一个全新的物体 .

    现在你可能会问,“这怎么这么简单?每当我想要创建一个新的圆圈时,我需要克隆 circle 并手动为它指定一个半径” . 那么解决方案就是使用一个函数为你做繁重的工作:

    function createCircle(radius) {
        var newCircle = Object.create(circle);
        newCircle.radius = radius;
        return newCircle;
    }
    
    var circle2 = createCircle(10);
    

    实际上,您可以将所有这些组合成一个对象文字,如下所示:

    var circle = {
        radius: 5,
        create: function (radius) {
            var circle = Object.create(this);
            circle.radius = radius;
            return circle;
        },
        area: function () {
            var radius = this.radius;
            return Math.PI * radius * radius;
        },
        circumference: function () {
            return 2 * Math.PI * this.radius;
        }
    };
    
    var circle2 = circle.create(10);
    

    JavaScript中的原型继承

    如果您在上面的程序中注意到 create 函数创建了一个克隆 circle ,为其分配一个新的 radius 然后返回它 . 这正是构造函数在JavaScript中的作用:

    function Circle(radius) {
        this.radius = radius;
    }
    
    Circle.prototype.area = function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    };
    
    Circle.prototype.circumference = function () {         
        return 2 * Math.PI * this.radius;
    };
    
    var circle = new Circle(5);
    var circle2 = new Circle(10);
    

    JavaScript中的构造函数模式是反转的原型模式 . 您可以创建构造函数,而不是创建对象 . new 关键字将构造函数内的 this 指针绑定到构造函数的 prototype 的克隆 .

    听起来很混乱?这是因为JavaScript中的构造函数模式不必要地使事情复杂化 . 这是大多数程序员难以理解的 .

    他们没有考虑从其他对象继承的对象,而是认为构造函数继承自其他构造函数,然后变得完全混淆 .

    还有很多其他原因可以避免JavaScript中的构造函数模式 . 你可以在我的博客文章中阅读它们:Constructors vs Prototypes


    那么原型继承优于经典继承有什么好处呢?让我们再次讨论最常见的论点,并解释原因 .

    1.原型继承很简单

    CMS在他的回答中指出:

    在我看来,原型继承的主要好处是它的简单性 .

    让我们考虑一下我们刚刚做了什么 . 我们创建了一个半径为 5 的对象 circle . 然后我们克隆它并给克隆半径 10 .

    因此,我们只需要两件事就可以使原型继承工作:

    • 一种创建新对象的方法(例如对象文字) .

    • 一种扩展现有对象的方法(例如 Object.create ) .

    相比之下,经典继承要复杂得多 . 在经典继承中,你有:

    • 课程 .

    • 对象 .

    • 接口 .

    • 抽象类 .

    • 最终课程 .

    • 虚拟基类 .

    • 构造函数 .

    • 析构函数 .

    你明白了 . 关键是原型继承更容易理解,更容易实现,更容易推理 .

    正如Steve Yegge在他的经典博客文章“Portrait of a N00b”中所说:

    元数据是任何其他类型的描述或模型 . 代码中的注释只是计算的自然语言描述 . 元数据元数据的原因在于它并非绝对必要 . 如果我有一条带有一些谱系文书的狗,而我丢失了文书工作,我仍然有一只完全有效的狗 .

    在同样的意义上,类只是元数据 . 继承不严格要求类 . 然而,有些人(通常是n00bs)找到更舒适的课程 . 它给了他们一种虚假的安全感 .

    嗯,我们也知道静态类型只是元数据 . 它们是针对两种读者的专门评论:程序员和编译器 . 静态类型讲述了有关计算的故事,可能是为了帮助两个读者组理解程序的意图 . 但是静态类型可以在运行时抛弃,因为最终它们只是风格化的注释 . 他们就像是谱系文书工作:它可能会使某种不安全的性格类型对他们的狗更开心,但狗肯定不会在意 .

    正如我之前所说,课程给人一种虚假的安全感 . 例如,即使您的代码非常清晰,您在Java中也会获得太多 NullPointerException . 我发现经典继承通常会妨碍编程,但也许只是Java . Python有一个惊人的经典继承系统 .

    2.原型继承是强大的

    大多数来自古典背景的程序员认为经典继承比原型继承更强大,因为它具有:

    • 私有变量 .

    • 多重继承 .

    这种说法是错误的 . 我们已经知道JavaScript支持private variables via closures,但是多重继承呢? JavaScript中的对象只有一个原型 .

    事实是原型继承支持从多个原型继承 . 原型继承只是意味着一个对象继承自另一个对象 . 实际上有two ways to implement prototypal inheritance

    • 委托或差异继承

    • 克隆或连接继承

    是JavaScript只允许对象委托给另一个对象 . 但是,它允许您复制任意数量的对象的属性 . 例如_.extend就是这样做的 .

    当然,许多程序员不认为这是真正的继承,因为instanceofisPrototypeOf否则说 . 但是,通过存储一系列原型可以很容易地解决这个问题在通过串联从原型继承的每个对象上:

    function copyOf(object, prototype) {
        var prototypes = object.prototypes;
        var prototypeOf = Object.isPrototypeOf;
        return prototypes.indexOf(prototype) >= 0 ||
            prototypes.some(prototypeOf, prototype);
    }
    

    因此,原型继承与经典继承一样强大 . 实际上它比经典继承更强大,因为在原型继承中,您可以手动选择要复制的属性以及从不同原型中省略哪些属性 .

    在经典继承中,选择要继承的属性是不可能的(或者至少是非常困难的) . 他们使用虚拟基类和接口来解决the diamond problem .

    然而,在JavaScript中,您很可能永远不会听到钻石问题,因为您可以精确控制要继承的属性以及原型 .

    3.原型继承不太冗余

    这一点有点难以解释,因为经典继承不一定会导致更多的冗余代码 . 实际上,继承(无论是经典还是原型)用于减少代码中的冗余 .

    一个论点可能是大多数具有经典继承的编程语言是静态类型的,并且需要用户显式声明类型(与具有隐式静态类型的Haskell不同) . 因此,这会导致更详细的代码 .

    Java因这种行为而臭名昭着 . 我清楚地记得Bob Nystrom在他关于Pratt Parsers的博客文章中提到了以下轶事:

    你必须爱上Java的“请在一式四份签署”官僚程度 .

    再说一次,我认为这只是因为Java糟透了 .

    一个有效的论点是,并非所有具有经典继承的语言都支持多重继承 . 再次想到Java . 是Java有接口,但这还不够 . 有时你真的需要多重继承 .

    由于原型继承允许多重继承,因此如果使用原型继承而不是使用具有经典继承但没有多重继承的语言编写,那么需要多继承的代码就不那么多了 .

    4.原型继承是动态的

    原型继承最重要的优点之一是您可以在创建原型后为其添加新属性 . 这允许您向原型添加新方法,该方法将自动提供给委托给该原型的所有对象 .

    这在经典继承中是不可能的,因为一旦创建了类,就无法在运行时修改它 . 这可能是原型继承优于经典继承的最大优势,它应该是最重要的 . 但是我喜欢最好的保存 .

    结论

    原型继承很重要 . 让JavaScript程序员了解为什么放弃原型继承的构造函数模式以支持原型继承的原型模式是很重要的 .

    我们需要开始正确地教授JavaScript,这意味着向新程序员展示如何使用原型模式而不是构造函数模式编写代码 .

    使用原型模式不仅可以更容易地解释原型继承,而且还可以使更好的程序员 .

    如果您喜欢这个答案,那么您还应该阅读我的博客文章“Why Prototypal Inheritance Matters” . 相信我,你不会失望的 .

  • 33

    请允许我实际回答内联问题 .

    原型继承具有以下优点:

    • 它更适合动态语言,因为继承与它所处的环境一样动态 . (这里对JavaScript的适用性应该很明显 . )这允许你快速做事,比如在没有大量基础设施的情况下自定义类码 .

    • 实现原型对象方案比传统的类/对象二分法更容易 .

    • 它消除了对象模型周围复杂的尖锐边缘的需要,如"metaclasses"(我从来没有我喜欢的类别......对不起!)或"eigenvalues"等 .

    但它有以下缺点:

    • 类型检查原型语言是非常非常困难的 . 大多数"type checking"原型语言都是纯粹的运行时"duck typing"样式检查 . 这不适合所有环境 .

    • 同样难以通过静态(或通常甚至动态!)分析来优化方法调度 . 它很容易非常低效 . (我强调: can )非常低效 .

    • 类似地,对象创建可以(并且通常是)在a中慢得多比较传统的类/对象二分法方案中的原型语言 .

    我想你可以在上面的行之间进行阅读,并提出传统类/对象方案的相应优缺点 . 当然,每个区域都有更多,所以我将剩下的留给其他人回答 .

  • 9

    IMO原型继承的主要好处是它的简单性 .

    语言的原型性质可能会使经过专业训练的人感到困惑,但事实证明,这实际上是一个非常简单而有力的概念,differential inheritance .

    您不需要进行分类,代码更小,冗余更少,对象继承自其他更通用的对象 .

    如果你原型思考你会很快注意到你不需要课程......

    原型继承在不久的将来会更受欢迎,ECMAScript 5th Edition规范引入了Object.create方法,它允许您以一种非常简单的方式生成一个从另一个继承的新对象实例:

    var obj = Object.create(baseInstance);
    

    所有浏览器供应商都在实施该标准的新版本,我认为我们将开始看到更纯粹的原型继承......

  • 509

    这两种方法之间的选择真的不多 . 要掌握的基本思想是,当JavaScript引擎被赋予要读取的对象的属性时,它首先检查实例,如果缺少该属性,它将检查原型链 . 这是一个显示原型和经典之间差异的例子:

    Prototypal

    var single = { status: "Single" },
        princeWilliam = Object.create(single),
        cliffRichard = Object.create(single);
    
    console.log(Object.keys(princeWilliam).length); // 0
    console.log(Object.keys(cliffRichard).length); // 0
    
    // Marriage event occurs
    princeWilliam.status = "Married";
    
    console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
    console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)
    

    Classical with instance methods (效率低,因为每个实例都存储它自己的属性)

    function Single() {
        this.status = "Single";
    }
    
    var princeWilliam = new Single(),
        cliffRichard = new Single();
    
    console.log(Object.keys(princeWilliam).length); // 1
    console.log(Object.keys(cliffRichard).length); // 1
    

    Efficient classical

    function Single() {
    }
    
    Single.prototype.status = "Single";
    
    var princeWilliam = new Single(),
        cliffRichard = new Single();
    
    princeWilliam.status = "Married";
    
    console.log(Object.keys(princeWilliam).length); // 1
    console.log(Object.keys(cliffRichard).length); // 0
    console.log(cliffRichard.status); // "Single"
    

    如您所见,由于可以操作以古典风格声明的“类”原型,因此使用原型继承确实没有任何好处 . 它是经典方法的一个子集 .

  • 26

    Web开发:原型继承与经典继承

    http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html

    经典Vs原型继承 - Stack Overflow

    Classical Vs prototypal inheritance

相关问题