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;