【问题标题】:prototypal inheritance concept in javascript as a prototype based languagejavascript中的原型继承概念作为基于原型的语言
【发布时间】:2012-08-25 10:18:46
【问题描述】:

你知道 Javascript 是一个 prototype-based programming language
我读过一些关于 Javascript 及其 原型继承 概念的书籍,但是:

“如果你不能向一个六岁的孩子解释它,你自己真的不明白。”。好吧,我试图向一个 22 岁的朋友解释 JavaScript 原型概念,完全失败了!

你会如何向一个对这个主题非常感兴趣的 6 岁孩子解释它?
我在 Stack Overflow 中看到了一些示例,但没有帮助。

【问题讨论】:

  • 我不同意这样一种说法,即如果你不能向一个 6 岁的孩子解释它,你就不会理解它。例如,递归是一个固有的简单概念,但对于许多人来说却难以理解,即使已经用非常简单的术语向他们解释过。不过,这仍然是一个好问题,+1。

标签: javascript prototypejs prototype prototype-programming prototypal-inheritance


【解决方案1】:

经典继承是关于扩展事物的类型。假设你有一堂课,比如Bike。当您想扩展行为时,您必须设计一种新型自行车(如MotorBike)。

这就像建造一座工厂 - 你制作了许多蓝图,以及引用这些蓝图的蓝图,但要骑上一辆,你必须拿走蓝图并从中制造一些东西。

基于原型的继承是关于扩展事物本身。假设您有一种制作Bike 对象的方法。您将其中一个Bikes 带入车库,然后将喷气发动机绑在上面。

这不符合蓝图。这是您对这辆特定自行车所做的事情。但是你的朋友看到​​你的装置也想要一个。因此,您不必为您的新设计制定蓝图,而是贴上一个标有“JetBike factory”的标志,然后开始制作更多。每次您不记得某些东西如何组合在一起时,您只需查看原始自行车即可,而不是查看蓝图。您的原始自行车是原型自行车,所有新自行车都是基于它。

现在,对于一个真正的 6 岁孩子来说,这可能就是我要停下来的地方(如果我还没有丢失它们的话),但实际上基于原型的继承不只是构造副本,它甚至可以做一些事情更酷 - 它实际上将新的JetBike 对象链接到您车库中的原始原型自行车。如果您更换原型自行车的避震器,那么您所有朋友的自行车也会神奇地更换避震器。

让我们看一些 JS-ish 伪代码:

function Bike() {
    this.wheels = 2;
}
Bike.prototype = {
    ride: function() {
        // Ride the bike
    },
    crash: function() {
        // Fall off the bike
    }
};

function JetBike() {
    this.engines = 2;
}
// Start with an ordinary bike
JetBike.prototype = new Bike();
// Modify it
JetBike.prototype.fly = function () {
    // Engage thrusters and head for the ramp
};

【讨论】:

  • 非常好的解释和类比!
  • 我见过的关于原型继承的最佳解释,感谢您的贡献
【解决方案2】:

Javascript 是一种面向对象的语言,它的独特之处在于它没有类。相反,我们使用函数来创建对象。

所有函数都有一个原型,您使用该函数创建的所有对象都将从该原型继承所有属性和方法。由于 Javascript 没有类,因此您可以使用要继承的实际对象(而不是类)来执行继承。您可以将函数的原型设置为对象,从而使您使用该函数创建的所有对象都可以继承该函数原型对象的所有方法和属性。

所以如果我有一个创建对象的函数:

function Foo() {

}
Foo.prototype.someProperty = 'blahblahblah';

您可以创建另一个函数来创建对象,并通过将函数原型设置为该对象来允许它继承对象的属性和方法。

function Bar() {

}
Bar.prototype = new Foo();

然后你就可以访问所有被继承的东西了。

var bar = new Bar();
alert( bar.someProperty ); // blahblahblah

【讨论】:

    【解决方案3】:

    有趣的挑战:-)

    首先,我绝不会试图用文字向任何人解释这一点,但我会尝试一下 :-)

    “原型继承就像一只可以从其他口袋妖怪那里窃取力量的口袋妖怪。”

    认为您可以创建自己的口袋妖怪。您可以决定它有多大,它是什么颜色等(构造函数)。然后你可以赋予那个口袋妖怪力量(原型)。您可以根据需要生成任意数量的这些口袋妖怪。原型继承给你的是让一个、几个或所有这些口袋妖怪从其他口袋妖怪那里窃取力量的可能性。您甚至可以从已经从其他口袋妖怪那里窃取力量的口袋妖怪身上窃取力量。这创造了一系列全新的超强口袋妖怪。

    也许有点傻,但它反映了原型继承是多么强大......在口袋妖怪意义上:-)

    【讨论】:

    • “偷窃”是错误的词,我想。如果口袋妖怪 B 窃取了口袋妖怪 A 的力量,这表明口袋妖怪 A 不再拥有从它身上窃取的力量。 “继承”是一个更好的词,但从 6 岁儿童的角度来看可能不是最好的词。
    • 是的,我完全同意,但在游戏中窃取权力通常意味着“继承”。你不能偷力量,但你可以偷武器……但是,是的,这完全是另一回事;-)
    • 好点。从技术角度来看,“窃取”是一个不恰当的术语。但是 6 岁的孩子更有可能思考游戏术语,在这种情况下,“偷”是非常合适的。
    【解决方案4】:

    与大多数其他面向对象的语言不同,JavaScript 实际上没有概念 的类。在大多数其他面向对象的语言中,您会实例化特定类的实例,但在 JavaScript 中并非如此。
    在 JavaScript 中,对象可以创建新对象,并且对象可以从其他对象继承。
    这整个概念称为原型继承

    但是我们如何制作一个对象呢?
    只需使用{} 创建一个通用对象。

    var a = {};
    a.prop = "myprop";
    console.log(a); //Object { prop="myprop" }
    

    您不能创建a 的实例,因为它不是函数。换句话说,它没有特殊的内部方法[[Construct]]

    在 JavaScript 中,任何函数也可以实例化为对象。下面的函数是一个简单的函数,它接受一个名称并将其保存到当前上下文:

    function User( name ) {
       this.name = name;
    }
    

    我们可以看到User是Function的实例:

    alert(User instanceof Function); //true
    

    使用指定的名称创建该函数的新实例:

    var me = new User( "My Name" ); 
    

    我们可以看到它的name已经被设置为它自己的一个属性:

    alert( me.name == "My Name" ); //true
    

    它是User 对象的一个​​实例:

    alert( me.constructor == User ); //true
    

    现在,既然User() 只是一个函数,那么当我们这样对待它时会发生什么?

    User( "Test" );
    

    由于未设置其this 上下文,它默认为全局window 对象,这意味着window.name 等于提供的name

    alert( window.name == "Test" ); //true
    

    constructor 属性存在于每个对象上,并且始终指向创建它的函数。这样,您应该能够有效地复制对象,创建一个具有相同基类但属性不同的新对象。下面是一个例子:

    var you = new me.constructor();
    

    我们可以看到,构造函数其实是一样的:

    alert( me.constructor == you.constructor ); //true
    

    原型和公共方法

    Prototype 仅包含一个对象,该对象将作为其父对象的所有新副本的基本引用。本质上,原型的任何属性都将在该对象的每个实例上可用。这个创建/引用过程为我们提供了一个廉价的继承版本。
    由于对象原型只是一个对象,因此您可以将新属性附加到它们,就像任何其他对象一样。将新属性附加到原型将使它们成为从原始原型实例化的每个对象的一部分,从而有效地公开所有属性。示例:

    function User( name, age ){
       this.name = name;
       this.age = age;
    }
    

    向构造函数的原型属性添加方法和属性是向构造函数生成的对象添加功能的另一种方法。让我们再添加一个属性CardNo 和一个getName() 方法:

    User.prototype.CardNo='12345';
    User.prototype.getName = function(){
      return this.name;
    };
    

    并在原型中添加另一个功能。请注意,上下文将在实例化对象内。

    User.prototype.getAge = function(){
       return this.age;
    };
    

    实例化一个新的用户对象:

    var user = new User( "Bob", 44 );
    

    我们可以看到,我们附加的两个方法是与对象相关的,具有适当的上下文:

    alert( user.getName() == "Bob" ); //true
    alert( user.getAge() == 44 ); //true
    

    所以 javascript 中的每个函数都有一个原型属性。它的初始值是一个空对象 ({})。请注意,通用对象(不是函数)没有原型属性:

    alert( user.prototype ); //undefined (and it is not useful even you define it)
    

    代表团

    当您尝试访问user 的属性时,例如user.name,JavaScript 引擎将查看对象的所有属性,搜索名为name 的对象,如果找到,则返回其值:

    alert( user.name );
    

    如果 javascript 引擎找不到该属性怎么办?它将识别用于创建此对象的构造函数的原型(与您执行user.constructor.prototype 相同)。如果在原型中找到该属性,则使用此属性:

    alert(user.CardNo); // "12345"
    

    等等……
    如果您想区分对象自己的属性和原型的属性,请使用hasOwnProperty()。试试:

    alert( user.hasOwnProperty('name') ); //true
    alert( user.hasOwnProperty('CardNo') ); //false
    

    私有方法

    当您直接为函数设置属性时,它将是私有的。示例:

    function User()
    {
        var prop="myprop";
        function disp(){
           alert("this is a private function!");
        }
    }
    var we = new User();
    alert(we.prop); //undefined
    we.disp(); // Fails, as disp is not a public property of the object
    

    特权方法

    特权方法是 Douglas Crockford 创造的一个术语,指的是能够 查看和操作私有变量(在一个对象内),同时仍然可以访问 用户作为公共方法。示例:
    创建一个新的 User 对象构造函数:

    function User( name, age ) {
       //Attempt to figure out the year that the user was born:
       var year = (new Date()).getFullYear() – age;
    
       //Create a new Privileged method that has access to the year variable, but is still publically available:
       this.getYearBorn = function(){
          return year;
       };
    }
    

    创建用户对象的新实例:

    var user = new User( "Bob", 44 );
    

    验证返回的年份是否正确:

    alert( user.getYearBorn() == 1962 ); //true
    

    请注意,我们无法访问对象的私有年份属性:

    alert( user.year == null ); //true
    

    本质上,特权方法是动态生成的方法,因为它们是添加的 到对象在运行时,而不是在代码第一次编译时。虽然这种技术在计算上比将简单方法绑定到对象原型更昂贵,但它也更加强大和灵活。

    静态方法

    静态方法背后的前提实际上与任何其他普通函数的前提相同。然而,主要区别在于函数作为对象的静态属性存在。作为一个属性,它们在该对象的实例的上下文中是不可访问的;它们仅在与主对象本身相同的上下文中可用。对于熟悉传统类继承的人来说,这有点像静态类方法。
    实际上,以这种方式编写代码的唯一好处是保持对象命名空间的整洁。
    附加到 User 对象的静态方法:

    function User(){}
    User.cloneUser = function( user ) {
       //Create, and return, a new user
       return new User( user.getName(), user.getAge() );
    };
    

    cloneUser 函数只能由User 访问:

    var me = new User();
    me.cloneUser(me); //Uncaught TypeError: Object #<User> has no method 'cloneUser' 
    

    【讨论】:

      【解决方案5】:

      原型继承就像一个儿子走到哪里都背着父亲。如果有人问儿子:“你的鞋子是什么颜色的?”他会用他的鞋子颜色回应,除非他赤脚,否则他会用他爸爸的鞋子颜色回应。

      【讨论】:

        【解决方案6】:
        /* Here is simple way how to inherit objects properties and methods from others object by using prototyping inheritance in plain java script.*/
        (function() {  
              // get dom elements for display output`enter code here
              var engTeacherPara = document.getElementById("engTeacher");
              var chemTeacherPara = document.getElementById("chemTeacher");
              // base class 
              var SchoolStaff = function(name, id) {
                  this.name = name;
                  this.id = id;
                }
                // method on the SchoolStaff object
              SchoolStaff.prototype.print = function() {
                return "Name : " + this.name + " Employee id: " + this.id;
              }
            
              SchoolStaff.prototype.sayHello = function() {
                return "Hello Mr : " + this.name;
              }
            
              // sub class engTeacher
              var EngTeacher = function(name, id, salary) {
                  SchoolStaff.call(this, name, id);
                  this.salary = salary;
                }
                // Inherit the SchoolStaff prototype
              EngTeacher.prototype = Object.create(SchoolStaff.prototype);
            
              // Set the engTeacher constructor to engTeacher object
              EngTeacher.prototype.constructor = EngTeacher;
            
              // method on engTeacher object
              EngTeacher.prototype.print = function() {
                return "Name : " + this.name + " Salary : " + this.salary + " Employee id: " + this.id;
              }
            
              // sub class chemTeacher
              var ChemTeacher = function(name, id, salary, bonus) {
                  EngTeacher.call(this, name, id, salary);
                  this.bonus = bonus;
                }
                // Inherit the SchoolStaff prototype
              ChemTeacher.prototype = Object.create(EngTeacher.prototype);
            
              // Set the ChemTeacher constructor to ChemTeacher object
              ChemTeacher.prototype.constructor = ChemTeacher;
            
              // method on engTeacher object
              ChemTeacher.prototype.print = function() {
                console.log("Name : " + this.name + " Salary : " + this.salary + " Employee id: " + this.id + " bonus : " + this.bonus);
              }
            
              // create new objcts and check sub class have base class methods access
              var schoolStaff = new SchoolStaff("Base Class", 100);
              console.log(schoolStaff.sayHello()); // Hello Mr : Base Class
            
              var engTeacher = new EngTeacher("Eng Teacher", 1001, 20000);
              engTeacherPara.innerHTML = engTeacher.sayHello(); // Hello Mr : Eng Teacher  
            
              var chemTeacher = new ChemTeacher("Chem Teacher", 1001, 30000, 4000);
              chemTeacherPara.innerHTML = chemTeacher.sayHello(); // Hello Mr : Chem Teacher
            })();
        

        【讨论】:

        • 请解释这有什么帮助。不鼓励仅使用代码回答。
        • 为每一行添加了注释,以便于理解原型继承在普通 java 脚本中是如何工作的。
        猜你喜欢
        • 2015-01-20
        • 1970-01-01
        • 2021-01-27
        • 1970-01-01
        • 2010-10-23
        • 2010-09-28
        • 2010-09-28
        • 2018-02-21
        相关资源
        最近更新 更多