Sunday, November 22, 2015

Quick Language Reference: JavaScript

JavaScript used to be viewed as a half-broken scripting language, though with node.js and language improvements over the last 15 years, it's been increasingly viewed as a platform for serious development (and a target for language compilation).

    See: http://www.crockford.com/JavaScript/JavaScript.html
    "JavaScript is lisp in C's clothing"

Chrome ships with an interactive console.  The last variable is output, no need for alert() or console.log()  for example

    a = 1
    a
    >> 1

write debug messages to console

    console.log('hello')

note on error handling:

    JavaScript will stop executing a process if it hits an error such as a missing variable or function.

    Chrome: check the bottom right of chrome for a red (!)
    Firefox debugger: firebug


data types:

    undefined (empty var place holder)
    null (empty object place holder)
    boolean
    number
    string
    object
    function  (type of object)
    array     (type of object)


    NOTE: there are no hashes (associative arrays) in JavaScript, only objects

    ALSO, there are primative value types

        boolean, number, and string

    then, there are also (different) boxed types with methods

        Boolean, Number, String

    eg: "hello" instanceof String; //false

The typeOf operator tells you which built-in type a variable is.  null is slightly couter intuitive, though was intended to be used only with objects.

    typeOf null == "object"

IOW, there are two primative null values: null and undefined.  null was intended for objects only.

SO...

    If you want to test for null
             you can use === (a strict equals test).
    If you want to test the type for undefined, Boolean, String, Number, or Function,
            you can use typeof.
    For anything else, you can use instanceof.

the instanceOf operator tests if an Object is a instance of a class

    a instanceOf SomeClass


java/javascript inconsistencies (in case you have to switch between the two)

    in java:

        array.length
        String.length()  // function

    in JavaScript both are properties:

        array.length
        string.length

    if you try to use length() you will get error :)

    TypeError: number is not a function

"false" values

    false null undefined "" 0 NaN

null vs undefined

    null !== undefined

    null is an object with a type of null
    undefined is the default value of an undefined reference or hash element
        (iow, undefined is what most languages call 'null')

    avoid using
    if ( a != null ) {
        // because this does type conversion and can throw errors
    }

 
    if(typeof neverDeclared == "undefined") //no errors
    if(neverDeclared == null) //throws ReferenceError: var is not defined

    undefined means "does not exist". null is an intentionally set value.

    null can be used as a flag, if a variable has been initialized, but value is unknown or an exceptional case:

    var o = null;
    if ( test_something()  ) {
        o = 21;
    }
    if ( o != null ) {
        // no exception, initialized ok
    }


semicolon at end of line

    optional, but probably best practice

declare a variable

    var a = '';

    Notes:
    Variables are bound to the function they are defined in.
     If you don't use `var` then the variable scope is global
     //avoid polluting the global namespace.

     i_am_global = 'prepare for namspace collision'


comments

    // single line
    /* multiline */
    <!-- single line (html style, generally leads to confusion)


initialize an object if undefined

  var pagedata = pagedata || {};


strings

     // there's no difference between double and single quotes
 
    var example1 = "I'm a String";
    var example2 = 'So am I.';

   // they are handy if you want to avoid escaping the quotes
   // using c-style backspace escape sequences 
    var example3 = 'I\'m a String too, but I have to escape the "\'" character ';


string weirdness

    JavaScript actually represents text strings in two different ways:  as primitive string values, and as string objects:

        var a = "I am a string!";
        alert(typeof a); // Will output "string"
        var b = new String("I am also a string!");
        alert(typeof b); // Will output "object"

    string are also char arrays (like in PHP)

    > console.log( "test"[0] )
    > t

    string type versus string object
    new String("a") == new String("a") will return false (compares object refs)
    "a" == "a" will return true (compares primatives)


    String types are a value type in JS, so they can't have any properties attached to them, no prototype, etc. Any attempt to access a property on them is technically performing the JS [[ToObject]] conversion (in essence new String).

    Easy way of distinguishing the difference is (in a browser)

        a = "foo"
        a.b = "bar"
        alert("a.b = " + a.b); //Undefined

        A = new String("foo");
        A.b = "bar";
        alert("A.b = " + A.b); // bar
        Additionally while

        "foo" == new String("foo")

    is true, it is only true due to the implicit type conversions of the == operator

        "foo" === new String("foo")

    will fail.


length of string is property

    .length


functions


    function your_function (arg1,arg2,arg3) {
        // do something
    }


    // call function
    your_function(1,2,3);

    // refer to function (without calling)
    your_function

    // the function reference is just like any other variable
    var f = your_function;
    f(1,2,3); // call it


using an anonymous, isolated function scope and namespace

    (function (){
        // your namespace is protected here  
        alert(1)  
    })();


vars declaration must be at the top of a function
 
    var x = 3;
    (function (){
        console.log(x + 2); //NaN - x is not defined
        var x = 0; //var declaration at end of function ?
    }());

    This is because "x" is redefined inside the function. This means that the interpreter automatically moves the var statements to the top of the function (you're welcome) and we end up executing this instead:

    var x = 3;
    (function (){
        var x;
        console.log(x + 2); //NaN - x is not defined
        x = 0;
    }());


introspection/reflection

    in Chrome console

        console.log( yourObject )


    // programmatically

    Object.getOwnPropertyNames(Object.prototype) gives :

    ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__"]

    Object.getOwnPropertyNames(Object) gives :

    ["length", "name", "arguments", "caller", "prototype", "keys", "create", "defineProperty", "defineProperties", "freeze", "getPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyNames", "is", "isExtensible", "isFrozen", "isSealed", "preventExtensions", "seal"]


conditionals and type juggling

   // the if statement mirrors c/java syntax
   if ( boolean ) {

   } else if ( another_boolean ) {

   } else {

   }

    // note: types are automatically "juggled" or converted
    // 1 == "1"

    if ( a == b ) {  console.log(1) } // with type-juggling
    if ( a === b ) {  console.log(1) } // with strict type-check

    you may want to avoid "new String()" to avoid these quirks


regex

    var regex = /\S*\.gif/i;
    var results = test.match(regex);
    var file = results[0];     // file.gif


convert string to number type

    parseFloat ( s );

    parseInt ( s, 10 ); // must pass in base 10
    js is pickier (compared to php) when it comes to type juggling
    always use a radix:

        parseInt("08")     == 0    // not kidding :)
        parseInt("08",10)  == 8 // what you'd expect


get elements, for example, on document

    node.getElementById( id ) ;
    node.getElementsByTagName( 'a' ) ;

replace html

    your_html_node.innerHTML= 'your string';


arrays

    Warning: don't use trailing commas in arrays
    This will break in IE: which follows the spec in this point out of spite :-)

        var wrong = [1,2,3 ,<<<<<<<];  // can't have have , at end

    Most tutorials start out introducing you to arrays as such:

        // don't do this
        var myArray = new Array(10);

    Current best-practice avoids the "new" keyword on Javascript primitives. If you want to create a new Array simply use brackets [] like this:

        // better
        var myArray = [];

    // initialize ten elements [ undefined, ... ]
    var badArray = new Array(10); // Creates an empty Array that's sized for 10 elements.

    // faster
    var goodArray= [10];          // Creates an Array with 10 as the first element.

    NOTE: Javascript arrays are passed by reference, assigned by reference


literal syntax

    JavaScript has the types Object, Array, Boolean, Number, String, and Function. Each has its own literal syntax and so the explicit constructor is never required.

    var a = new Object();

    a.greet = "hello"; // avoid
    var a = { greet: "hello" }; // better

    var b = new Boolean(true);// avoid
    var b = true; // better

    var c = new Array("one", "two");// avoid
    var c = ["one", "two"]; // better

    var d = new String("hello");// avoid
    var d = "hello" // better

    var e = new Function("greeting", "alert(greeting);");// avoid
    var e = function(greeting) { alert(greeting); }; // better


array splice

    The splice() method adds/removes items to/from an array, and returns the removed item(s).

    syntax:
    array.splice(index,howmanyremoved,item1,.....,itemX)

    index    Required. An integer that specifies at what position to add/remove items, Use negative values to specify the position from the end of the array
    howmanyremoved    Required. The number of items to be removed. If set to 0, no items will be removed
    item1, ..., itemX    Optional. The new item(s) to be added to the array

    example:
    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.splice(2,0,"Lemon","Kiwi");
    // The result of fruits will be:
    // Banana,Orange,Lemon,Kiwi,Apple,Mango


escape for url data context

    function urlencode (str) {
        if ( str === null || str === undefined ) {
            return '';
        }
        var str = new String(str);
        str = encodeURIComponent(str);
        str = str.replace(/!/g, '%21');
        str = str.replace(/'/g, '%27');
        str = str.replace(/\(/g, '%28');
        str = str.replace(/~/g, '%7E');
        str = str.replace(/\)/g, '%29');
        str = str.replace(/\*/g, '%2A');
        return str.valueOf();
    }

reverse url encoding

    function urldecode(s) {
        if ( s ) {
            s = new String(s);
            s = decodeURIComponent(s.replace(/\+/g, " "));
            return s.valueOf();
        }
        return '';
    };



escape for html data context

    // white space options
    var HTML_NBSP = 1;
    var HTML_BR = 2;
    var HTML_NBSP_BR = 3; // 1 + 2
    var HTML_BR_NBSP = 3; // 1 + 2

    function htmlencode (str, ws) {
        if ( str === null || str === undefined ) {
            return '';
        }
        var str = new String(str);
        str = str.replace(/&/g, "&");
        str = str.replace(/>/g, ">");
        str = str.replace(/</g, "<");
        str = str.replace(/"/g, """);
        str = str.replace(/'/g, "'");
        // ecode whitespace
        if ( ws == 1 || ws == 3 ) {
            str = str.replace(/ /g, " ");
            str = str.replace(/\t/g, "    ");
        }
        // also insert line breaks
        if ( ws == 2 || ws == 3 ) {
            str = str.replace(/\n/g, "<br />");
        }
        return str.valueOf();
    }


similar to php string escape

    function addslashes(str) {
        if ( str ) {
            str = new String( str );
            var str = str.replace(/\\/g, '\\\\').
                replace(/\u0008/g, '\\b').
                replace(/\t/g, '\\t').
                replace(/\n/g, '\\n').
                replace(/\f/g, '\\f').
                replace(/\r/g, '\\r').
                replace(/'/g, '\\\'').
                replace(/"/g, '\\"');
            return str.valueOf();
        }
        return '';
    }


zipped/compressed sources

    most browsers will support gzipped source

        <script type="text/JavaScript" src="prototype.js.gz"></script>


dynamically writing to a pop up window:

    function write_to_pop_up () {
        new_window = open("","TITLE","width=640,height=480,left=10,top=10,resizable=1,scrollbars=1");
        new_window.document.open();
        new_window.document.write("Hello World");
        new_window.document.close();
    }

xml tags in strings

    when contructing xml tags in the page,
    use a backslash to escape the close tag like this:

    xml = "<tag>" + value + "<\/tag>";

    This is not exactly a bug bu a quirk in the SGML markup spec
    close-tags are interpreted in a script tag.


disable buttons/links on click

    <form name="form1">
      setting the onclick to null in the onclick handler:

      <h3> rewrite on click </h3>
      <a href="JavaScript:void(0)" id="a"
            onClick="this.onclick=null;alert('this is a test');"> set before </a>
      <br />
      <a href="JavaScript:void(0)" id="b"
            onClick="alert('this is a test');this.onclick=null;"> set after </a>
      <br />

      <h3> rewrite href </h3>
      <a href="JavaScript:alert("this is a test")" id="c"
            onClick="eval(unescape(this.href));this.href='JavaScript:void(0)'"> set href onclick </a>
      <br />

    </form>



closures:

  IMO the real power of JavaScript comes from the closures syntax.

  https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

  a closure caling this function:

    function test1(){}

  looks like:

    function() { test1(); }

  useful for capturing values in scope that must be passsed to another function
  at a later time (for example setTimeout or ajax callback).

  note that return value of closure is not the same as function unless you use:

    function() { return test1(); }

you can also refer to a function by name

        function example1() { }

        function example2(example1); // pass function to function


closure in loop


    // doesn't do what you'd expect, all the same number
    function attachEventsToListItems( ) {
        var oList = document.getElementById('myList');
        var aListItems = oList.getElementsByTagName('li');
         for(var i = 0; i < aListItems.length; i++) {
            var oListItem = aListItems[i];
            // Here I try to use the variable i in a closure:
            oListItem.onclick = function() {
                alert(i);
            }
        }
    }

    // REASON: variable scope is at function level, not at block level
    // FIX:    create a new scope for the 'current' value of i
    //            by executing a function inside the loop:
    function attachEventsToListItems( ) {
        var oList = document.getElementById('myList');
        var aListItems = oList.getElementsByTagName('li');
        for(var i = 0; i < aListItems.length; i++) {
            var oListItem = aListItems[i];

            // create new function scope:
            oListItem.onclick = (function(value) {
                return function() {
                    alert(value);
                }
            })(i);
        }
    }

    //or your could use something like:

        function  bindarg( f, arg ) {
            return function(e){ f(arg) };
        }


    // for new browsers you can use

        let var = ...

    // or use bind -- this creates a wrapper function with the bound vars

        fun.bind(thisArg[, arg1[, arg2[, ...]]])

        // eg

        this.element.click(

            // `this` will be redefined to a dom element inside click
            (
                 function() {

                    // ...
                    // after bind, `this` reference will be restored to refer to object context
                 }

            ).bind(this)
        );



data element

    // start with data-

    <a data-seqno="1" onclick="dostuff()">

    // read in the function

        var sequenceNo = this.dataset.seqno;



try/catch

    try {
        //Run some code here

    } catch(err) {
        //Handle errors here

    }

also
    } finally {
    }

text of error message:

    e.message


throw exception


    // simple
    throw "your string";
    throw { "name": "name", "message": "message" }


    // with a constructor
    function MyError(name,message) {
        this.name = name;
        this.message = message;
    }
    MyError.prototype = new Error;


    //usage
    try {
      something();
     } catch(e) {
      if(e instanceof MyError)
       doSomethingElse();
      else if(e instanceof Error)
       andNowForSomethingCompletelyDifferent();
    }


be careful with type juggling.  + operator may not understand what is intended

    for ( i in obj ) {
        var j = i + 1;  // "0" + 1 == "01" ... wrong!
        var j = i; j++;  // 2

        // force to int with default 8
        var j= parseInt(i) || 8;
    }



cast to bool

    use not-not operator
    var a = 4;

    !! a


    if("true"){
        alert("1");
    }else{
        if(!!"true" == true){
            alert("2");
        }
    }
    // alerts 1

    if("true" == true){
        alert("1");
    }else{
        if(!!"true" == true){
            alert("2");
        }
    }
    // alerts 2


    type juggling rules:
        1. string must literally match
        2. true false are like 1 0
        3. whitespace is trimmed

    examples:

        '' == '0' // false  
        0 == '' // true
        0 == '0' // true

        false == 'false' // false
        false == '0' // true

        false == undefined // false
        false == null // false
        null == undefined // true

        ' \t\r\n ' == 0 // true

        groking the type juggling:
        if types are unequal, data is converted first to closest common primative type, then compared.

            '1' == 1
            true

            '1' == new String( 1 )
            true  // to string primative

            new String('1') == new String( 1 )
            false     // object comparisons

            new String('1') == 1
            true

            new String('1') == 1.0000
            true

            new String('1.0000') == 1
            true

            new String('1.000') == '1'
            false // converted to string types

            new String('1') == true
            true

            new String('0') == null
            false

            new String('0') == false
            true

            false == null
            false

            null == undefined
            true

            null == false
            false

            null == true
            false

            undefined == false
            false



    auto convert rules:

        String object -> string primative (Box -> primative)
        string primative of number -> number
        number -> boolean (true false null)

    be careful string comparisons of serilization of json data number != string
 
    construct "if ( a )" means "if a is not null"


    cast to int:

        +i

    eg:

        +'-1' === -1

        Unary plus casts the value to a number, but binary plus does not: +'12' === 12 (result is a number), but 0 + '12' === '012' (result is a string). Note, however, that 0 - '12' === -12


new

Calling a function with the new keyword creates a new object and then calls the function with that new object as its context. The object is then returned. Conversely, invoking a function without 'new' will result in the context being the global object if that function is not invoked on an object (which it won't be anyway if it's used as a constructor!)


open new window

    window.open(URL,name,specs,replace)


    URL    Optional. Specifies the URL of the page to open. If no URL is specified, a new window with about:blank is opened

        name    Optional. Specifies the target attribute or the name of the window. The following values are supported:
        _blank - URL is loaded into a new window. This is default
        _parent - URL is loaded into the parent frame
        _self - URL replaces the current page
        _top - URL replaces any framesets that may be loaded
        name - The name of the window

    specs    Optional. A comma-separated list of items. The following values are supported:

        channelmode=yes|no|1|0    Whether or not to display the window in theater mode. Default is no. IE only
        directories=yes|no|1|0    Whether or not to add directory buttons. Default is yes. IE only
        fullscreen=yes|no|1|0    Whether or not to display the browser in full-screen mode. Default is no. A window in full-screen mode must also be in theater mode. IE only
        height=pixels    The height of the window. Min. value is 100
        left=pixels    The left position of the window
        location=yes|no|1|0    Whether or not to display the address field. Default is yes
        menubar=yes|no|1|0    Whether or not to display the menu bar. Default is yes
        resizable=yes|no|1|0    Whether or not the window is resizable. Default is yes
        scrollbars=yes|no|1|0    Whether or not to display scroll bars. Default is yes
        status=yes|no|1|0    Whether or not to add a status bar. Default is yes
        titlebar=yes|no|1|0    Whether or not to display the title bar. Ignored unless the calling application is an HTML Application or a trusted dialog box. Default is yes
        toolbar=yes|no|1|0    Whether or not to display the browser toolbar. Default is yes
        top=pixels    The top position of the window. IE only
        width=pixels    The width of the window. Min. value is 100

    replace    Optional.Specifies whether the URL creates a new entry or replaces the current entry in the history list. The following values are supported:
        true - URL replaces the current document in the history list
        false - URL creates a new entry in the history list


for in

    // this syntax deviates from python/perl/php and most other languages.
    // DO NOT use for arrays
    // use to loop over keys of an hash

    var person={fname:"John",lname:"Doe",age:25};

    var txt = '';
    for (x in person) {
      txt + =  x + ': ' + person[x] + '<br>';
      }

    console.log( txt );

    BEWARE:
        this also scans the prototype chain

        The order of the iteration is undefined by the standard, because the intent is to iterate over unordered structures like hashes.  Many browsers preserve the order (like php), some do not.


hash

    does not really exist in javascipt.  All that exists is Object
    Warning:  a hash is really an object

        // JavaScript magic trick

        // watch me pull a rabbit out of my hat, Rocky:
        // nothing up my sleeve:
        var emptyHat = [ ];

        // and presto!
        if ( emptyHat['constructor'] ) { alert("look a rabbit") }


        solution: use the 'in' operator
     
        BEWARE2: in searches the prototype chain :)
        newer function obj.hasOwnProperty(prop) may help restrict to current scope


get base url of page

  function getBaseURL() {
    var url = location.href;
    var baseURL = url.substring(0, url.lastIndexOf('/')+1);
    return baseURL ;
  }



apply vs call

    theFunction.apply(valueForThis, arrayOfArgs)
    theFunction.call(valueForThis, arg1, arg2, ...)



general util class

    http://jquery.com

    http://underscorejs.org

        templates, iterators, useful functions





'with' considered harmful


    acceptable use:

        Math.cos(Math.asin(0.5))

    could be more legible as

        with(Math) cos(asin(0.5))


 


dump object to string


    function dump(arr,level) {
        var dumped_text = "";
        if(!level) level = 0;

        //The padding given at the beginning of the line.
        var level_padding = "";
        for(var j=0;j<level+1;j++) level_padding += "    ";

        if(typeof(arr) == 'object') { //Array/Hashes/Objects
            for(var item in arr) {
                var value = arr[item];

                if(typeof(value) == 'object') { //If it is an array,
                    dumped_text += level_padding + "'" + item + "' ...\n";
                    dumped_text += dump(value,level+1);
                } else {
                    dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
                }
            }
        } else { //Stings/Chars/Numbers etc.
            dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
        }
        return dumped_text;
    }


--
GOTCHAS

weird data types

    https://github.com/stevekwan/best-practices/blob/master/JavaScript/gotchas.md
    http://www.codeproject.com/Articles/182416/A-Collection-of-JavaScript-Gotchas

     typeof null == 'object';     // true

    no such thing as an associative array. they are actually objects, with 'constructor' key

this - quirks!
 

    in binding a closure to an element, the element's this.properties have tighter scope than local variables.  Example:

    var value = 123;
    onclick = function() {
        alert(value); // will alert this.value of control, not variable!
    }
 

prototype vs __proto__

    prototype goes on constructors that create objects, and __proto__ goes on objects being created.

One more thing: please don't ever try to manipulate the __proto__ pointer. JavaScript is not supposed to support editing of __proto__ as it is an internal property. Some browsers will let you do it, but it's a bad idea to rely on this functionality. If you need to manipulate the prototype chain you're better off using hasOwnProperty() instead.

Why is parseInt() mucking up when I get into big numbers?

    all numbers are floats:

    Despite looking like it, JavaScript doesn't actually have an integer data type - it only has a floating point type.

parseInt("1000000000000000", 10) < parseInt("1000000000000001", 10); //true

but add one more zero:

parseInt("10000000000000000", 10) < parseInt("10000000000000001", 10); //false


No block scope.  Variables belong to function scope.

NaN === NaN; // false

Constructors (more confusing than they should be)

http://zeekat.nl/articles/constructors-considered-mildly-confusing.html

sort:

    var objs = [
            { first_nom: 'Lazslo', last_nom: 'Jamf'     },
            { first_nom: 'Pig',    last_nom: 'Bodine'   },
            { first_nom: 'Pirate', last_nom: 'Prentice' }
        ];


    function compare(a,b) {
      if (a.last_nom < b.last_nom)
         return -1;
      if (a.last_nom > b.last_nom)
        return 1;
      return 0;
    }

    objs.sort(compare);


--
OOP IN JAVASCRIPT

gist and weirdness of js:

    functions are objects

    variable scope is bound to a function.  This is very conter-intuitive in some cases.

    The `this` reference is just a var that is set in the calling context.  it's not fixed
    You can save a reference to `this`
    (`this` means the calling object, not the current object)
        var self=this; // in constructor
        iow, 'this' should have been renamed to something like 'that'

    Private: to hide a varible, define it inside a function
            to hide a function, define it inside a function


    There are no classes
    There are two basic approaches for oop

        prototypes -- more efficient memory handling (possibly though this is becoming a non-issue).  more limited in acccessing scope/this
        closures -- cleaner code, more modern, not as efficient with memory

    objects always inherit from another object


prototype vs closures

    any function defined in the prototype only exists in one place in memory
    closure methods exist for every copy of the object
    prototype methods have no knowledge/access of what is contructed, unless attached to `this`

    So, prototype based approaches are currently the 'recommended' approach. However:

    Modern JS interpreters will likely be getting smart enough to NOT create
    rededundant copies of a method. So it's cleaner (from the code side) to think
    of 'function' as a synonym of 'class'.  Also, maintaining references in
    prototypes requires more work, for example using .bind(this), which I think
    long term might cause more of a permanent performance hit as it's permanently
    wrapping function calls in a bind call.

    prototype methods will have difficulty in maintaining `this` reference

    prototypes CANNOT access private variables of a class.        
    prototypes CANNOT access `this` in

        YourObj.prototype.clickSomething = function(){
            // sorry, `this` has been lost, if you access from a button
            // find another way to do this.
            // imo prototype approach is somewhat broken.
        }



    IOW, the VM's will get smarter and more efficient.  So, write the cleanest code, and optimize only if needed.

    // here's a simple prototype based object
    // more verbose, ugly code
    // more effiencient in old vm's

        // class ObjectConstructor { // I'm not really here

            function ObjectConstructor {
            }

            ObjectConstructor.prototype.yourdata = {}
            ObjectConstructor.prototype.yourfunction = function() {

                // `this` refers to the current instance here...
                // but, you'll likely run into a headache here trying to
                // maintain a reference to `this` inside nested closures or
                // event handlers.  Since "var this = 'mua hahaha!" can be
                // set from the caller.

                this.yourdata = 1;

                // note: you can assign an object directly to .prototype
            }

        // } // end fake class

 

    // alternately, here's a simple closure based object

        function YourObject(options) {  // think: class YourObject {

            // the constructor is this whole class/function

            // use `self` from here on out to refer to the object instance.
            // `this` is just a variable that can change in the calling context.
            // It's kinda a hack, but a simple one:
            var self = this;

            // example: private variable.  Put everything in one object reference.
            var data = { 'test': 'this is a private var' };

            // example: public variable.  Put everything in one object reference.
            self.data2 = { 'test': 'this is a public var' };

            // example: private function
            var yourFunction1 = function(){

                // reference to private variable
                console.log(data);

                // reference to public variable
                console.log(self.data2);
            }

            // example: public function
            self.yourFunction2 = function(){

                // reference to private variable
                console.log(data);

                // reference to public variable
                console.log(self.data2);

            }
        }



    // instantiate objects with

        var o = new YourObject();

    //NOTE: you can also reuse the constructor with

            function TestClass() { } // child object


            // some example use object.  Note no args pass
            TestClass.prototype = new YourObject();

            // you can also use a function reference, but will need to call the constructor
            TestClass.prototype = YourObject;
            function TestClass {
             
            }
 
    // example2 data on prototype is isolated across instances

            function Test1() {
                this.a = 1;
            }

            function Test2() {
            }

            Test2.prototype = new Test1();

            var t2 = new Test2();

            t2.a
            // 1



OOP with prototypes

    Everything assigned to prototype is public

    You can change prototypes on the fly (even for already-defined objects)

    The prototype is an Object

    Note: you may have some preserving `this` reference in prototype closures.

    You can use bind() method in modern browsers to preserve the this method:

            fun.bind(thisArg[, arg1[, arg2[, ...]]])

        // eg

            this.element.click(

                // `this` will be redefined to a dom element inside click
                (
                     function() {

                        // ...
                        // after bind, `this` reference will be restored to refer to object context
                     }

                ).bind(this)     // <=== pass in and override this from the current context.
                                // this creates a function wrapper
            );


    // use bind polyfill for old browers

        if (!Function.prototype.bind) {
          Function.prototype.bind = function(oThis) {
            if (typeof this !== 'function') {
              // closest thing possible to the ECMAScript 5
              // internal IsCallable function
              throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }

            var aArgs   = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP    = function() {},
                fBound  = function() {
                  return fToBind.apply(this instanceof fNOP
                         ? this
                         : oThis,
                         aArgs.concat(Array.prototype.slice.call(arguments)));
                };

            if (this.prototype) {
              // native functions don't have a prototype
              fNOP.prototype = this.prototype;
            }
            fBound.prototype = new fNOP();

            return fBound;
          };
        }


    Personally, the `bind` approach looks like an ugly hack to me.  I think the closure approach looks much cleaner.


OOP with closure pattern

    Public

        function ConstructorName(...) {
            this.membername = value;
        }

        // static copy -- saves memory
        ConstructorName.prototype.membername = value;

    Private

        function ConstructorName(...) {
            var that = this;
            var membername = value;
            function membername(...) {...}

        }

    Note: The function statement

        function membername(...) {...}

        is shorthand for

        var membername = function membername(...) {...};

    Privileged

        function ConstructorName(...) {
            this.membername = function (...) {...};
        }



static vs instance scope

    // remember that functions are first class objects.

    // static method
    ConstructorName.method = function () { /* code */ }

    // public instance method
    // NOT static
    ConstructorName.prototype.method = function () { /* code using this.values */ }

    // same goes for data
    ConstructorName.prototype.data = 'this is NOT static data';
    ConstructorName.data = 'this IS static data';

    // test example
        Test1 = function(){}
        Test1.prototype.d1 = 1

        t1 = new Test1();
        t2 = new Test1();

        t2.d1 = 2
        t1.d1
        // 1
        t2.d1
        // 2


`this` limitations

    this cannot refer to an object before it's completely initialized.

    var a = {
        x: 1,
        y: 2,
        z: this.x + this.y,   // NaN, won't work
        z2: function() { return this.x + this.y }   // OK: a.z2() == 3
    }


inheritence

    var childObject = Object.create(parentObject);


    // base class
    function Actor(name) {
        this.name = name;
    }
    Actor.prototype.canSpeak = true;


    // extended class
    function SilentActor() {
        // pass along constructor args
        // eg: super(arguments)
        Actor.apply(this, arguments);
    }

    // set constructor prototype
    // eg: SilentActor extends Actor
    SilentActor.prototype = new Actor();
    SilentActor.prototype.canSpeak = false;


No comments: