不涉及模拟经典继承的原型继承示例?

6

看看这个:https://dev59.com/eWs05IYBdhLWcg3wCNlG#12816953 - Lorenzo Polidori
4个回答

6
继承就是继承,所以你可以从任何一种继承中获得相同的基本功能。
JavaScript中原型继承的一个好处是允许在运行时动态添加新方法或修改旧方法,并使其对所有对象可用(而不会增加每个对象的内存开销)。
这可能是危险的,特别是当内置方法(例如String或Object)以向后破坏(或向前威胁)的方式被覆盖时。
String.prototype.replace = function () {
    return 'hahaha';
};

但是,当某些浏览器或库的实现功能不足或性能滞后时,它可以发挥强大的作用。

它还有助于库的模块化、可扩展性和改进。如果你包含了某个人的库,并发现他们对某个方法的实现可以更好地优化,你可以插入他们的代码而不会篡改它,同时仍然有能力改进它或添加功能到它,并使所有在他们的库之外定义的对象受益(至少在你开始将它添加到原型时)。一个库甚至可以基于用户偏好交换实现(虽然这可能不是一个通常的好主意,因为它可能会干扰使用该方法的其他代码),或让他们动态定义他们想要使用的方法名称。

并且,即使在“类”内部,原型行为也会发挥作用,因为你可以利用直接存储在对象上的便利性(尽管在这种情况下它会增加内存,可能更好地创建一个新的类——但它仍然可以很方便)。

function Dog (type) {
    if (type === 'poodle') {
        this.bark = function () {
            alert('(yapyapyap)');
        };
    }
}
Dog.prototype.bark = function () {
    alert('(woof)');
};

var muffy = new Dog('poodle');
muffy.bark(); // '(yapyapyap)'
var rover = new Dog();
rover.bark(); // '(woof)'

JavaScript中的原型方法可以动态地更改或替换原型,这意味着你可以在运行时动态地创建新类,而不像那些更传统的语言一样。至少这种方法提供了更简洁的表现力。
function Creature () {}
Creature.prototype.respire = function () { return 'oooooh'; };

function createClass (o, f) {
    f = f || function f () {}
    f.prototype = (typeof o === 'function') ? o.prototype : o.constructor.prototype;
    f.prototype.constructor = f;
    return f;
}

var animals = ['Dog', 'Tiger', 'Lion', 'Frog', 'Kangaroo'];
animals.forEach(function (animal) {
    window[animal] = createClass(Creature);
});
var rover = new Dog();

最后,您可以避免严格的is-a层次结构,只借用您需要的部分,同时仍然利用可继承的功能:
function createMixinClass (old, constructor, newMethods) {
    if (typeof constructor === 'object') {
        newMethods = constructor;
        constructor = null;
    }
    var proto = old.prototype, constructor = constructor || function () {};

    for (var m in proto) {
        constructor.prototype[m] = proto[m];
    }
    for (var method in newMethods) {
        if (!newMethods[method]) {
            delete constructor.prototype[method];
        }
        else {
            constructor.prototype[method] = newMethods[method];
        }
    }
    return constructor;
}

var Cat = createMixinClass(Dog, {bark:null, meow: function () {alert('meow');}});
var kitty = new Cat();

简而言之,我认为并没有什么特别不同的东西让你能够处理新类型的问题,但它提供了更多的灵活性,尤其是一些可重复使用的实用程序很方便。

非常感谢你的周到回答。你强调了运行时动态,这是我没有充分考虑过的。 - Samuel Danielson
很高兴看到有人提出问题,试图更深入地了解一个主题。 - Brett Zamir
继承就是继承,所以你可以从任一种继承中获得相同的基本功能。但实际上并不是这样的...... 请看我的解释,了解为什么应该选择原型继承。 - Eric Elliott

2

这并不是一个“教学”示例,而是原型继承在实际应用中的例子。jQuery插件就是一种“真实世界”中的应用。$.fn实际上是神奇的jQuery集合的原型,而jQuery插件会向其中添加方法,以便为任何jQuery集合添加功能。


1

在实际应用中有很多原型继承的例子。值得注意的是,jQuery使用它。每次进行jQuery选择时,都会使用委托原型来继承jQuery方法。它也被广泛应用于各种Web应用程序,包括Adobe的Creative Cloud平台,雅虎的许多产品等等...

事实上,JavaScript中的所有经典继承实现都实际上采用了原型继承来模拟经典继承--这只是为方便那些更熟悉经典继承而不是原型继承的程序员。原型继承非常灵活,可以轻松地使用原型继承来模仿经典继承的特性。反之则不然。

原型继承简单地意味着一个对象直接从另一个对象继承。下面是一个例子:

var switchProto = {
  isOn: function isOn() {
    return this.state;
  },

  toggle: function toggle() {
    this.state = !this.state;
    return this;
  },

  state: false
},
switch1 = Object.create(switchProto),
switch2 = Object.create(switchProto);

通常将Object.create()调用放在工厂函数中,以使对象实例化更加方便。

经典继承存在许多问题,而原型继承则不存在这些问题,例如:

经典继承

紧密耦合。继承是OO设计中最紧密的耦合形式。派生类对其祖先类有着深入的了解。

不灵活的层次结构(也称必要的重复)。单个父级层次结构很少能够描述所有可能的用例。最终,所有层次结构都对于新用例“错误”——这个问题需要代码重复。

多重继承很复杂。通常希望从多个父级继承。该过程非常复杂,其实现与单一继承的过程不一致,这使得阅读和理解更加困难。

脆弱的架构。由于紧密耦合,通常很难重构具有“错误”设计的类,因为许多现有功能依赖于现有设计。

猩猩/香蕉问题。通常,您不希望继承父级的某些部分。子类允许您覆盖父级中的属性,但它不允许您选择要继承的属性。

原型继承

为了理解原型继承如何解决这些问题,您首先应该了解有两种不同类型的原型继承。JavaScript 支持两种:

委托。如果在实例上找不到属性,则会在实例的原型上搜索该属性。这使您可以在许多实例之间共享方法,从而免费提供享元模式

拼接。动态添加属性到对象的能力使您可以自由地将任何属性从一个对象复制到另一个对象,全部或选择性地。

您可以结合这两种形式的原型继承来实现非常灵活的代码重用系统。实际上,它非常灵活,以至于使用原型轻松实现经典继承。反之则不然。

原型继承允许您使用大多数经典语言中的重要功能。在 JavaScript 中,闭包和工厂函数允许您实现私有状态,并且函数继承可以轻松地与原型组合,以添加支持数据隐私的混入。

原型继承的一些优点:

松耦合。实例不需要直接引用父类或原型。虽然可以存储对象原型的引用,但这是不明智的,因为它会在对象层次结构中促进紧密耦合——这是经典继承最大的陷阱之一。

扁平化层次结构。使用串联和委托,使用原型面向对象编程可以轻松保持继承层次结构扁平 - 拥有单个级别的对象委托和单个实例,没有对父类的引用。

简单的多重继承。从多个祖先继承就像使用串联组合多个原型的属性来形成新对象或新对象的新委托那样容易。

灵活的架构。由于使用原型面向对象编程可以选择性地继承,因此您不必担心“错误设计”问题。新类可以从任意组合的源对象中继承任何组合的属性。由于层次结构变得扁平化的容易度,一个地方的更改不一定会导致整个后代对象链中的涟漪。

不再有大猩猩。选择性继承消除了大猩猩香蕉问题。

我不知道传统继承在原型继承方面有什么优势。如果有人知道,请启发我。


经典继承相对于原型继承的优势,目前我能想到的只有安全性。与原型继承链相关的JavaScript库经常需要进行安全补丁修复以防止攻击。 - AJP
1
在JavaScript中,类继承是基于原型链实现的,因此在JavaScript中安全性上实际上没有区别。在那些通过复制而不是引用进行类继承的语言中,超类中的代码更改会导致与JS中原型链相似的漏洞类别。过多代码的意外继承也会导致类似于Solidity中Parity钱包漏洞的系列漏洞。 - Eric Elliott

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接