Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 3 of 3
  1. #1
    New to the CF scene
    Join Date
    Aug 2011
    Location
    Copenhagen, Denmark
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts

    Array in prototype gives wrong results

    The code below looks elementary, but it gives absurd results.
    Can you tell how I should have written it?
    I am slowly beginning to understand why it fails. But I do not understand why
    the JavaScript tutorials I've studied never warned against this trap.

    Any ship needs a list of crew members. The total number of persons onboard must be known
    too, for the case the ship sinks.

    We place these two properties in a prototype, so that we won't have to repeat them
    for each kind of ship.

    A passenger list is needed only if the ship is a passenger ship, so we decide not to
    place the passenger list in a prototype.

    Code:
    function Ship() {
        this.crew = [];
        this.persons = 0;
    }
    
    function PassengerShip(name) {
        this.name = name;
        this.passengers = [];
    }
    
    PassengerShip.prototype = new Ship;
    Passengers and crew arrive one by one shortly before departure.
    The first thing they do onboard is to check in:

    Code:
    Ship.prototype.checkin = function(list, person) {
        this[list].push(person);
        ++this.persons;
    }
    (Instead of the push function, we could as well have used a statement such as
    Code:
    this[list][ this[list].length ] = person;
    I have tried. Same wrong result.)

    Let us launch two passenger ships and check some persons in.

    Code:
    var msFloat = new PassengerShip("M/S Float");
    
    msFloat.checkin("crew", "Captain");
    msFloat.checkin("crew", "Cook");
    
    msFloat.checkin("passengers", "Alice");
    msFloat.checkin("passengers", "Bob");
    
    var msFlawed = new PassengerShip("M/S Flawed");
    
    msFlawed.checkin("crew", "Capt'n");
    msFlawed.checkin("crew", "Sailor");
    
    msFlawed.checkin("passengers", "Charlie");
    The shipping company wants a report when everybody has checked in.
    But I'm not sure they will like it:

    Code:
    Ship.prototype.report = function() {
        function makeList(ship, list) {
            if (!ship[list]) return (ship.name + " has no list of " + list + ".\n");
            if (!ship[list].length) return (ship.name + "  has no " + list + ".\n");
            return (list + ":  " + ship[list].join(", ") + "\n");
        }
        alert("\nPre-Departure Report For  '"  + this.name + "'\n\n" +
              makeList(this, "passengers")     + makeList(this, "crew") +
              "\nNumber of persons onboard:  " + this.persons + "\n\n");
    }
    
    msFloat.report();  // OK: Alice, Bob as passengers; Captain, Cook as crew
    msFlawed.report(); // WRONG CREW: Captain, Cook, Capt'n, Sailor
    The number of persons (passengers + crew) is reported correctly for both ships,
    and so is the passenger list.

    But the "M/S Flawed" reports four crew members. Two of those never checked in on that ship.

    How can such a thing happen? What kind of blunder did I make?

    An "edit+execute" version of the code is available at bjarne.altervista.org/sailor.html
    together with a little testoutput, some considerations based on that testoutput,
    and a workaround (which is not the same as a solution - it looks ridiculous).
    It seems as if arrays placed in prototypes do not always work as expected.

    Can that be true? The testoutput suggests that JavaScript sometimes forgets to create
    a much-needed own property in the this object, and instead changes the prototype.
    But that sounds incredible, doesn't it?
    I am not too familiar with the deeper theory of JavaScript, and I hope to get a response.

    Here is the code concatenated and embedded:

    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html><head><title>Array In JavaScript Prototype</title></head>
    <body>
     <script type="text/javascript" language="JavaScript">
    
    function Ship() {
        this.crew = [];
        this.persons = 0;
    }
    
    function PassengerShip(name) {
        this.name = name;
        this.passengers = [];
    }
    
    PassengerShip.prototype = new Ship;
    
    Ship.prototype.checkin = function(list, person) {
        this[list].push(person);
        ++this.persons;
    }
    
    Ship.prototype.report = function() {
        function makeList(ship, list) {
            if (!ship[list]) return (ship.name + " has no list of " + list + ".\n");
            if (!ship[list].length) return (ship.name + "  has no " + list + ".\n");
            return (list + ":  " + ship[list].join(", ") + "\n");
        }
        alert("\nPre-Departure Report For  '" + this.name + "'\n\n" +
            makeList(this, "passengers") + makeList(this, "crew") +
              "\nNumber of persons onboard:  " + this.persons + "\n\n");
    }
    
    var msFloat = new PassengerShip( "M/S Float" );
    msFloat.checkin("crew", "Captain");
    msFloat.checkin("crew", "Cook");
    msFloat.checkin("passengers", "Alice");
    msFloat.checkin("passengers", "Bob");
    msFloat.report();  // OK: Alice, Bob as passengers; Captain, Cook as crew
    
    var msFlawed = new PassengerShip( "M/S Flawed" );
    msFlawed.checkin("crew", "Capt'n");
    msFlawed.checkin("crew", "Sailor");
    msFlawed.checkin("passengers", "Charlie");
    msFlawed.report(); // WRONG CREW: Captain, Cook, Capt'n, Sailor
    
    </script>
    </body>
    </html>
    I have spent much time struggling with this kind of error, which is reproducible in 5 different browsers.
    This is the first time I have been able to reproduce it in a form that can be presented to others.

    Is there a well-established programming practice that can prevent it? What do experienced
    JavaScript programmers do?

    Regards
    Bjarne Pagh Byrnak

  2. #2
    Senior Coder
    Join Date
    Aug 2010
    Posts
    1,248
    Thanks
    44
    Thanked 275 Times in 273 Posts
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="generator" content="daveyerwin">
    <title>Untitled</title>
    
    <script type="text/javascript">
    
    function Ship() {
        this.crew = [];
        this.persons = 0;
    }
    
    function PassengerShip(name) {
        Ship.call(this);
        this.name = name;
        this.passengers = [];
    }
    
    PassengerShip.prototype = new Ship;
    
    Ship.prototype.checkin = function(list, person) {
        this[list].push(person);
        ++this.persons;
    }
    
    Ship.prototype.report = function() {
        function makeList(ship, list) {
            if (!ship[list]) return (ship.name + " has no list of " + list + ".\n");
            if (!ship[list].length) return (ship.name + "  has no " + list + ".\n");
            return (list + ":  " + ship[list].join(", ") + "\n");
        }
        alert("\nPre-Departure Report For  '" + this.name + "'\n\n" +
            makeList(this, "passengers") + makeList(this, "crew") +
              "\nNumber of persons onboard:  " + this.persons + "\n\n");
    }
    
    var msFloat = new PassengerShip( "M/S Float" );
    msFloat.checkin("crew", "Captain");
    msFloat.checkin("crew", "Cook");
    msFloat.checkin("passengers", "Alice");
    msFloat.checkin("passengers", "Bob");
    msFloat.report();  // OK: Alice, Bob as passengers; Captain, Cook as crew
    
    var msFlawed = new PassengerShip( "M/S Flawed" );
    msFlawed.checkin("crew", "Capt'n");
    msFlawed.checkin("crew", "Sailor");
    msFlawed.checkin("passengers", "Charlie");
    msFlawed.report(); // CORRECT CREW: Capt'n, Sailor
    
    </script>
    </body>
    </html>
    PassengerShip.prototype = new Ship;
    That line is the only time a new Crew
    array is made in your code so both
    M/S Float and M/S Flawed
    shared the same array

    Ship.call(this);
    That line calls Ship each
    time an instance of
    PassengerShip is creted
    so each instannce gets
    its own crew array.
    Last edited by DaveyErwin; 11-25-2011 at 02:34 AM.

  3. Users who have thanked DaveyErwin for this post:

    Bjarne_Byrnak (11-25-2011)

  4. #3
    New to the CF scene
    Join Date
    Aug 2011
    Location
    Copenhagen, Denmark
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts
    Thanks for a prompt and constructive answer.

    Your proposed usage of call allows the problem to be addressed from
    within the constructors and kept away from the methods.

    I just tried it out, and found it to work, in an example extended with
    two cruise ships that use PassengerShip as their prototype. The testoutput
    is correct and satisfactory. (It is shown at the above URL.)

    Still, programmers have to do things for arrays that operators such as ++ do
    automatically.

    Fine, as long as we know what to do and why. To modify the language definition,
    and let giant-sized arrays be copied into instantiations automatically, might
    create more problems than it would solve - we could soon run out of memory.

    So, I regard my problem as solved.

    Regards
    Bjarne Pagh Byrnak
    Last edited by Bjarne_Byrnak; 11-25-2011 at 10:54 PM.


 

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •