【问题标题】:Javascript - the difference between parent.call(arguments) and child.prototype = new parent(arguments)?Javascript - parent.call(arguments) 和 child.prototype = new parent(arguments) 之间的区别?
【发布时间】:2014-02-13 15:03:07
【问题描述】:

我正试图完全理解原型继承和 Javascript 的继承系统。

我试图弄清楚这两个示例之间的区别。我猜测它与原型继承链有关,但我很难找到与这两者相关的信息。

示例 1 - call()

function Vehicle(wheels) {
    this.wheels = wheels;
}

function Car(doors) {
    this.doors = doors;
    Vehicle.call(this,4);
}

示例 2 - 原型

function Vehicle(wheels) {
    this.wheels = wheels;
}

function Car(doors) {
    this.doors = doors;
}
Car.prototype = new Vehicle(4);

【问题讨论】:

    标签: javascript inheritance properties call prototype


    【解决方案1】:

    在第一个示例中,您正在初始化 Car 对象,使其具有新 Vehicle 对象的所有相同属性。但是,它不是“真正的”继承,体现在以下方面:

    var car = new Car(2);
    car instanceof Vehicle; // => false
    

    这是第一件事:instanceof 不起作用。这是第二个:

    var car = new Car(2);
    
    Vehicle.prototype.drive = function() {
      console.log('vroom!');
    };
    
    car.drive(); // throws
    

    所以这是第二件事:对Car 实例的访问不会到达原型链到Vehicle 的原型。

    考虑到这一点,我倾向于第二种方法。


    也就是说,您可能实际上想要执行 both,因为您的 Vehicle 构造函数中可能存在每个实例的初始化逻辑,您实际上希望在何时执行你初始化一个Car(这不会仅仅通过设置原型来实现)。

    要理解我的意思:在您的示例中,您总是将4 作为wheels 参数传递给Vehicle 构造函数,这是有道理的。但是假设我们添加另一个可能因实例而异的参数:

    function Vehicle(wheels, color) {
      this.wheels = wheels;
      this.color = color;
    }
    

    现在我们可以采取两种方法:

    function Car(doors, color) {
      this.doors = doors;
      Vehicle.call(this, 4, color);
    }
    
    Car.prototype = new Vehicle();
    

    请注意,在设置Car 的原型时,我们实际上在Car 中显式调用构造函数,而不是向Vehicle 构造函数传递任何内容。这样我们就可以正确设置color

    另一种方法是:

    function Car(doors, color) {
      this.doors = doors;
      this.color = color;
    }
    
    Car.prototype = new Vehicle(4, null);
    

    那也行。

    【讨论】:

    • 例如,如果我们在 Vehicle 构造函数中有一个“model”属性(使用函数参数设置)和一个“drive”方法,那么执行 vehicle.call(model ) 在汽车构造函数中,以及将原型设置为车辆?
    • @Kris:是的,没错!我正在更新我的答案,以便在您发表评论时讨论这个问题。
    • 如果你要设置原型在初始化时调用父构造函数,我推荐使用Child.prototype = Object.create(Parent),这样父构造函数只会被调用一次。跨度>
    • 对不起,如果它偏离了这个特定问题的主题,但是 Child.prototype = Object.create(Parent) 和 Child.prototype = new Parent() 之间有什么区别(除了浏览器支持) ?
    • @Kris:首先,我相信你会打电话给Object.create(Parent.prototype),而不是Object.create(Parent)。但在回答您的问题时,Object.create 方法实际上并没有调用 Parent 构造函数。因此它提供了最“经典”的继承机制(因为在其他 OOP 语言中,您通常不必实例化一个基类来定义一个子类)。
    【解决方案2】:

    解释器添加一个内部引用(如果你愿意,你可以调用proto),它指向相应构造函数的原型。

    如果您运行第一个示例并实例化每个构造函数的两个实例,您最终会得到如下内容:

    这里需要注意的是:

    • carsObj 有一个 Car 构造函数未定义的附加属性。原因是您在 Car 构造函数中的这一行 Vehicle.call(this,4);
    • 构造函数及其实例都指向一个称为原型的对象;这是可以存储共享成员的地方(即方法或共享属性)。此外,这允许通过原型链创建继承层次结构。
    • car 对象的实例将永远无法访问 Vehicle 原型的成员。

    最后一个要点是限制应用正确继承的内容。

    同样,如果您运行第二个示例,内存可能如下所示:

    区别非常明显。 Car 构造函数的原型属性指向 Vehicle 构造函数的一个实例,Car 构造函数的所有实例也是如此。这是由于这条线Car.prototype = new Vehicle(4);

    此结构允许 Car 构造函数的实例访问在父类的原型上定义的所有属性。这是由于 JavaScript 在查找属性时如何遍历链接的原型。

    这种技术的缺点是 Car 实例不从 Vehicle 构造函数继承任何实例属性。它只继承共享成员。

    更好的解决方案是结合以上两种技术;那就是:

    function Vehicle(wheels) {
       this.wheels = wheels;
    }
    
    function Car(doors) {
       this.doors = doors;
       Vehicle.call(this, 4)
    }
    
    Car.prototype = Object.create(Vehicle);
    

    这样,Car 构造函数的实例将继承共享成员和实例成员。

    【讨论】:

    • 我想我明白了......所以没有 .prototype,Car 不能继承固定的 Vehicle 属性和方法( Vehicle.drive() ),但没有 call() 它可以'不设置每个 Vehicle 实例不同的属性( color = this.color )?
    猜你喜欢
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 2015-08-10
    • 1970-01-01
    • 1970-01-01
    • 2012-06-20
    相关资源
    最近更新 更多