www.webdeveloper.com
Results 1 to 8 of 8

Thread: OOP in Javascript

  1. #1
    Join Date
    Oct 2012
    Posts
    4

    OOP in Javascript

    Hey all,

    So I'm new to Javascript, but I've been working on a programming language with similar semantics to C#, and I want to add a JS trans-compiler. I've been looking through various ways of achieving OOP in Javascript, but I'm not entirely sure if it's going to work further down the line.

    Let me start with an example. Here's a very simple bit of code in my language:

    Code:
    // test.fd
    // this code will be put in package 'test'
    class Foo
    {
        float bar;
    
        - test
        {
            int x = 3;
        }
    }
    It outputs this:
    Code:
    var test = {
        function Foo(){
            this.bar = 0.0;
        };
    
        foo.prototype.test = function(){
            var x = 3;
        };
    }

    Now I have a few of questions. Currently, when it compiles the class it creates the js function 'Foo()', which I see is really behaving as a constructor. I'm thinking I should create this by default if the user doesn't create a default constructor, in which case it is used. But what if I wanted to have multiple constructors for the same class? Will they have to have different names, and will I have to recreate all methods and properties for each constructor?

    My next question is, what is the best way to achieve inheritance in JS? My language assumes all methods are virtual so it should hopefully make polymorphism easier.

    Thanks all,
    Zotoaster

  2. #2
    Join Date
    Jul 2003
    Location
    The City of Roses
    Posts
    2,503
    Quote Originally Posted by Zotoaster View Post
    But what if I wanted to have multiple constructors for the same class? Will they have to have different names, and will I have to recreate all methods and properties for each constructor?
    JavaScript doesn't support method overloading. Plus, since it's a loosely typed language, your ability to detect different forms of invocation is limited. The best you could do is something like this:

    PHP Code:
    function Foo(param) {
        if (
    typeof param == 'string') {
            
    Foo1.apply(thisarguments);
        } else if (
    param instanceof SomeType) {
            
    Foo2.apply(thisarguments);
        }
    }

    function 
    Foo1(param) {
        
    // ...
    }

    function 
    Foo2(param) {
        
    // ...

    Quote Originally Posted by Zotoaster View Post
    My next question is, what is the best way to achieve inheritance in JS?
    Unfortunately this isn't a simple answer. Most people are unsatisfied with the native way inheritance is expressed in JavaScript, so there's a plethora of patterns and mini-libraries to do inheritance. The simplest looks like this:

    PHP Code:
    function Animal(name) {
        
    this.name name;
    }

    // New Animal instances inherit from Animal.prototype,
    // so any methods and properties we attach here
    // will be available to all instances.
    Animal.prototype.sayName = function () {
        
    console.log(this.name);
    };

    function 
    Dog(name) {
        
    // Manually call super constructor
        
    Animal.call(thisname);
    }

    // Replace Dog's prototype with a new Animal instance.
    // Since new Dog instances inherit from Dog.prototype,
    // and Dog.prototype is now an Animal instance,
    // which inherits from Animal.prototype,
    // Dog instances now also inherit from Animal.prototype.
    Dog.prototype = new Animal();

    Dog.prototype.bark = function () {
        
    console.log('Woof!');
    }; 
    It's verbose. But the biggest functional problem is that you need to create a nameless, stateless Animal instance just to set up inheritance. This problem has been rectified relatively recently with the Object.create method, which we can simulate for older browsers.

    PHP Code:
    if (!Object.create) {
        
    Object.create = function (o) {
            function 
    F() {}
            
    F.prototype o;
            return new 
    F();
        };
    }

    // ...

    Dog.prototype Object.create(Animal.prototype); 
    That's better, but it's still verbose. People have created functions similar to the one below to make things cleaner.

    PHP Code:
    function extend(superFnprotoProperties) {
        
    // The extend function creates the constructor function for you
        
    function SubFn() {
            
    // Its only job is to call a specially-named method
            
    this.init.apply(thisarguments);
        }
        
        
    // Set up inheritance
        
    if (superFn) {
            
    SubFn.prototype Object.create(superFn.prototype);
        }
        
        
    // Copy protoProperties into the SubFn's prototype
        
    for (var propertyName in protoProperties) {
            if (
    protoProperties.hasOwnProperty(propertyName)) {
                
    SubFn.prototype[propertyName] = protoProperties[propertyName];
            }
        }
        
        
    // And finally return the new constructor function
        
    return SubFn;

    And you'd use it like so:

    PHP Code:
    var Animal extend(null, {
        
    init: function (name) {
            
    this.name name;
        },
        
        
    sayName: function () {
            
    console.log(this.name);
        }
    });

    var 
    Dog extend(Animal, {
        
    bark: function () {
            
    console.log('Woof!');
        }
    }); 
    Much cleaner. More intricate variations of the above is the de facto standard pattern in JavaScript right now.

    I'll admit, though, that I use a different pattern. My reasons are, first, the above pattern doesn't handle static methods or static properties very well. And second, the only reason a type is defined in two parts -- a constructor function and a prototype object -- is because a prototypal language was trying to disguise itself as a classical language. This has been coined as pseudo-classical, and it was done to appeal to the Java crowd. But as the JavaScript community has become more comfortable with prototypal inheritance, I'm more inclined to use an inheritance mechanism that's truer to the prototypal style... No constructor functions. Just objects inheriting from other objects.

    PHP Code:
    var Base = {
        
    extend: function (typeDefinition) {
            
    // Spawn
            
    var subtype Object.create(this);

            
    // Augment
            
    if (typeDefinition) {
                
    subtype.mixIn(typeDefinition);
            }

            
    // Reference supertype
            
    subtype.$super this;

            return 
    subtype;
        },

        
    create: function () {
            var 
    instance this.extend();
            
    instance.init.apply(instancearguments);

            return 
    instance;
        },

        
    init: function () {
        },

        
    mixIn: function (properties) {
            for (var 
    propertyName in properties) {
                if (
    properties.hasOwnProperty(propertyName)) {
                    
    this[propertyName] = properties[propertyName];
                }
            }
        }
    }; 
    And you'd use it like so:

    PHP Code:
    var Animal Base.extend({
        
    init: function (name) {
            
    this.name name;
        },
        
        
    sayName: function () {
            
    console.log(this.name);
        }
    });

    var 
    Dog Animal.extend({
        
    bark: function () {
            
    console.log('Woof!');
        }
    }); 
    for(split(//,'))*))91:+9.*4:1A1+9,1))2*:..)))2*:31.-1)4131)1))2*:3)"'))
    {for(ord){$i+=$_&7;grep(vec($s,$i++,1)=1,1..($_>>3)-4);}}print"$s\n";

  3. #3
    Join Date
    Oct 2012
    Posts
    4
    Lots of good knowledge in there. I clearly have a lot to learn, but it seems JS is increasingly becoming a more popular target language due to it's cross-platform capabilities, so I'm eager to learn more about using it. Thanks for all the good tips!

  4. #4
    Join Date
    Jul 2008
    Location
    urbana, il
    Posts
    2,787
    if you ask 10 coders "what's the best ____", you should get 10 different answers. That's great, and it shows the flexibility of JS overcoming the "power" of a classical language. Doug Crockford has a lot to say about this, read his many well-written online writings on the subject.

    personally, i think classical is over-rated.
    i also don't like using looping to tack-on props that should be inherited in some fashion.

    recently, i've become a fan of .apply()-based inheritance. I have no idea what it's called (if anything), but it lets me pass arguments at the time of creating the instance (like Classes do) and i can type out the pattern on-demand in a few seconds:

    Code:
    function Animal(name){
     this.name=name;
    }// "parent/super/base" definition:
    
    
    function Dog(){
      Animal.apply(this, arguments);
      this.type="dog";
    }// "subclass" constructor
    
    
    Dog.prototype.bark=function(){
      alert("bow wow");
    };// class-wide method
    
    
    // Let's try it:
    var myDog= new Dog("Spot"); // a new dog, yay!
    myDog.bark();  // shows "bow wow"
    JSON.stringify(myDog); //== {"name":"Spot","type":"dog"}
    though i admit the keyword usage isn't the-most readable, i still think it's a very clean syntax pattern, and it provides "enough" oop-functionality for of my projects.

    this doesn't preserve instanceof functionality, but you can use your own conventions if you need that (most don't).

    you can also use .bind() instead of .apply() to pre-bake arguments to the constructor (currying), which can be nice.

    bottom line: play around with all the inheritance options in JS; it's nice to have several to choose from.
    Last edited by rnd me; 10-29-2012 at 01:44 PM.

  5. #5
    Join Date
    Jul 2003
    Location
    The City of Roses
    Posts
    2,503
    Quote Originally Posted by rnd me View Post
    ...apply()-based inheritance...
    In your example code, you have a method on Dog.prototype, but if you had a method on Animal.prototype, how does that inherit? In the pattern you're describing, I think it only works if the methods are created and attached to each instance within the constructor. The downside of that kind of pattern is that each method is re-created for each instance, rather than one copy of each method that is shared across all instances.
    Last edited by Jeff Mott; 10-29-2012 at 02:05 PM.
    for(split(//,'))*))91:+9.*4:1A1+9,1))2*:..)))2*:31.-1)4131)1))2*:3)"'))
    {for(ord){$i+=$_&7;grep(vec($s,$i++,1)=1,1..($_>>3)-4);}}print"$s\n";

  6. #6
    Join Date
    Oct 2012
    Posts
    4
    Ok folks I've made some progress on it. I found a simple method that helped to do inheritance.

    I've come up with some ideas for inheritance, polymorphism and function overloading. Basically I'm creating a table of all methods in a class. If there is a base class, the table is inherited. If you override a method, you override that table index with the new method. Then you simply call using the method index (which is simple using a compiler, but doesn't help readability, regardless, I've put some auto-generated comments in to help).

    Here's a very simple app written in my language demonstrating creating classes, methods, inheritance, polymorphism and method overloading.
    PHP Code:
    class Foo
    {
        - new
        {
        }
        
        - 
    test
        
    {
            print 
    "bleh";
        }
        
        - 
    test(int x)
        {
            print 
    "hello " x;
        }
    }

    class 
    Bar Foo
    {
        - new
        {
        }
        
        - 
    test(int x)
        {
            print 
    "goodbye " x;
        }
    }

    class 
    App
    {
        - new
        {
            
    Foo t = new Foo();
        }

    Here's the output in javascript.
    PHP Code:
                // Extension utility
                
    var __extends this.__extends || function (db) {
                    function 
    __() { this.constructor d; }
                    
    __.prototype b.prototype;
                    
    d.prototype = new __();
                }

                
    /* Package 'test' */
                
    var test = (function() {

                    
    /* Class 'Foo' */
                    
    var Foo = (function() {

                        
    // Initialise
                        
    function Foo(_argarr_ccall) {

                            
    // Setup v-table
                            
    this._vtable = [];
                            
    this._vtable[0] = function() {        // new()
                            
    };
                            
    this._vtable[1] = function() {        // test()
                                
    document.write('bleh');
                            };
                            
    this._vtable[2] = function(x) {        // test(int)
                                
    document.write('hello ' x);
                            };
                            
    // Constructor dispatch
                            
    this._vtable[_ccall].apply(this_argarr);
                        }

                        
    // call util
                        
    Foo.prototype.callv = function(vindexargs) {
                            
    this._vtable[vindex].apply(thisargs);
                        };

                        return 
    Foo;
                    })();

                    
    /* Class 'Bar' */
                    
    var Bar = (function(_super) {
                        
    __extends(Bar_super);

                        
    // Initialise
                        
    function Bar(_argarr_ccall) {
                            
    _super.apply(thisarguments);

                            
    // Setup v-table
                            
    this._vtable[0] = function() {        // new()
                            
    };
                            
    this._vtable[2] = function(x) {        // test(int)
                                
    document.write('goodbye ' x);
                            };
                            
    // Constructor dispatch
                            
    this._vtable[_ccall].apply(this_argarr);
                        }

                        return 
    Bar;
                    })(
    Foo);

                    
    /* Class 'App' */
                    
    var App = (function() {

                        
    // Initialise
                        
    function App(_argarr_ccall) {

                            
    // Setup v-table
                            
    this._vtable = [];
                            
    this._vtable[0] = function() {        // new()
                                
    var = new Foo([], 0);
                            };
                            
    // Constructor dispatch
                            
    this._vtable[_ccall].apply(this_argarr);
                        }

                        
    // call util
                        
    App.prototype.callv = function(vindexargs) {
                            
    this._vtable[vindex].apply(thisargs);
                        };

                        return 
    App;
                    })();

                    return {
    FooFooBarBarAppApp};
                })();

                
    // begin app
                
    var app = new test.App([], 0); 
    As you can see, most of the code is wrapped in a 'test' module, which is a grouping created by the compiler.

    I haven't quite got round to implementing method calls, but the basic idea is that a method will be called as such:
    PHP Code:
    // pass vtable index and parameters
    obj.callv(2, [42"hello"]); 

    Does anyone see any room for improvement?
    Thanks

  7. #7
    Join Date
    Jul 2003
    Location
    The City of Roses
    Posts
    2,503
    Actually... my first suggestion for improvement is not about the inheritance mechanism, but about code readability. Right from the beginning, in your extends function, variable names such as "b" and "d" are extremely unhelpful. And a lack of comments about what the function does and what parameters it takes doesn't help. I had to rename or rewrite some of your code just to understand what it was doing before I could even begin to analyze it.

    On to the inheritance mechanism. By-and-large, this seems to be the "apply" pattern that rndme described earlier, except for two key differences: 1) Rather than attaching methods to each instance, instead you're attaching methods to a vtable array, which in turn is attached to each instance. What benefit do you get from attaching to the vtable rather than just attaching to the instance? And 2) rather than indexing methods by name, instead you're indexing methods by an arbitrary number. This is going to make the JavaScript output virtually unintelligible. What benefit is that supposed to provide?
    for(split(//,'))*))91:+9.*4:1A1+9,1))2*:..)))2*:31.-1)4131)1))2*:3)"'))
    {for(ord){$i+=$_&7;grep(vec($s,$i++,1)=1,1..($_>>3)-4);}}print"$s\n";

  8. #8
    Join Date
    Oct 2012
    Posts
    4
    Hi Jeff. I'm really just experimenting with different methods for OOP in JS. I've scrapped the v-table idea because it's redundant. The numbers are placed by the compiler so I can just put the number at the end of the method name. Now all methods are named '_Method1' etc. It doesn't improve readability, but my goal here is to be able to overload functions (which I have achieved). I am going to be adding more back-ends to my language so I don't want to be limited by Javascript's capabilities, I'd rather get a slightly uglier output in the end of the day.

    Anyway, here's the same code sample as before (method calls now added):
    PHP Code:
    class Foo 

        - new 
        { 
        } 
         
        - 
    test 
        

            print 
    "bleh"
        } 
         
        - 
    test(int x
        { 
            print 
    "hello " x
        } 


    class 
    Bar Foo 

        - new 
        { 
        } 
         
        - 
    test(int x
        { 
            print 
    "goodbye " x
        } 


    class 
    App 

        - new 
        { 
            
    Foo t = new Bar();
            
    t.test(42);
        } 


    And here's the output. Just ignore the functions at the top:
    PHP Code:
    /* Utilities */
    var __extends this.__extends || function (db) {
        function 
    __() { this.constructor d; }
        
    __.prototype b.prototype;
        
    d.prototype = new __();
    }
    var 
    __clone = (function(){
        return function (
    obj) { Clone.prototype=obj; return new Clone() };
        function Clone(){}
    }());
    function print(
    str) {
        var 
    cons document.getElementById('GameConsole');
        if(
    cons)
            
    cons.value += str '\n';
            
        if(
    window.console != undefined)
            
    window.console.log(str);
    }


    /* Package 'test' */
    var test = (function() {

        
    /* Class 'Foo' */
        
    var Foo = (function() {

            
    // Initialise
            
    function Foo(_argarr_ccall) {
                
    // Constructor dispatch
                
    switch (_ccall) {
                    case 
    0:    this._Foo_Constr0.apply(this_argarr); break;
                    default: break;
                }
            }

            
    /* constructor Foo.new() */
            
    Foo.prototype._Foo_Constr0 = function() {
            };

            
    /* Foo.test() */
            
    Foo.prototype._Method1 = function() {
                print(
    'bleh');
            };

            
    /* Foo.test(int) */
            
    Foo.prototype._Method2 = function(x) {
                print(
    'hello ' x);
            };

            return 
    Foo;
        })();

        
    /* Class 'Bar' */
        
    var Bar = (function(_super) {
            
    __extends(Bar_super);

            
    // Initialise
            
    function Bar(_argarr_ccall) {
                
    // Constructor dispatch
                
    switch (_ccall) {
                    case 
    0:    _super.apply(this, [_argarr0]); this._Bar_Constr0.apply(this_argarr); break;
                    default: break;
                }
            }

            
    /* constructor Bar.new() */
            
    Bar.prototype._Bar_Constr0 = function() {
            };

            
    /* Bar.test(int) */
            
    Bar.prototype._Method2 = function(x) {
                print(
    'goodbye ' x);
            };

            return 
    Bar;
        })(
    Foo);

        
    /* Class 'App' */
        
    var App = (function() {

            
    // Initialise
            
    function App(_argarr_ccall) {
                
    // Constructor dispatch
                
    switch (_ccall) {
                    case 
    0:    this._App_Constr0.apply(this_argarr); break;
                    default: break;
                }
            }

            
    /* constructor App.new() */
            
    App.prototype._App_Constr0 = function() {
                var 
    = new Bar([], 0);
                
    t._Method2(42);
            };

            return 
    App;
        })();

        return {
    FooFooBarBarAppApp};
    })();

    // begin app
    window.onload = function(e) {
        var 
    app = new test.App([], 0);


Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

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



Recent Articles