代码之家  ›  专栏  ›  技术社区  ›  Dasha Salo

了解JavaScript中的原型继承

  •  166
  • Dasha Salo  · 技术社区  · 15 年前

    我对javascript OOP是个新手。你能解释一下以下代码块之间的区别吗?我测试了两个模块。最佳实践是什么?为什么?

    第一块:

    function Car(name){
        this.Name = name;
    }
    
    Car.prototype.Drive = function(){
        console.log("My name is " + this.Name + " and I'm driving.");
    }
    
    SuperCar.prototype = new Car();
    SuperCar.prototype.constructor = SuperCar;
    
    function SuperCar(name){
        Car.call(this, name);
    }
    
    SuperCar.prototype.Fly = function(){
        console.log("My name is " + this.Name + " and I'm flying!");
    }
    
    var myCar = new Car("Car");
    myCar.Drive();
    
    var mySuperCar = new SuperCar("SuperCar");
    mySuperCar.Drive();
    mySuperCar.Fly();

    第二街区:

    function Car(name){
        this.Name = name;
        this.Drive = function(){ 
            console.log("My name is " + this.Name + " and I'm driving.");
        }
    }
    
    SuperCar.prototype = new Car();
    
    function SuperCar(name){
        Car.call(this, name);
        this.Fly = function(){
            console.log("My name is " + this.Name + " and I'm flying!");
        }
    }
    
    var myCar = new Car("Car");
    myCar.Drive();
    
    var mySuperCar = new SuperCar("SuperCar");
    mySuperCar.Drive();
    mySuperCar.Fly();

    为什么作者添加了 Drive Fly 方法使用 prototype ,并且没有将它们声明为 this.Drive 方法内部 Car 类和AS this.Fly SuperCar 班级?

    为什么 SuperCar.prototype.constructor 需要重新设置为 超级跑车 ?是 constructor 属性重写时间 原型 设定好了吗?我把这句话注释掉了,没什么变化。

    为什么叫 Car.call(this, name); 超级跑车 构造函数?不会的属性和方法 小型车 当我继承的时候

    var myCar = new Car("Car");
    
    6 回复  |  直到 6 年前
        1
  •  81
  •   mik01aj    11 年前

    这两个块的不同之处在于第一个示例中 Drive() 在第二种方法时只存在一次 驱动器() 每个实例都存在(每次 new Car() 函数 drive() 将再次创建)。或者说不同的是,第一个使用原型来存储函数,第二个使用构造函数。函数的查找是构造函数,然后是原型。所以你要查一下 驱动器() 不管它是在构造函数中还是在原型中,它都会找到它。使用原型更有效,因为通常每个类型只需要一个函数。

    这个 new 调用javascript会自动在原型中设置构造函数。如果要重写原型,则必须手动设置构造函数。

    javascript中的继承与 super . 因此,如果您有一个子类,唯一调用超级构造函数的机会就是使用它的名称。

        2
  •  139
  •   Community Neeleshkumar S    7 年前

    添加到 Norbert Hartl's answer 不需要supercar.prototype.constructor,但有些人将其用作获取对象构造函数(本例中为supercar对象)的方便方法。

    仅从第一个示例开始,car.call(this,name)在supercar构造函数函数中,因为执行此操作时:

    var mySuperCar = new SuperCar("SuperCar");
    

    这就是JavaScript所做的:

    1. 一个新的空白对象被实例化。
    2. 新物体的内部原型被设置为汽车。
    3. supercar构造函数函数运行。
    4. 在mysupercar中返回并设置完成的对象。

    注意到javascript并没有为您调用car。原型机和它们一样,任何你没有为超级跑车设置的属性或方法都会在车内被查找。有时这是好的,例如超级跑车没有驱动方法,但它可以共享汽车的一个,所以所有超级跑车将使用相同的驱动方法。有时你不想分享,就像每个超级跑车都有自己的名字。那么,如何将每个超级跑车的名称设置为它自己的东西呢?可以在supercar构造函数函数内设置此.name:

    function SuperCar(name){
        this.Name = name;
    }
    

    这行,不过等一下。我们在汽车制造厂做的不是完全一样吗?不想重复我们自己。既然汽车已经定了名字,我们就叫它吧。

    function SuperCar(name){
        this = Car(name);
    }
    

    哎呀,你可不想换这个特别的 this 对象引用。还记得4个步骤吗?抓住Javascript提供给你的对象,因为这是保持超级跑车对象和汽车之间珍贵的内部原型链接的唯一方法。那么,我们如何设置名称,不重复我们自己,也不丢弃我们新的超级跑车对象,javascript花了这么多特别的精力来为我们做准备呢?

    两件事。一:意义 是灵活的。第二:汽车是一种功能。我们可以调用car,而不是使用一个原始的、新的实例化对象,而是使用一个supercar对象。这给了我们最终的解决方案,这是您问题的第一个示例的一部分:

    function SuperCar(name){
        Car.call(this, name);
    }
    

    作为函数,可以使用函数的 call method 从而改变了 在车内到超级跑车的实例,我们正在建立。急板地!现在每个超级跑车都有自己的名称属性。

    包裹起来, Car.call(this, name) 在supercar构造函数中,每个新的supercar对象都有自己的唯一名称属性,但不会复制车中已有的代码。

    一旦你理解了原型就不可怕了,但是它们与经典的类/继承OOP模型一点也不相似。我写了一篇关于 the prototypes concept in JavaScript . 它是为一个使用javascript的游戏引擎而编写的,但它与firefox使用的javascript引擎是相同的,所以它应该都是相关的。希望这有帮助。

        3
  •  8
  •   Rob    15 年前

    诺伯特,你应该注意到你的第一个例子几乎就是道格拉斯·克罗克福德所说的伪经典继承。需要注意的事项:

    1. 您将两次调用汽车构造者,一次是从supercar.prototype=new car()行调用,另一次是从“构造函数窃取”行汽车调用。调用(这…您可以创建一个助手方法来继承原型,而汽车构造者只需运行一次就可以提高设置的效率。
    2. supercar.prototype.constructor=supercar行将允许您使用instanceof来标识构造函数。有些人想要这个其他人只是避免使用instanceof
    3. 在super(例如car)上定义的引用var,如:var arr=['one','two']将被所有实例共享。这意味着inst1.arr.push['three']、inst2.arr.push['four']等将在所有实例中显示!本质上,你可能不想要的静态行为。
    4. 第二个块定义了构造函数中的fly方法。这意味着每次调用它时,都会创建一个“方法对象”。最好使用方法的原型!但是,如果愿意的话,您可以将它保存在构造函数中-您只需要进行一次保护,因此实际上只需初始化原型文本一次(伪):if(supercar.prototype.mymethod!)='函数')…然后定义原型文本。
    5. “为什么要调用car.call(this,name)…”“:我没有时间仔细查看您的代码,因此我可能是错的,但这通常是为了让每个实例都能保持自己的状态来修复我上面描述的原型链接的“静态”行为问题。

    最后,我想说的是,我有几个在这里工作的TDD JavaScript继承代码示例: TDD JavaScript Inheritance Code and Essay 我很想得到你的反馈,因为我希望改进它并保持开源。其目标是帮助经典程序员快速掌握JavaScript,同时补充Crockford和Zakas的书。

        4
  •  1
  •   Jeff Ober    15 年前

    我不是100%确定,但我相信区别在于第二个示例只是将汽车类的内容复制到超级跑车对象中,而第一个示例将超级跑车原型链接到汽车类中,因此对汽车类的运行时更改也会影响超级跑车类。

        5
  •  1
  •   Jack Sane    9 年前
    function abc() {
    }
    

    为函数abc创建的原型方法和属性

    abc.prototype.testProperty = 'Hi, I am prototype property';
    abc.prototype.testMethod = function() { 
       alert('Hi i am prototype method')
    }
    

    为函数abc创建新实例

    var objx = new abc();
    
    console.log(objx.testProperty); // will display Hi, I am prototype property
    objx.testMethod();// alert Hi i am prototype method
    
    var objy = new abc();
    
    console.log(objy.testProperty); //will display Hi, I am prototype property
    objy.testProperty = Hi, I am over-ridden prototype property
    
    console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property
    

    http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html

        6
  •  0
  •   trincot Jakube    6 年前

    这里有几个问题:

    你能解释一下以下代码块之间的区别吗?我测试了两个模块。

    第一个只创建一个 Drive 函数,第二个函数创建其中两个:一个在 myCar 另一个在 mySuperCar .

    以下是在执行第一个或第二个块时产生不同结果的代码:

    myCar.Fly === mySuperCar.Fly // true only in the first case
    Object.keys(myCar).includes("Fly") // true only in the second case
    Object.keys(Car.prototype).length === 0 // true only in the second case
    

    最佳实践是什么?为什么?
    作者为什么补充 驱动器 Fly 方法使用 prototype ,但不将其声明为 this.Drive 内部方法 Car 类和 this.Fly 在里面 SuperCar 班级?

    最好在原型上定义方法,因为:

    • 每个方法只定义一次
    • 每个方法也可用于在不执行构造函数的情况下创建的实例(在调用 Object.create(Car.prototype) ;
    • 您可以检查在实例原型链的哪个级别定义了某个方法。

    为什么 SuperCar.prototype.constructor 需要重新设置为 超级跑车 ?是 constructor 属性重写时间 原型 设定好了吗?我把这句话注释掉了,没什么变化。

    这个 构造函数 原型 被设置。但是 new Car() 小型车 所以如果你设定 新的Car() SuperCar.prototype ,那么显然 supercar.prototype.constructor公司 小型车 .

    只要你不重新分配给 原型 存在不变性: Constructor.prototype.constructor === Constructor . 例如,对于 小型车 : Car.prototype.constructor === Car 但同样适用于 Array , Object , String ……等。

    但是如果您将另一个对象重新分配给 原型 不变性被打破了。通常这不是问题(如您所注意到的),但最好恢复它,因为它回答了问题。 “创建新实例时,哪个构造函数使用此原型对象?” 有些代码可能会做这种检查并依赖于它。见 "Why is it necessary to set the prototype constructor?" 对于这种情况。

    为什么叫 Car.call(this, name); 在里面 超级跑车 构造函数?当我这样做的时候,汽车的属性和方法不会被“继承”吗?

    var myCar = new Car("Car");
    

    如果你不这样做 呼叫(这个,名字); 那么你的 超级跑车 实例将没有名称属性。你当然可以决定 this.name = name; 相反,它只复制 小型车 构造函数,但在更复杂的情况下,这样的代码重复是不好的做法。

    打电话没用 new Car(name) 超级跑车 构造函数,因为它将创建 另一个 对象,而您确实需要扩展 this 对象。不使用 new (使用) call 相反)你真的告诉 小型车 不作为构造函数运行的函数(即 创建一个新对象),但要使用传递给它的对象。

    时代变了

    在Javascript的现代版本中,您可以使用 super(name) 而不是 Car.call(this, name) :

    function SuperCar(name) {
        super(name);
    }
    

    今天,您还可以使用 class 语法和编写问题的第一个代码块如下:

    class Car {
        constructor(name) {
            this.name = name;
        }
        drive() {
            console.log(`My name is ${this.name} and I'm driving.`);
        }
    }
    
    class SuperCar extends Car {
        constructor(name) {
            super(name);
        }
        fly() {
            console.log(`My name is ${this.name} and I'm flying!`);
        }
    }
    
    const myCar = new Car("Car");
    myCar.drive();
    
    const mySuperCar = new SuperCar("SuperCar");
    mySuperCar.drive();
    mySuperCar.fly();

    注意你甚至不必提及 原型 实现目标的属性。这个 class ... extends 语法还负责设置 prototype.constructor 属性作为问题中的第一个块。