There are several JavaScript tricks that are used widely by experienced programmers. Many of them may not be instantly obvious, especially for beginners. These tricks use language features not by their direct purpose, but rather by their side-effect to achieve goals, that can’t be achieved by default language means. Here I made a little compilation of such tricks with explanation.
You should understand that most of these tricks are rather hacks and not something you should use in your daily development. The purpose of that article is to explain how they work, not to push to use them.
Using !! to convert value into a boolean
Everything in JavaScript can be interpreted either as truthy or falsy. This means that when you put object into an if expression, it will either let you go by true-branch (i.e. it’s ‘truthy’) of by false branch(‘falsy’).
0, false, "", null, undefined, NaN are all falsy, every other object is truthy. Sometimes you’d like to convert an object to a plain boolean value. To do this you can use double negation !!.
Another side of that coin is that instead of if (x == "test") you can simply write if (x). In case x is empty (hence falsy) it will run else block.
Converting string into a number with +str
In JavaScript + is a unary operator that returns a numeric representation of operand or NaN if not applicable. Sometimes you can enforce code (see underscorejs sources) such as x === +x, which is thus just checking whether x is numeric.
This one is not obvious at all. Normally you’d want to use parseFloat and parseInt(x, 10) for parsing numbers.
Providing default value with ||
In JavaScript || is an example of short-circuit evaluation, which is also commonly used in some other language. This operator will firstly evaluate expression on the left side, and then, if falsy, will proceed with on the right. In any case it will return first non-falsy result. Consider the following example
function setAge(age) {
this.age = age || 10
}
setAge();
We didn’t provide age, thus age || 10 will return 10, which is a nice way to provide some defaults values. In fact this is equivalent to
var x;
if (age) {
this.age = age;
} else {
this.age = 10;
}
The former is obviously more succinct and that’s why you will see it used everywhere.
Personally, I use that pattern a lot. I like its conciseness and clarity. Notice, however, that since 0 is falsy you won’t have an ability to set age to 0. Therefore, it might be a better (but slightly more verbose) solution to use something like this:
this.age = (typeof age !== "undefined") ? age : 10;
Using void 0 instead of undefined
Keyword void takes one argument and always return undefined. Why not to simply use undefined? Because in some browsers undefined is just a variable that can be reassigned, and the former gives us a higher level of confidence. Although you can find this being used in source code of some libraries, I would not recommend using it on the regular basis, since all EC5-compliant browsers don’t allow to rewrite undefined.
Encapsulation with (function() {...})() pattern
You’ll want to wrap your code into an anonymous function and then immediately call it, when you want some encapsulation. There are only two types of scopes in JavaScript (but look at ECMA6 block scopes): global scope and function scope. Everything you write goes into global scope, which is accessible from everywhere. This includes vars and function declarations. Normally you’d want to encapsulate most of the code somewhere inside of a function and expose to global scope only interface. And that’s where this pattern gets really handy. Consider the following:
(function() {
function div(a, b) {
return a / b;
}
function divBy5(x) {
return div(x, 5);
}
window.divBy5 = divBy5;
})()
div // => undefined
divBy5(10); // => 2
Among other hacks listed in this article this one is really harmless and you can and should use it in your code to prevent exposing some inner logic into the global scope.
In conclusion, I’d like to remind you that any code you write should be simple and clear to other programmers. And any natural constructions provided by language should be preferred to artificial ones.
Some of the problems, listed in that article are being solved in elegant way by ES6 standart (next version of JavaScript). For instance, you’ll probably won’t need the obscure age = age || 10 pattern in the future, since ES6 allows you to write default arguments in a better way:
function(age = 10) {
...
}
Another example, is (function() {...})() pattern, which you can leave in the past after EC6 modules get implemented by modern browsers.
Append an array to another array
var a = [4,5,6];
var b = [7,8,9];
Array.prototype.push.apply(a, b);
uneval(a); // is: [4, 5, 6, 7, 8, 9]
Milliseconds since epoch
+new Date() // 1259359833574
Simulate threads using yield operator
JavaScript 1.7
//// thread definition
function Thread( name ) {
for ( var i = 0; i < 5; i++ ) {
Print(name+': '+i);
yield;
}
}
//// thread management
var threads = [];
// thread creation
threads.push( new Thread('foo') );
threads.push( new Thread('bar') );
// scheduler
while (threads.length) {
var thread = threads.shift();
try {
thread.next();
threads.push(thread);
} catch(ex if ex instanceof StopIteration) {}
}
prints:
foo: 0
bar: 0
foo: 1
bar: 1
foo: 2
bar: 2
foo: 3
bar: 3
foo: 4
bar: 4
prefix an integer with zeros
function PrefixInteger(num, length) {
return (num / Math.pow(10, length)).toFixed(length).substr(2);
}
And more efficient:
function PrefixInteger(num, length) {
return ( "0000000000000000" + num ).substr( -length );
}
Thanks to fritzthe...
And even better:
function PrefixInteger(num, length) {
return (Array(length).join('0') + num).slice(-length);
}
Thanks to tobeytai...
shuffle the Array
JavaScript 1.?
var list = [1,2,3,4,5,6,7,8,9];
list = list.sort(function() Math.random() - 0.5);
Print(list); // prints something like: 4,3,1,2,9,5,6,7,8
multi-line text
JavaScript 1.6
var text = <>
this
is
my
multi-line
text
</>.toString();
Print(text);
prints:
this
is
my
multi-line
text
note
If you want to support special XML chars, you can use a CDATA section:
<><![CDATA[
>>> hello
]]></>.toString();
Escape and unescape HTML entities
const entityToCode = { __proto__: null,
apos:0x0027,quot:0x0022,amp:0x0026,lt:0x003C,gt:0x003E,nbsp:0x00A0,iexcl:0x00A1,cent:0x00A2,pound:0x00A3,
curren:0x00A4,yen:0x00A5,brvbar:0x00A6,sect:0x00A7,uml:0x00A8,copy:0x00A9,ordf:0x00AA,laquo:0x00AB,
not:0x00AC,shy:0x00AD,reg:0x00AE,macr:0x00AF,deg:0x00B0,plusmn:0x00B1,sup2:0x00B2,sup3:0x00B3,
acute:0x00B4,micro:0x00B5,para:0x00B6,middot:0x00B7,cedil:0x00B8,sup1:0x00B9,ordm:0x00BA,raquo:0x00BB,
frac14:0x00BC,frac12:0x00BD,frac34:0x00BE,iquest:0x00BF,Agrave:0x00C0,Aacute:0x00C1,Acirc:0x00C2,Atilde:0x00C3,
Auml:0x00C4,Aring:0x00C5,AElig:0x00C6,Ccedil:0x00C7,Egrave:0x00C8,Eacute:0x00C9,Ecirc:0x00CA,Euml:0x00CB,
Igrave:0x00CC,Iacute:0x00CD,Icirc:0x00CE,Iuml:0x00CF,ETH:0x00D0,Ntilde:0x00D1,Ograve:0x00D2,Oacute:0x00D3,
Ocirc:0x00D4,Otilde:0x00D5,Ouml:0x00D6,times:0x00D7,Oslash:0x00D8,Ugrave:0x00D9,Uacute:0x00DA,Ucirc:0x00DB,
Uuml:0x00DC,Yacute:0x00DD,THORN:0x00DE,szlig:0x00DF,agrave:0x00E0,aacute:0x00E1,acirc:0x00E2,atilde:0x00E3,
auml:0x00E4,aring:0x00E5,aelig:0x00E6,ccedil:0x00E7,egrave:0x00E8,eacute:0x00E9,ecirc:0x00EA,euml:0x00EB,
igrave:0x00EC,iacute:0x00ED,icirc:0x00EE,iuml:0x00EF,eth:0x00F0,ntilde:0x00F1,ograve:0x00F2,oacute:0x00F3,
ocirc:0x00F4,otilde:0x00F5,ouml:0x00F6,divide:0x00F7,oslash:0x00F8,ugrave:0x00F9,uacute:0x00FA,ucirc:0x00FB,
uuml:0x00FC,yacute:0x00FD,thorn:0x00FE,yuml:0x00FF,OElig:0x0152,oelig:0x0153,Scaron:0x0160,scaron:0x0161,
Yuml:0x0178,fnof:0x0192,circ:0x02C6,tilde:0x02DC,Alpha:0x0391,Beta:0x0392,Gamma:0x0393,Delta:0x0394,
Epsilon:0x0395,Zeta:0x0396,Eta:0x0397,Theta:0x0398,Iota:0x0399,Kappa:0x039A,Lambda:0x039B,Mu:0x039C,
Nu:0x039D,Xi:0x039E,Omicron:0x039F,Pi:0x03A0,Rho:0x03A1,Sigma:0x03A3,Tau:0x03A4,Upsilon:0x03A5,
Phi:0x03A6,Chi:0x03A7,Psi:0x03A8,Omega:0x03A9,alpha:0x03B1,beta:0x03B2,gamma:0x03B3,delta:0x03B4,
epsilon:0x03B5,zeta:0x03B6,eta:0x03B7,theta:0x03B8,iota:0x03B9,kappa:0x03BA,lambda:0x03BB,mu:0x03BC,
nu:0x03BD,xi:0x03BE,omicron:0x03BF,pi:0x03C0,rho:0x03C1,sigmaf:0x03C2,sigma:0x03C3,tau:0x03C4,
upsilon:0x03C5,phi:0x03C6,chi:0x03C7,psi:0x03C8,omega:0x03C9,thetasym:0x03D1,upsih:0x03D2,piv:0x03D6,
ensp:0x2002,emsp:0x2003,thinsp:0x2009,zwnj:0x200C,zwj:0x200D,lrm:0x200E,rlm:0x200F,ndash:0x2013,
mdash:0x2014,lsquo:0x2018,rsquo:0x2019,sbquo:0x201A,ldquo:0x201C,rdquo:0x201D,bdquo:0x201E,dagger:0x2020,
Dagger:0x2021,bull:0x2022,hellip:0x2026,permil:0x2030,prime:0x2032,Prime:0x2033,lsaquo:0x2039,rsaquo:0x203A,
oline:0x203E,frasl:0x2044,euro:0x20AC,image:0x2111,weierp:0x2118,real:0x211C,trade:0x2122,alefsym:0x2135,
larr:0x2190,uarr:0x2191,rarr:0x2192,darr:0x2193,harr:0x2194,crarr:0x21B5,lArr:0x21D0,uArr:0x21D1,
rArr:0x21D2,dArr:0x21D3,hArr:0x21D4,forall:0x2200,part:0x2202,exist:0x2203,empty:0x2205,nabla:0x2207,
isin:0x2208,notin:0x2209,ni:0x220B,prod:0x220F,sum:0x2211,minus:0x2212,lowast:0x2217,radic:0x221A,
prop:0x221D,infin:0x221E,ang:0x2220,and:0x2227,or:0x2228,cap:0x2229,cup:0x222A,int:0x222B,
there4:0x2234,sim:0x223C,cong:0x2245,asymp:0x2248,ne:0x2260,equiv:0x2261,le:0x2264,ge:0x2265,
sub:0x2282,sup:0x2283,nsub:0x2284,sube:0x2286,supe:0x2287,oplus:0x2295,otimes:0x2297,perp:0x22A5,
sdot:0x22C5,lceil:0x2308,rceil:0x2309,lfloor:0x230A,rfloor:0x230B,lang:0x2329,rang:0x232A,loz:0x25CA,
spades:0x2660,clubs:0x2663,hearts:0x2665,diams:0x2666
};
var charToEntity = {};
for ( var entityName in entityToCode )
charToEntity[String.fromCharCode(entityToCode[entityName])] = entityName;
function UnescapeEntities(str) str.replace(/&(.+?);/g, function(str, ent) String.fromCharCode( ent[0]!='#' ? entityToCode[ent] : ent[1]=='x' ? parseInt(ent.substr(2),16): parseInt(ent.substr(1)) ) );
function EscapeEntities(str) str.replace(/[^\x20-\x7E]/g, function(str) charToEntity[str] ? '&'+charToEntity[str]+';' : str );
Remove an object from an array
JavaScript 1.8
function RemoveArrayElement( array, element ) !!let (pos=array.lastIndexOf(element)) pos != -1 && array.splice(pos, 1);
Creates a random alphabetic string
function RandomString(length) {
var str = '';
for ( ; str.length < length; str += Math.random().toString(36).substr(2) );
return str.substr(0, length);
}
Brainfuck interpreter
var code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.';
var inp = '23\n';
var out = '';
var codeSize = code.length;
var i = 0, ip = 0, cp = 0, dp = 0, m = {};
var loopIn = {}, loopOut = {};
var tmp = [];
for ( var cp = 0; cp < codeSize ; cp++ )
if ( code[cp] == '[' )
tmp.push(cp);
else
if ( code[cp] == ']' )
loopOut[loopIn[cp] = tmp.pop()] = cp;
for (var cp = 0; cp < codeSize && i < 100000; cp++, i++) {
switch(code[cp]) {
case '>': dp++; break;
case '<': dp--; break;
case '+': m[dp] = ((m[dp]||0)+1)&255; break
case '-': m[dp] = ((m[dp]||0)-1)&255; break;
case '.': out += String.fromCharCode(m[dp]); break;
case ',': m[dp] = inp.charCodeAt(ip++)||0; break;
case '[': m[dp]||(cp=loopOut[cp]); break;
case ']': cp = loopIn[cp]-1; break;
}
}
Print(out);
This Brainfuck program just prints 'Hello World!'
Optional named function arguments
function foo({ name:name, project:project}) {
Print( project );
Print( name );
}
foo({ name:'soubok', project:'jslibs' })
foo({ project:'jslibs', name:'soubok'})
String converter
JavaScript 1.8
function CreateTranslator(translationTable) function(s) s.replace(new RegExp([k for (k in translationTable)].join('|'), 'g'), function(str) translationTable[str]);
exemple of use:
var translationTable = { a:1, bb:2, b:3, c:4 };
var MyTranslater = CreateTranslator( translationTable );
MyTranslater('aabbbc'); // returns: 11234
Display the current call stack
function Stack() { try { throw Error() } catch(ex) { return ex.stack } }
print( Stack() );
prints:
Error()@:0
Stack()@test.js:1
@test.js:3
Change the primitive value of an object with valueOf
function foo() {
this.valueOf = function() {
return 'this is my value';
}
}
var bar = new foo();
Print( bar ); // prints: this is my value
Print( bar == 'this is my value' ) // prints: true
Print( bar === 'this is my value' ) // prints: false
Transform the arguments object into an array
JavaScript 1.6
function foo() {
var argArray = Array.slice(arguments); // is ['aa',11]
}
foo('aa',11);
use also
var argArray = Array.prototype.slice.call(arguments);
Convert a string into a charcode list
Method 1: JavaScript 1.6
Array.map('foo', function(x) { return String.charCodeAt(x) }) // is [112,111,111]
Method 2: JavaScript 1.7
[ String.charCodeAt(x) for each ( x in 'foo' ) ] // is [112,111,111]
Array iteration pitfall
var foo = [3,4,5];
for ( var i in foo ) {
if ( i == 1 ) {
foo.unshift(6);
}
Print('item: '+foo[i])
}
Print( 'array: '+foo.toSource() )
output:
item: 3
item: 3
item: 4
array: [6, 3, 4, 5]
exceptions for non-fatal errors
JavaScript 1.7
function ERR() { throw ERR }
function CHK( v ) { return v || ERR() }
try {
var data1 = 'a/b/c';
var arr1 = CHK(data1).split('/');
var data2 = '';
var arr2 = CHK(data2).split('/'); // the exception is throw here
} catch(ex if ex == ERR) {
Print('non fatal error while decoding the data')
}
prints:
a b c
A Switch function
JavaScript 1.8
function Switch(i) arguments[++i];
usage:
Print( Switch(2,'aa','bb','cc','dd') ); // prints: cc
Print( Switch(100,'aa','bb','cc','dd') ); // prints: undefined
A Match function
JavaScript 1.8
function Match(v) Array.indexOf(arguments,v,1)-1;
usage:
Print( Match('aa', '0', 'b', 123, 'aa', 8.999 ) ); // prints: 3
Print( Match('Z', 'b', 123, 'aa', 8.999 ) ); // prints: -2 (< 0 is not found)
new object override
JavaScript 1.5
function foo() {
return new Array(5,6,7);
}
var bar = new foo();
bar.length // is 3
Kind of destructuring assignments
JavaScript 1.7
var { a:x, b:y } = { a:7, b:8 };
Print(x); // prints: 7
Print(y); // prints: 8
Generator Expressions
JavaScript 1.7
[ y for ( y in [5,6,7,8,9] ) ] // is [0,1,2,3,4]
and
[ y for each ( y in [5,6,7,8,9] ) ] // is [5,6,7,8,9]
Because in for extracts index names, and for each extracts the values.
Advanced use of iterators
JavaScript 1.7
Number.prototype.__iterator__ = function() {
for ( let i = 0; i < this; i++ )
yield i;
};
for ( let i in 5 )
print(i);
prints:
1
2
3
4
5
This make Number object to act as a generator.
Expression Closures
JavaScript 1.8
function(x) x * x;
Note that braces {...} and return are implicit
Function declaration and expression
Function declaration:
bar(); // prints: bar
function bar() {
Print('bar');
}
function foo() {
Print('foo');
}
foo(); // prints: foo
Function expression:
(function foo() {
Print( foo.name );
})(); // prints: foo
foo(); // rise a ReferenceError
!function foo() {
}
foo(); // rise a ReferenceError
var bar = function foo() {
}
foo(); // rise a ReferenceError
Factory method pattern
Complex = new function() {
function Complex(a, b) {
// ...
}
this.fromCartesian = function(real, mag) {
return new Complex(real, imag);
}
this.fromPolar = function(rho, theta) {
return new Complex(rho * Math.cos(theta), rho * Math.sin(theta));
}
}
var c = Complex.fromPolar(1, Math.pi); // Same as fromCartesian(-1, 0);
see factory pattern on wikipedia
Closures by example
JavaScript 1.5
function CreateAdder( add ) {
return function( value ) {
return value + add;
}
}
usage:
var myAdder5 = CreateAdder( 5 );
var myAdder6 = CreateAdder( 6 );
Print( myAdder5( 2 ) ); // prints 7
Print( myAdder6( 4 ) ); // prints 10
Further information about Nested functions and closures
SyntaxError
JavaScript 1.5
raised when a syntax error occurs while parsing code in eval()
try {
eval('1 + * 5'); // will rise a SyntaxError exception
} catch( ex ) {
Print( ex.constructor == SyntaxError ); // Prints true
}
JavaScript 1.7
try {
eval('1 + * 5');
} catch( ex if ex instanceof SyntaxError ) {
Print( 'SyntaxError !' ); // prints: SyntaxError !
}
ReferenceError
raised when de-referencing an invalid reference.
try {
fooBar(); // will rise a ReferenceError exception
} catch( ex ) {
Print( ex.constructor == ReferenceError ); // Prints true
}
JavaScript 1.7
try {
fooBar();
} catch( ex if ex instanceof ReferenceError ) {
Print( 'ReferenceError !' ); // prints: ReferenceError !
}
JavaScript Minifier / comment remover
jslibs
var script = new Script('var a; /* this is a variable */ var b; // another variable');
Print( script.toString() );
prints:
var a;
var b;
javascript
function foo() {
var a; // a variable
var b = [1, 2, 3]; // an array
var c = {x: {y: 1}};
function bar() { // my function
return 1 /* 2 */;
}
}
Print( foo.toSource() );
JavaScript code beautifier (un-minifier)
function foo() {var a;var b=[1,2,3];var c={x:{y:1}};function bar(){return 1}}
Print( foo.toSource(1) );
prints:
function foo() {
var a;
var b = [1, 2, 3];
var c = {x: {y: 1}};
function bar() {
return 1;
}
}
Auto indent JavaScript code / unobfuscator
Spidermonkey JavaScript engine only
function foo() { function bar(){};var i=0;for(;i<10;++i) bar(i) }
Print(foo.toSource(2));
prints:
function foo() {
function bar() {
}
var i = 0;
for (; i < 10; ++i) {
bar(i);
}
}
Objects, constructor and instanceof
JavaScript 1.5
function Foo() {
// Foo class
}
var a = new Foo();
Bar.prototype = new Foo();
function Bar() {
// Bar class
}
var b = new Bar();
Print( a.constructor == Foo ); // true
Print( a instanceof Foo ); // true
Print( b.constructor == Foo ); // true
Print( b instanceof Foo ); // true
Print( b.constructor == Bar ); // false !
Print( b instanceof Bar ); // true
Objects private and public members
function MyConstructor( pub, priv ) {
var privateVariable = priv;
this.publicVariable = pub;
this.SetThePrivateVariable = function( value ) {
privateVariable = value;
}
this.GetThePrivateVariable = function() {
return privateVariable;
}
this.PrintAllVariables = function() {
Print( privateVariable + ',' + this.publicVariable );
}
}
var myObject = new MyConstructor( 123, 456 );
Print( myObject.privateVariable ); // prints: undefined
Print( myObject.publicVariable ); // prints: 123
myObject.PrintAllVariables(); // prints 456,123
myObject.SetThePrivateVariable( 789 );
myObject.PrintAllVariables(); // prints 789,123
Stack data structure
JavaScript 1.5 Array object has all needed methods to be used as a stack.
var stack = [];
stack.push( 111 );
stack.push( 2.22 );
stack.push( 'ccc' );
Print( stack ); // prints: 111,2.22,ccc
Print( stack.pop() ); // prints: ccc
Print( stack.pop() ); // prints: 2.22
Singleton pattern
JavaScript 1.5 The singleton pattern is a design pattern that is used to restrict instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
function MySingletonClass() {
if ( arguments.callee._singletonInstance )
return arguments.callee._singletonInstance;
arguments.callee._singletonInstance = this;
this.Foo = function() {
// ...
}
}
var a = new MySingletonClass()
var b = MySingletonClass()
Print( a === b ); // prints: true
Use Functions as an Object
JavaScript 1.5
function Counter() {
if ( !arguments.callee.count ) {
arguments.callee.count = 0;
}
return arguments.callee.count++;
}
Print( Counter() ); // prints: 0
Print( Counter() ); // prints: 1
Print( Counter() ); // prints: 2
E4X add nodes
JavaScript 1.6
var x = <store/>;
x.* += <item price="10" />;
x.* += <item price="15" />;
Print( x.toXMLString() );
prints:
<store>
<item price="10"/>
<item price="15"/>
</store>
E4X dynamic document creation
JavaScript 1.6
var html = <html/>;
html.head.title = "My Page Title";
html.body.@bgcolor = "#e4e4e4";
html.body.form.@name = "myform";
html.body.form.@action = "someurl.jss";
html.body.form.@method = "post";
html.body.form.@onclick = "return somejs();";
html.body.form.input[0] = "";
html.body.form.input[0].@name = "test";
Print(html.toXMLString());
prints:
<html>
<head>
<title>My Page Title</title>
</head>
<body bgcolor="#e4e4e4">
<form name="myform" action="someurl.jss" method="post" onclick="return somejs();">
<input name="test"></input>
</form>
</body>
</html>
E4X dynamic tag name
JavaScript 1.6
var nodeName = 'FOO';
if ( isXMLName(nodeName) ) { // assert that nodeName can be used as a node name
var x = <{nodeName}>test</{nodeName}>
Print( x.toXMLString() ); // prints: <FOO>test</FOO>
}
E4X dynamic content
JavaScript 1.6
var content = 'FOO';
var x = <item>{content}</item>
Print( x.toXMLString() ); // prints: <item>FOO</item>
E4X iteration
JavaScript 1.6
var sales =
<sales vendor="John">
<item type="peas" price="4" quantity="5"/>
<item type="carrot" price="3" quantity="10"/>
<item type="chips" price="5" quantity="3"/>
</sales>;
for each( var price in sales..@price ) {
Print( price + '\n' );
}
prints:
4
3
5
Listen a property for changes
function onFooChange( id, oldval, newval ) {
Print( id + " property changed from " + oldval + " to " + newval );
return newval;
}
var o = { foo:5 };
o.watch( 'foo', onFooChange );
o.foo = 6;
delete o.foo;
o.foo = 7;
o.unwatch('foo');
o.foo = 8;
prints:
foo property changed from 5 to 6
foo property changed from undefined to 7
Logical operators tricks
var a = 5;
a == 5 && Print( 'a is 5 \n' );
a == 7 || Print( 'a is not 7 \n' );
prints:
a is 5
a is not 7
Functions argument default value
function foo( a, b ) {
a = a || '123';
b = b || 55;
Print( a + ',' + b );
}
foo(); // prints: 123,55
foo('bar'); // prints: bar,55
foo('x', 'y'); // prints x,y
but:
foo(0,''); // prints: 123,55
because 0 and '' are evaluated as false !
Remove an item by value in an Array object
var arr = ['a', 'b', 'c', 'd'];
var pos = arr.indexOf( 'c' );
pos > -1 && arr.splice( pos, 1 );
Print( arr ); // prints: a,b,d
Multiple-value returns
JavaScript 1.7
function f() {
return [1, 2];
}
var [a, b] = f();
Print( a + ' ' + b ); // prints: 1 2
Operator [ ] and strings ( like charAt() )
JavaScript 1.6
var str = 'foobar';
Print( str[4] );
prints:
a
indexOf() and lastIndexOf() Works on Array
JavaScript 1.6
var obj = {};
var arr = [ 'foo', 567, obj, 12.34 ];
Print( arr.indexOf(obj) ); // prints: 2
Using Array functions on a non-Array object
JavaScript 1.7
var obj = {};
Array.push(obj, 'foo');
Array.push(obj, 123);
Array.push(obj, 5.55);
Print( obj.toSource() ); // prints: ({0:"foo", length:3, 1:123, 2:5.55})
Change current object (this) of a function call
function test(arg) {
Print( this[0]+' '+this[1]+' '+arg );
}
var arr = ['foo', 'bar'];
test.call(arr, 'toto'); // prints: foo bar toto
Filter / intercept a function call
function bar(a, b, c, d, e, f) {
Print(a, b, c, d, e, f)
}
function foo() {
bar.apply(this, arguments);
}
foo(1, 2, 3, 4, 5, 6); // prints: 123456
E4X Query ( like XPath )
JavaScript 1.6
var xml = <mytree>
<data id="1" text="foo"/>
<data id="2" text="bar"/>
</mytree>
Print( xml.data.(@id==1).@text );
var myid = 2;
Print( xml.data.(@id==myid).@text );
prints:
foo
bar
swap two variables
JavaScript 1.7
var a = 1;
var b = 2;
[a,b] = [b,a];
Destructuring assignment with function arguments
JavaScript 1.7
function foo( [a,b] ) {
Print(a);
Print(b);
}
foo( [12,34] );
Prints:
12
34
JavaScript scope is not C/C++ scope
if ( false ) { // never reach the next line
var foo = 123;
}
Print(foo); // prints: undefined ( but is defined )
Print(bar); // failed: ReferenceError: bar is not defined
JavaScript scope and LET instruction
JavaScript 1.7
var x = 5;
var y = 0;
let (x = x+10, y = 12) {
Print(x+y);
}
Print(x+y);
prints:
27
5
or,
for ( let i=0 ; i < 10 ; i++ ) {
Print(i + ' ');
}
Print(i);
prints:
0 1 2 3 4 5 6 7 8 9 test.js:4: ReferenceError: i is not defined
Defer function calls
var opList = [];
function deferCall( text, value ) {
opList.push(arguments)
}
function doCall(func) {
while (opList.length)
func.apply(this,opList.shift());
}
deferCall( 'one', 1 );
deferCall( 'titi', 'toto' );
deferCall( 5, 'foo' );
function callLater(a,b) {
Print(a+', '+b);
}
doCall( callLater )
Prints:
one, 1
titi, toto
5, foo
Insert an array in another array
var a = [1,2,3,7,8,9]
var b = [4,5,6]
var insertIndex = 3;
a.splice.apply(a, Array.concat(insertIndex, 0, b));
Print(a); // prints: 1,2,3,4,5,6,7,8,9
Multiple string concatenation
var html = ['aaa', 'bbb', 'ccc', ...].join('');
Is faster than:
var html = 'aaa' + 'bbb' + 'ccc' + ...;
This is true with a big number of elements ( > 5000 )
HTTP headers parser
var rexp_keyval = /(.*?): ?(.*?)\r?\n/g;
function headersToObject( allHeaders ) {
var res, hdrObj = {};
for ( rexp_keyval.lastIndex = 0; res = rexp_keyval.exec(allHeaders); hdrObj[res[1]] = res[2])
return hdrObj;
}
Using 'with' scope
with({ a:5 }) function toto() { return a }
toto() // returns 5
(object).toString()
var a = { toString:function() { return '123'; } }
Print(a); // prints '123', and not [Object object]
RegExpr.$1
var re = /a(.*)/
'abcd'.match(re)
Print( RegExp.$1 ) // prints 'bcd'
Binary with XmlHTTPRequest
browser related example
var req = new XMLHttpRequest();
req.open('GET', "http://code.google.com/images/code_sm.png",false);
req.overrideMimeType('text/plain; charset=x-user-defined');
//req.overrideMimeType('application/octet-stream');
req.send(null);
var val = req.responseText;
Print( escape(val.substr(0,10)) );
Iterate on values
JavaScript 1.6
for each ( var i in [3,23,4] )
Print(i)
Prints:
3
23
4
Exceptions Handling / conditional catch (try catch if)
function Toto(){}
function Titi(){}
try {
throw new Titi()
} catch ( err if err instanceof Toto ) {
Print('toto')
} catch ( err if err instanceof Titi ) {
Print('titi')
} catch(ex) {
throw(ex);
}
Special chars
$=4
_=5
Print( _+$)
prints:
9
object's eval method
var foo = { bar:123 };
foo.eval('bar') // returns 123
var foo = { bar:123 };
with ( foo )
var val = eval( 'bar' );
Print( val ); // returns 123
eval this
function test() {
Print(eval('this'));
}
test.call(123)
prints:
123
No Such Method ( noSuchMethod )
var o = {}
o.__noSuchMethod__ = function(arg){ Print('unable to call "'+arg+'" function') }
o.foo(234)
prints:
unable to call "foo" function
RegExp replace
function Replacer( conversionObject ) {
var regexpStr = '';
for ( var k in conversionObject )
regexpStr += (regexpStr.length ? '|' : '') + k;
var regexpr = new RegExp(regexpStr,'ig'); // g: global, m:multi-line i: ignore case
return function(s) { return s.replace(regexpr, function(str, p1, p2, offset, s) { var a = conversionObject[str]; return a == undefined ? str : a }) }
}
var myReplacer = Replacer( { '<BR>':'\n', '&':'&', '<':'<', '>':'>', '"':'"' } );
Print( myReplacer('aa<BR>a &&&<') );
prints:
aa
a &&&<
Values comparison
[4] === 4 // is: false
[4] == 4 // is: true
'0' == 0 // is: true
'0' === 0 // is: false
undefined, null, 0, false, '', ...
var a = { b:undefined, c:null, d:0, f:'' }
a['b'] // is: undefined
a['e'] // is: undefined
'b' in a // is: true
'e' in a // is: false
Boolean(a.b) // is: false
Boolean(a.c) // is: false
Boolean(a.d) // is: false
Boolean(a.e) // is: false !
Boolean(a.f) // is: false
typeof( asvqwfevqwefq ) == 'undefined' // =true
Print( '' == false ); // prints: true
Print( 0 == false ); // prints: true
Print( [] == false ); // prints: true
Print( [] == '' ); // prints: true
Print( '' == 0 ); // prints: true
Print( [] == 0 ); // prints: true
constructor property ( InstanceOf + Type )
var a = {};
a.constructor === Object // is: true
AJAX evaluation
var a = {
b:function() { Print(123) }
}
var body = 'b();';
with(a) { eval(body) }; // Prints 123
Comma operator
var a = 0;
var b = ( a++, 99 );
a // is: 1
b // is: 99
var i = 0;
while( Print('x '), i++<10 )
Print(i + ' ')
prints: x 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x
closures pitfall
var a = [];
for ( var i=0; i<10; i++ ) {
a[i] = function() { Print(i); }
}
a[0](); // is: 10
a[1](); // is: 10
a[2](); // is: 10
a[3](); // is: 10
simpler case:
var i = 5;
function foo() { Print(i) }
foo(); // prints 5
i = 6;
foo(); // prints 6
Closure definition
A closure occurs when one function appears entirely within the body of another, and the inner function refers to local variables of the outer function.
links
obligated lecture to understand closures
In the first example, you can avoid the behavior using the let instruction:
var a = [];
for ( var i=0; i<10; i++ ) {
let j = i;
a[i] = function() { Print(j); }
}
In this case, each instances of the (anonymous) inner function refer to different instances of variable j.
sharp variable
var a = { titi:#1={}, toto:#1# };
a.titi === a.toto; // is: true
var a = { b:#1={ c:#1# } }
// a.b.c.c.c.c.c.c...
common object between 2 objects
function a() {}
a.prototype = { b:{} }
c = new a;
d = new a;
c.b.e = 2;
c.b === d.b // is: true !
d.b.e // is: 2
constructor
function a( b ) {
Print(b);
}
c.prototype = new a;
function c( arg ) {
this.constructor.apply( this, arg );
};
o = new c( [1,2,3] );
prints:
undefined
1
JavaScript string can contain null chars
var test = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00';
test.length // is: 10
'new' operator precedence
function test() {
this.toto = 1234;
}
(new test()).toto // ok ( logical way )
new test().toto // ok
new test.toto // bad !
(new test).toto // ok
constructor usage
function toto( val ) { // parent class
this.print = function() {
Print( val );
}
}
titi.prototype = new toto;
function titi( val ) { // child class
this.constructor(val);
}
(new titi(7)).print(); // prints 7
To object
var obj = Object(123);
obj.foo = 678;
Print( typeof( obj ) + ', ' + obj + ', ' + obj.foo ); // prints: object, 123, 678
Serialize or uneval a variable or an object ( can be nested )
var o = { a:123, b:'test', c:function() { return 987; } }
Print( o.toSource() ); // prints: ({a:123, b:"test", c:(function () {return 987;})})
Print( uneval(o) ); // prints: ({a:123, b:"test", c:(function () {return 987;})})
JavaScript labels
xxx: {
Print( 111 )
break xxx;
Print( 222 )
}
prints:
111
for in loop
for ( var i in function(){ return [1, 2, 3] }() )
Print( i );
prints:
0
1
2
proto ( prototype property )
var a = {}
var b = {}
a.__proto__ == b.__proto__ // is: true
Numbers and floating point error
Print( 1000000000000000128 ); // prints 1000000000000000100
Number base conversion ( hexadecimal )
(255).toString(16); // is: ff
parseInt( 'ff', 16 ) // is: 255
parseInt('0xff'); // is 255
try / catch / finally
try {
} catch(ex) {
Print('catch')
} finally {
Print('finally')
}
prints:
finally
Object argument
var proto = {
x: function() { return 'x' }
}
var o1 = new Object( proto );
var o2 = new Object( proto );
o1 == o2 // is: true
object and its prototype
function obj() {}
obj.prototype = { x:1 };
var b = new obj;
obj.prototype = { x:2 };
var c = new obj;
c.x == b.x // is: false
Runtime prototype object
var proto1 = {
a:function(){ return 1 }
}
var proto2 = {
a:function(){ return 2 }
}
function createObject( proto ) {
var cl = function() {}
cl.prototype = proto;
return new cl;
}
var v1 = createObject( proto1 ).a
var v2 = createObject( proto2 ).a
Print( v1() );
Print( v2() );
Print( createObject( proto1 ).a === createObject( proto1 ).a );
prints:
1
2
true
for in loop and undefined value
var o = { x:undefined }
for ( var i in o )
Print(i)
prints:
x
Call function in parent class
toto.prototype = new function() {
this.a = function() {
Print(456)
}
};
function toto() {
this.a=function(){
Print(123)
toto.prototype.a.call(this); // or: this.__proto__.a();
}
}
var o = new toto;
o.a();
prints:
123
456
getter and setter function
abcd getter = function() {
Print(345);
}
abcd;
abcd;
abcd;
prints:
345
345
345
Beware
The previous syntax used to define a getter is highly deprecated, and should be replaced with:
__defineGetter__('abcd', function() {
Print(345);
});
defineGetter ( define getter )
o = {}
o.__defineGetter__('x', function(){ Print('xxx')} )
o.x
prints:
xxx
check if an object or its prototype has a propery (hasOwnProperty)
var o = { a:1 }
o.__proto__ = { b:2 }
o.hasOwnProperty('a'); // is: true
o.hasOwnProperty('b'); // is: false
check this article about hasOwnProperty function
check if a property is enumerable (propertyIsEnumerable)
var o = { a:1 }
o.propertyIsEnumerable( 'a' ); // is: true
[].propertyIsEnumerable( 'splice' ); // is: false
find the getter/setter function of an object ( lookup getter )
function test() {
this.x getter = function(){ Print( 'getter' ) }
this.x setter = function(){ Print( 'setter' ) }
}
var t = new test
Print( t.__lookupGetter__( 'x' ) );
prints:
function () {
Print("getter");
}
suppress array element while iterating it
the following example will failed :
var a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
for ( var i in a ) {
if ( a[i] == 1 || a[i] == 2 )
a.splice( i, 1 );
}
Print( a ); // prints: 0,2,3,4,5,6,7,8,9
Print( a.length ); // prints: 9
We can use : a[i] == undefined; Or start from the end :
var a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
for ( var i = a.length - 1; i >= 0; i-- ) {
if ( a[i] == 0 || a[i] == 8 )
a.splice( i, 1 );
}
Print( a ); // prints: 1,2,3,4,5,6,7,9
Print( a.length ); // prints: 8
JavaScript arrays
var a = [ 1,2,3 ];
a.x = 'xxx';
for ( var i = 0; i < a.length; i++ )
Print('a: '+a[i]);
for ( var i in a )
Print('b: '+a[i]);
prints:
a: 1
a: 2
a: 3
b: 1
b: 2
b: 3
b: xxx
delete array element
The following do not work:
var ti = [5,6,7,8];
ti.length // is: 4
delete ti[3]
ti.length // is: 4
Use 'splice' instead:
ti.splice(3, 1);
понедельник, 29 сентября 2014 г.
пятница, 26 сентября 2014 г.
Усовершенствование стандартного Array
Arr.js - это класс, унаследованный от стандартного Array. Отличительными особенностями являются: наличие события change для отслеживания любых изменений в массиве, и методы insert(), update(), remove(), set(), get() для упрощенной работы с массивом. Доступны все родные методы стандартного Array.
(function(rootScope) {
var
arrayPop = Array.prototype.pop,
arrayPush = Array.prototype.push,
arrayReverse = Array.prototype.reverse,
arrayShift = Array.prototype.shift,
arraySort = Array.prototype.sort,
arraySplice = Array.prototype.splice,
arrayUnshift = Array.prototype.unshift;
/**
*
*/
var Arr = function() {
arrayPush.apply(this, arguments);
this.events = [];
};
Arr.prototype = [];
/**
*
*/
Arr.prototype.events = [];
/**
*
*/
Arr.prototype.get = function(index, defaultValue) {
defaultValue = typeof defaultValue === 'undefined' ? undefined: defaultValue;
return typeof this[index] === 'undefined' ? defaultValue: this[index];
};
/**
*
*/
Arr.prototype.on = function(eventName, handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
this.events.push({
name: eventName,
handler: handler
});
return this;
};
/**
*
*/
Arr.prototype.trigger = function(eventName, args) {
args = args || [];
for (var i=0,len=this.events.length; i<len; i++) {
if (this.events[i].name == eventName) {
this.events[i].handler.apply(this, [args]);
}
}
return this;
};
/**
*
*/
Arr.prototype.update = function(handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
var oldValue, newValue;
var result = [];
for (var i=0,len=this.length; i<len; i++) {
oldValue = this[i];
newValue = handler.apply(this, [oldValue, i]);
if (typeof newValue !== 'undefined') {
this[i] = newValue;
result.push(newValue);
}
}
if (result.length > 0) {
this.trigger('change', {
type: 'update',
items: result
});
}
return this;
};
/**
*
*/
Arr.prototype.insert = function(items) {
if (! items instanceof Array) {
throw new Error('items should be an Array');
}
arrayPush.apply(this, items);
this.trigger('change', {
type: 'insert',
items: items
});
return this;
};
/**
*
*/
Arr.prototype.remove = function(handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
var result = [];
var stay = [];
for (var i=0, len=this.length; i<len; i++) {
isRemove = handler.apply(this, [this[i], i]);
if (isRemove === true) {
result.push(this[i]);
} else {
stay.push(this[i]);
}
}
arraySplice.apply(this, [0, this.length]);
arrayPush.apply(this, stay);
if (result.length > 0) {
this.trigger('change', {
type: 'remove',
items: result
});
}
return this;
};
/**
*
*/
Arr.prototype.set = function(index, value) {
if (! index instanceof Number) {
throw new Error('index should be an Number');
}
this[index] = value;
this.trigger('change', {
type: 'update',
items: [this[index]]
});
return this;
};
/**
* Removes the last element from an array and returns that element.
*/
Arr.prototype.pop = function() {
var result = arrayPop.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: [result]
});
return result;
};
/**
* Adds one or more elements to the end of an array and returns the new length of the array.
*/
Arr.prototype.push = function() {
var result = arrayPush.apply(this, arguments);
this.trigger('change', {
type: 'insert',
items: Array.prototype.slice.call(arguments, 0)
});
return result;
};
/**
* Reverses the order of the elements of an array — the first becomes the last, and the last becomes the first.
*/
Arr.prototype.reverse = function() {
var result = arrayReverse.apply(this, arguments);
this.trigger('change', {
type: 'update',
items: result.slice(0)
});
return result;
};
/**
* Removes the first element from an array and returns that element.
*/
Arr.prototype.shift = function() {
var result = arrayShift.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: [result]
});
return result;
};
/**
* Sorts the elements of an array in place and returns the array.
*/
Arr.prototype.sort = function() {
var result = arraySort.apply(this, arguments);
this.trigger('change', {
type: 'update',
items: result
});
return result;
};
/**
* Adds and/or removes elements from an array.
*/
Arr.prototype.splice = function() {
var items = this.slice(arguments[0], arguments[0]+arguments[1]);
var result = arraySplice.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: items
});
return result;
};
/**
* Adds one or more elements to the front of an array and returns the new length of the array.
*/
Arr.prototype.unshift = function() {
var result = arrayUnshift.apply(this, arguments);
this.trigger('change', {
type: 'insert',
items: [result]
});
return result;
};
// export
if (typeof module !== 'undefined') {
module.exports = Arr;
} else {
rootScope.Arr = Arr;
}
})(this);
Примеры работы.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function() {
alert('I changed fruits: ' + fruits.join(', '));
});
fruits.push('banana');
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.get(0);
// apple
fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
// lime
fruits.get(20); // trying to get undefined element
// null
fruits.set(1, 'nut');
// ['nut', 'orange', 'pineapple']
fruits.insert(['lime', 'banana', 'kivi']);
// ['nut', 'orange', 'pineapple', 'lime', 'banana', 'kivi']
fruits.remove(function(item, index) {
if (item.indexOf('apple') !== -1) { // remove only items where word "apple" is founded
return true;
}
});
// ['nut', 'orange', 'lime', 'banana', 'kivi']
fruits.update(function(item, index) {
if (item.indexOf('nut') !== -1) { // update "nut" to "apple"
return 'apple';
}
});
// ['apple', 'orange', 'lime', 'banana', 'kivi']
Зачем событие change и как с ними работать.
Наличие события позволяет сделать:
подобие FRP: когда изменение одних данных должно повлечь за собой изменение других данных и так далее
отложенный рендеринг: что то изменилось в массиве — обновили HTML (ala angular)
автоматическое сохранение данных на сервер при любых изменениях
Поддерживается одно событие — change.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) { // handler
console.log(event);
});
fruits.push('banana');
// { "type": "insert", "items": ["banana"] }
fruits.remove(function(item) { return item == 'banana'; });
// { "type": "remove", "items": ['banana"] }
Понять что произошло в массиве можно по передаваемому в handler объекту, event. Свойства объекта event: type может принимать значения: insert, update, remove. Свойство items позволяет узнать какие элементы массива были затронуты.
Наглядный пример
// Массив в котором планируем хранить данные о погоде
var weatherList = new Arr;
// При изменении в массиве - перересовываем список
weatherList.on('change', function() {
var el = $('#weather');
var celsius, maxCelsius, minCelsius, weather;
el.html('');
weatherList.forEach(function(item) {
celsius = Math.floor(item.main.temp - 273);
maxCelsius = Math.floor(item.main.temp_max - 273);
minCelsius = Math.floor(item.main.temp_min - 273);
weather = item.weather.pop().main;
el.append('<li><b>' + item.name + '</b> ' + ' ' + celsius + ' (max: ' + maxCelsius + ', min: ' + minCelsius + ') ' + weather + '</li>');
});
});
// Загрузка погоды из сервиса, обновление массива weatherList
function loadWeather(lat, lng) {
$.get('http://api.openweathermap.org/data/2.5/find?lat=' + lat + '&lon=' + lng + '&cnt=10').done(function(data) {
// clear weather list
weatherList.remove(function() { return true; });
// insert items
weatherList.insert(data.list);
});
}
// Погода в Киеве
loadWeather(50.4666537, 30.5844519);
Примеры работы основных методов.
new Arr([item1, item2, ..., itemN])
Initialize new array items: item1, item2, ..., itemN.
Properties
events
List of Attached Events.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
console.log('fruits list is changed.');
});
// fruits.events
// [{
// "name": "change",
// "handler": function() { ... }
// }]
fruits.push('mango');
// fruits list is changed.
// fruits
// ['apple', 'orange', 'pineapple', 'mango']
length
Standard property length.
Accessor methods
get(index [, defaultValue])
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.get(0);
// apple
fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
// lime
fruits.get(20); // trying to get undefined element
// undefined
Standard accessor methods are supported
concat() Returns a new array comprised of this array joined with other array(s) and/or value(s).
join() Joins all elements of an array into a string.
slice() Extracts a section of an array and returns a new array.
toString() Returns a string representing the array and its elements. Overrides the Object.prototype.toString() method.
toLocaleString() Returns a localized string representing the array and its elements. Overrides the Object.prototype.toLocaleString() method.
indexOf() Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
lastIndexOf() Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
and other methods.
Mutator methods
Notice: Traditional mutator arr[index] = value do not trigger event change. Use method set(index, value) instead arr[index] = value.
set(index, value)
Set value by index. Will be triggered event change.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.set(0, 'banana');
// ['banana', 'orange', 'pineapple']
fruits.set(1, 'lime');
// ['banana', 'lime', 'pineapple']
fruits.set(3, 'nut');
// ['banana', 'lime', 'pineapple', 'nut']
insert([item1, item2, ..., itemN])
Insert array of items. Will be triggered event change.
Example:
var fruits = new Arr();
fruits.inser('apple', 'orange', 'pineapple');
// ['apple', 'orange', 'pineapple']
update(handler)
Update item if handler return true. Will be triggered event change if one or more items updated.
handler can recive data:
value (mixed) current value
index (number) current index
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.update(function(value, index) {
if (index === 2) {
return 'lime'; // "return" not undefined value for update item
}
});
// ['apple', 'orange', 'lime']
fruits.update(function(value, index) {
return index;
});
// [0, 1, 2]
remove(handler)
Remove item if handler return true. Will be triggered event change if one or more items removed.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.remove(function(value, index) {
if (value.indexOf('apple') !== -1) {
return true;
}
});
// ['orange']
Standard mutator methods are supported
Each mutator method throw event change. How? You can read in section Events.
pop() Removes the last element from an array and returns that element.
push() Adds one or more elements to the end of an array and returns the new length of the array.
reverse() Reverses the order of the elements of an array — the first becomes the last, and the last becomes the first.
shift() Removes the first element from an array and returns that element.
sort() Sorts the elements of an array in place and returns the array.
splice() Adds and/or removes elements from an array.
unshift() Adds one or more elements to the front of an array and returns the new length of the array.
and other methods.
Sometimes you need to push array of items to Arr. You can push array of items in this way (note: now you can use method insert()):
var fruits = new Arr;
fruits.push.apply(fruits, ['apple', 'orange', 'pineapple']);
// fruits
// ['apple', 'orange', 'pineapple']
For remove item(s) from Arr you can use traditional method splice() (note: now you can use method remove()).
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.splice(0, 1); // remove first item
// fruits
// ['orange', 'pineapple']
Iteration methods
forEach() Calls a function for each element in the array.
every() Returns true if every element in this array satisfies the provided testing function.
some() Returns true if at least one element in this array satisfies the provided testing function.
filter() Creates a new array with all of the elements of this array for which the provided filtering function returns true.
map() Creates a new array with the results of calling a provided function on every element in this array.
reduce() Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
reduceRight() Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.
and other methods.
You can use method filter() for searching items by conditions.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
var fruitsWithWordApple = fruits.filter(function(value, index) {
if (value.indexOf('apple') !== -1) {
return true;
} else {
return false;
}
});
// fruitsWithWordApple
// ['apple', 'pineapple']
Events
Instance of Arr throw only one event change.
How to use events? You can use array events like events in Backbone Collection.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
// event
// {
// "type": "insert",
// "items": ['mongo']
// }
console.log('fruits list is changed.');
});
// fruits.events
// [{
// "name": "change",
// "handler": function() { ... }
// }]
fruits.push('mango');
// fruits list is changed.
// fruits
// ['apple', 'orange', 'pineapple', 'mango']
Events Handling and Triggering
Notice: Traditional mutator arr[index] = value do not trigger event change. Use method set(index, value) instead arr[index] = value.
on(eventName, handler)
Attach event handler.
handler can recive event that have data:
type (string) can be insert, update, remove
items (array) are items that was inserted, updated or removed
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
// event
// {
// "type": "insert",
// "items": ['mango']
// }
console.log('fruits list is changed.');
});
fruits.push('mango');
trigger(eventName [, args])
Trigger some event.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
console.log('fruits list is changed.');
});
fruits.trigger('change');
// fruits list is changed.
Use Cases
Send server updates when Arr is changed.
var products = new Arr(
{
id: 1,
name: 'apple'
},
{
id: 2,
name: 'orange'
},
{
id: 3,
name: 'pineapple'
}
);
products.on('change', function(event) {
// products are changed
// you can use event.type == 'insert' or 'update' or 'remove' to detect items that you need to update on the server
// $ is link on jQuery
$.post('/products', products)
.fail(function() {
alert('error');
})
.done(function(updatedProducts) {
alert('products are updated');
});
});
products.push({
name: 'lime'
});
(function(rootScope) {
var
arrayPop = Array.prototype.pop,
arrayPush = Array.prototype.push,
arrayReverse = Array.prototype.reverse,
arrayShift = Array.prototype.shift,
arraySort = Array.prototype.sort,
arraySplice = Array.prototype.splice,
arrayUnshift = Array.prototype.unshift;
/**
*
*/
var Arr = function() {
arrayPush.apply(this, arguments);
this.events = [];
};
Arr.prototype = [];
/**
*
*/
Arr.prototype.events = [];
/**
*
*/
Arr.prototype.get = function(index, defaultValue) {
defaultValue = typeof defaultValue === 'undefined' ? undefined: defaultValue;
return typeof this[index] === 'undefined' ? defaultValue: this[index];
};
/**
*
*/
Arr.prototype.on = function(eventName, handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
this.events.push({
name: eventName,
handler: handler
});
return this;
};
/**
*
*/
Arr.prototype.trigger = function(eventName, args) {
args = args || [];
for (var i=0,len=this.events.length; i<len; i++) {
if (this.events[i].name == eventName) {
this.events[i].handler.apply(this, [args]);
}
}
return this;
};
/**
*
*/
Arr.prototype.update = function(handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
var oldValue, newValue;
var result = [];
for (var i=0,len=this.length; i<len; i++) {
oldValue = this[i];
newValue = handler.apply(this, [oldValue, i]);
if (typeof newValue !== 'undefined') {
this[i] = newValue;
result.push(newValue);
}
}
if (result.length > 0) {
this.trigger('change', {
type: 'update',
items: result
});
}
return this;
};
/**
*
*/
Arr.prototype.insert = function(items) {
if (! items instanceof Array) {
throw new Error('items should be an Array');
}
arrayPush.apply(this, items);
this.trigger('change', {
type: 'insert',
items: items
});
return this;
};
/**
*
*/
Arr.prototype.remove = function(handler) {
if (! handler instanceof Function) {
throw new Error('handler should be an Function');
}
var result = [];
var stay = [];
for (var i=0, len=this.length; i<len; i++) {
isRemove = handler.apply(this, [this[i], i]);
if (isRemove === true) {
result.push(this[i]);
} else {
stay.push(this[i]);
}
}
arraySplice.apply(this, [0, this.length]);
arrayPush.apply(this, stay);
if (result.length > 0) {
this.trigger('change', {
type: 'remove',
items: result
});
}
return this;
};
/**
*
*/
Arr.prototype.set = function(index, value) {
if (! index instanceof Number) {
throw new Error('index should be an Number');
}
this[index] = value;
this.trigger('change', {
type: 'update',
items: [this[index]]
});
return this;
};
/**
* Removes the last element from an array and returns that element.
*/
Arr.prototype.pop = function() {
var result = arrayPop.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: [result]
});
return result;
};
/**
* Adds one or more elements to the end of an array and returns the new length of the array.
*/
Arr.prototype.push = function() {
var result = arrayPush.apply(this, arguments);
this.trigger('change', {
type: 'insert',
items: Array.prototype.slice.call(arguments, 0)
});
return result;
};
/**
* Reverses the order of the elements of an array — the first becomes the last, and the last becomes the first.
*/
Arr.prototype.reverse = function() {
var result = arrayReverse.apply(this, arguments);
this.trigger('change', {
type: 'update',
items: result.slice(0)
});
return result;
};
/**
* Removes the first element from an array and returns that element.
*/
Arr.prototype.shift = function() {
var result = arrayShift.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: [result]
});
return result;
};
/**
* Sorts the elements of an array in place and returns the array.
*/
Arr.prototype.sort = function() {
var result = arraySort.apply(this, arguments);
this.trigger('change', {
type: 'update',
items: result
});
return result;
};
/**
* Adds and/or removes elements from an array.
*/
Arr.prototype.splice = function() {
var items = this.slice(arguments[0], arguments[0]+arguments[1]);
var result = arraySplice.apply(this, arguments);
this.trigger('change', {
type: 'remove',
items: items
});
return result;
};
/**
* Adds one or more elements to the front of an array and returns the new length of the array.
*/
Arr.prototype.unshift = function() {
var result = arrayUnshift.apply(this, arguments);
this.trigger('change', {
type: 'insert',
items: [result]
});
return result;
};
// export
if (typeof module !== 'undefined') {
module.exports = Arr;
} else {
rootScope.Arr = Arr;
}
})(this);
Примеры работы.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function() {
alert('I changed fruits: ' + fruits.join(', '));
});
fruits.push('banana');
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.get(0);
// apple
fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
// lime
fruits.get(20); // trying to get undefined element
// null
fruits.set(1, 'nut');
// ['nut', 'orange', 'pineapple']
fruits.insert(['lime', 'banana', 'kivi']);
// ['nut', 'orange', 'pineapple', 'lime', 'banana', 'kivi']
fruits.remove(function(item, index) {
if (item.indexOf('apple') !== -1) { // remove only items where word "apple" is founded
return true;
}
});
// ['nut', 'orange', 'lime', 'banana', 'kivi']
fruits.update(function(item, index) {
if (item.indexOf('nut') !== -1) { // update "nut" to "apple"
return 'apple';
}
});
// ['apple', 'orange', 'lime', 'banana', 'kivi']
Зачем событие change и как с ними работать.
Наличие события позволяет сделать:
подобие FRP: когда изменение одних данных должно повлечь за собой изменение других данных и так далее
отложенный рендеринг: что то изменилось в массиве — обновили HTML (ala angular)
автоматическое сохранение данных на сервер при любых изменениях
Поддерживается одно событие — change.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) { // handler
console.log(event);
});
fruits.push('banana');
// { "type": "insert", "items": ["banana"] }
fruits.remove(function(item) { return item == 'banana'; });
// { "type": "remove", "items": ['banana"] }
Понять что произошло в массиве можно по передаваемому в handler объекту, event. Свойства объекта event: type может принимать значения: insert, update, remove. Свойство items позволяет узнать какие элементы массива были затронуты.
Наглядный пример
// Массив в котором планируем хранить данные о погоде
var weatherList = new Arr;
// При изменении в массиве - перересовываем список
weatherList.on('change', function() {
var el = $('#weather');
var celsius, maxCelsius, minCelsius, weather;
el.html('');
weatherList.forEach(function(item) {
celsius = Math.floor(item.main.temp - 273);
maxCelsius = Math.floor(item.main.temp_max - 273);
minCelsius = Math.floor(item.main.temp_min - 273);
weather = item.weather.pop().main;
el.append('<li><b>' + item.name + '</b> ' + ' ' + celsius + ' (max: ' + maxCelsius + ', min: ' + minCelsius + ') ' + weather + '</li>');
});
});
// Загрузка погоды из сервиса, обновление массива weatherList
function loadWeather(lat, lng) {
$.get('http://api.openweathermap.org/data/2.5/find?lat=' + lat + '&lon=' + lng + '&cnt=10').done(function(data) {
// clear weather list
weatherList.remove(function() { return true; });
// insert items
weatherList.insert(data.list);
});
}
// Погода в Киеве
loadWeather(50.4666537, 30.5844519);
Примеры работы основных методов.
new Arr([item1, item2, ..., itemN])
Initialize new array items: item1, item2, ..., itemN.
Properties
events
List of Attached Events.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
console.log('fruits list is changed.');
});
// fruits.events
// [{
// "name": "change",
// "handler": function() { ... }
// }]
fruits.push('mango');
// fruits list is changed.
// fruits
// ['apple', 'orange', 'pineapple', 'mango']
length
Standard property length.
Accessor methods
get(index [, defaultValue])
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.get(0);
// apple
fruits.get(10, 'lime'); // trying to get undefined element - return defaultValue
// lime
fruits.get(20); // trying to get undefined element
// undefined
Standard accessor methods are supported
concat() Returns a new array comprised of this array joined with other array(s) and/or value(s).
join() Joins all elements of an array into a string.
slice() Extracts a section of an array and returns a new array.
toString() Returns a string representing the array and its elements. Overrides the Object.prototype.toString() method.
toLocaleString() Returns a localized string representing the array and its elements. Overrides the Object.prototype.toLocaleString() method.
indexOf() Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
lastIndexOf() Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
and other methods.
Mutator methods
Notice: Traditional mutator arr[index] = value do not trigger event change. Use method set(index, value) instead arr[index] = value.
set(index, value)
Set value by index. Will be triggered event change.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.set(0, 'banana');
// ['banana', 'orange', 'pineapple']
fruits.set(1, 'lime');
// ['banana', 'lime', 'pineapple']
fruits.set(3, 'nut');
// ['banana', 'lime', 'pineapple', 'nut']
insert([item1, item2, ..., itemN])
Insert array of items. Will be triggered event change.
Example:
var fruits = new Arr();
fruits.inser('apple', 'orange', 'pineapple');
// ['apple', 'orange', 'pineapple']
update(handler)
Update item if handler return true. Will be triggered event change if one or more items updated.
handler can recive data:
value (mixed) current value
index (number) current index
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.update(function(value, index) {
if (index === 2) {
return 'lime'; // "return" not undefined value for update item
}
});
// ['apple', 'orange', 'lime']
fruits.update(function(value, index) {
return index;
});
// [0, 1, 2]
remove(handler)
Remove item if handler return true. Will be triggered event change if one or more items removed.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.remove(function(value, index) {
if (value.indexOf('apple') !== -1) {
return true;
}
});
// ['orange']
Standard mutator methods are supported
Each mutator method throw event change. How? You can read in section Events.
pop() Removes the last element from an array and returns that element.
push() Adds one or more elements to the end of an array and returns the new length of the array.
reverse() Reverses the order of the elements of an array — the first becomes the last, and the last becomes the first.
shift() Removes the first element from an array and returns that element.
sort() Sorts the elements of an array in place and returns the array.
splice() Adds and/or removes elements from an array.
unshift() Adds one or more elements to the front of an array and returns the new length of the array.
and other methods.
Sometimes you need to push array of items to Arr. You can push array of items in this way (note: now you can use method insert()):
var fruits = new Arr;
fruits.push.apply(fruits, ['apple', 'orange', 'pineapple']);
// fruits
// ['apple', 'orange', 'pineapple']
For remove item(s) from Arr you can use traditional method splice() (note: now you can use method remove()).
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.splice(0, 1); // remove first item
// fruits
// ['orange', 'pineapple']
Iteration methods
forEach() Calls a function for each element in the array.
every() Returns true if every element in this array satisfies the provided testing function.
some() Returns true if at least one element in this array satisfies the provided testing function.
filter() Creates a new array with all of the elements of this array for which the provided filtering function returns true.
map() Creates a new array with the results of calling a provided function on every element in this array.
reduce() Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
reduceRight() Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.
and other methods.
You can use method filter() for searching items by conditions.
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
var fruitsWithWordApple = fruits.filter(function(value, index) {
if (value.indexOf('apple') !== -1) {
return true;
} else {
return false;
}
});
// fruitsWithWordApple
// ['apple', 'pineapple']
Events
Instance of Arr throw only one event change.
How to use events? You can use array events like events in Backbone Collection.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
// event
// {
// "type": "insert",
// "items": ['mongo']
// }
console.log('fruits list is changed.');
});
// fruits.events
// [{
// "name": "change",
// "handler": function() { ... }
// }]
fruits.push('mango');
// fruits list is changed.
// fruits
// ['apple', 'orange', 'pineapple', 'mango']
Events Handling and Triggering
Notice: Traditional mutator arr[index] = value do not trigger event change. Use method set(index, value) instead arr[index] = value.
on(eventName, handler)
Attach event handler.
handler can recive event that have data:
type (string) can be insert, update, remove
items (array) are items that was inserted, updated or removed
Example:
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
// event
// {
// "type": "insert",
// "items": ['mango']
// }
console.log('fruits list is changed.');
});
fruits.push('mango');
trigger(eventName [, args])
Trigger some event.
var fruits = new Arr('apple', 'orange', 'pineapple');
fruits.on('change', function(event) {
console.log('fruits list is changed.');
});
fruits.trigger('change');
// fruits list is changed.
Use Cases
Send server updates when Arr is changed.
var products = new Arr(
{
id: 1,
name: 'apple'
},
{
id: 2,
name: 'orange'
},
{
id: 3,
name: 'pineapple'
}
);
products.on('change', function(event) {
// products are changed
// you can use event.type == 'insert' or 'update' or 'remove' to detect items that you need to update on the server
// $ is link on jQuery
$.post('/products', products)
.fail(function() {
alert('error');
})
.done(function(updatedProducts) {
alert('products are updated');
});
});
products.push({
name: 'lime'
});
четверг, 25 сентября 2014 г.
Простой набор паттернов JavaScript
Паттерны JavaScript
1) Class
function Car (model) {
this.model = model;
this.year = 2012;
}
Car.prototype.getInfo = function () {
return this.model + ' ' + this.year;
};
var myCar = new Car('Ford');
myCar.year = 2010;
console.log(myCar.getInfo());
2) Mixin
var Person = function (firstName , lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = 'male';
};
var Superhero = function (firstName, lastName , powers) {
// Invoke the superclass constructor on the new object
// then use .call() to invoke the constructor as a method of
// the object to be initialized.
Person.call(this, firstName, lastName);
// Finally, store their powers, a new array of traits not found in a normal "Person"
this.powers = powers;
};
// или
function applyMixins(derivedCtor, baseCtors) {
baseCtors.forEach(function (baseCtor) {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(function (name) {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
// Disposable Mixin
var Disposable = (function () {
function Disposable() {}
Disposable.prototype.dispose = function () {this.isDisposed = true;};
return Disposable;
})();
// Activatable Mixin
var Activatable = (function () {
function Activatable() {}
Activatable.prototype.activate = function () {this.isActive = true;};
Activatable.prototype.deactivate = function () {this.isActive = false;};
return Activatable;
})();
var SmartObject = (function () {
function SmartObject() {
var _this = this;
this.isDisposed = false; // Disposable
this.isActive = false; // Activatable
setInterval(function () {return console.log(_this.isActive + " : " + _this.isDisposed);}, 500);
}
SmartObject.prototype.interact = function () {this.activate();};
return SmartObject;
})();
applyMixins(SmartObject, [Disposable, Activatable]);
var smartObj = new SmartObject();
3) Extend
var extend = this.extend || function (childClass, parentClass) {
for (var key in parentClass) {
if (parentClass.hasOwnProperty(key)) {
childClass[key] = parentClass[key];
}
}
function F () {this.constructor = childClass;}
F.prototype = parentClass.prototype;
childClass.prototype = new F();
};
var ParentClass = (function () {
// Constructor
function ParentClass(message) {
// Constructor body
this.greeting = message;
this.x = 1;
this.y = 2;
this.z = 3;
}
// Public function
ParentClass.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return ParentClass;
})();
var ChildClass = (function (parentClass) {
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass(message) {
parentClass.apply(this, arguments); // или можно сделать частично parentClass.call(this, message);
this.greeting = message;
}
// Public variable
ChildClass.prototype.name = 'Parent';
// Public function
ChildClass.prototype.greet = function () {
return 'Hello, ' + this.greeting;
};
// Public function
ChildClass.prototype.hey = function () {
return 'Hey! ' + privateFunc();
};
// Private variable
var privateVar = 'Hello';
// Private function
function privateFunc () {
return privateVar + ' my friend!';
}
// Static variable
ChildClass.staticVar = 'I am static variable';
// Static function
function staticFunc () {
return 'I am static function';
}
return ChildClass;
})(ParentClass);
var SubClass = (function (parentClass) {
extend(SubClass, parentClass);
function SubClass(message) {
parentClass.apply(this, arguments);
this.greeting = message;
}
SubClass.prototype.greet = function () {
return 'Hello, ' + this.greeting;
};
SubClass.prototype.hey = function () {
return 'Hey!';
};
return SubClass;
})(ChildClass);
var parentObject = new ParentClass('world');
var childObject = new ChildClass();
var subObject = new SubClass();
console.log(parentObject.greet());
console.log(childObject.hey());
console.log(subObject.hey());
4) Module
var module = (function($){
var privateVar = 1
, publicVar = 2 + privateVar;
function Boat (name) {
this.name = name;
this.year = 2012;
}
Boat.prototype.getInfo = function () {
return this.name + ' ' + this.year;
};
function Plane (name) {
this.name = name;
this.year = 2012;
}
Plane.prototype.getInfo = function () {
return this.name + ' ' + this.year;
};
function insertHTML (tag, HTMLcode) {
$(tag).html(HTMLcode);
}
return {
publicVar: publicVar
, Boat: Boat
, Plane: Plane
, insertHTML: insertHTML
};
})(jQuery);
console.log(model.publicVar);
var myBoat = new model.Boat('Ann');
myBoat.year = 2010;
console.log(myBoat.getInfo());
model.insertHTML('div', '<h1>Hello!</h1>');
// Using CommonJS, AMD or browser globals to create a module
(function (root, factory) {
if (typeof exports === 'object') {factory(exports, require('b')); // CommonJS
} else if (typeof define === 'function' && define.amd) {define(['exports', 'b'], factory); // AMD. Register as an anonymous module.
} else {factory((root.commonJsStrict = {}), root.b); // Browser globals
}
}(this, function (exports, b) {
// use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
5) Namespace
var myNamespace = myNamespace || {};
myNamespace.someVariable = 1;
myNamespace.someFunction = function () {alert(myNamespace.someVariable);};
// Namespace injection
var myApp = myApp || {};
myApp.utils = {};
(function () {
var val = 5;
this.getValue = function () {return val;};
this.setValue = function(newVal) {val = newVal;}
// also introduce a new sub-namespace
this.tools = {};
}).apply(myApp.utils);
(function () {
this.diagnose = function(){return "diagnosis";}
}).apply(myApp.utils.tools);
console.log(myApp); // Outputs: 5
console.log(myApp.utils.getValue());
myApp.utils.setValue(25);
console.log(myApp.utils.getValue());
console.log(myApp.utils.tools.diagnose());
6) Singleton
var singleton = (function(){
var instance;
function init () {
var privateVariable = 'I am private variable.';
function privatMethod () {
console.log('I am private method.');
}
return {
publicProperty: 'I am public property.'
, publicMethod: function () {
console.log('I am public method.');
}
};
}
return {
getInstance: function () {
if (!instance) {
instance = init();
}
return instance;
}
}
})();
var mySingleton1 = singleton.getInstance()
, mySingleton2 = singleton.getInstance();
console.log(mySingleton1 === mySingleton2); // true
console.log(mySingleton1.publicProperty);
mySingleton1.publicMethod();
7) Observer
function Observer () {
this.events = [];
}
Observer.prototype.on = function (eventName, callbackFunction) {
return this.events.push({
eventName: eventName
, callbackFunction: callbackFunction
});
};
Observer.prototype.off = function (eventName) {
var eventsLength = this.events.length;
for (;eventsLength--;) {
if (this.events[eventsLength].eventName === eventName) {
this.events.splice(eventsLength, 1);
}
}
};
Observer.prototype.trigger = function (eventName) {
var eventsLength = this.events.length;
for (;eventsLength--;) {
if (this.events[eventsLength].eventName === eventName) {
this.events.callbackFunction();
}
}
}
Observer.prototype.count = function () {
return this.events.length;
};
8) Command
var carManager = {
requestInfo: function (model, id) {
return "The information for " + model + " with ID " + id + " is foobar";
}
, buyVehicle: function (model, id) {
return "You have successfully purchased Item " + id + ", a " + model;
}
, arrangeViewing: function (model, id) {
return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
}
};
var car = {
model: 'Ford'
, id: 52738
};
console.log(carManager.call(car));
// или
carManager.execute = function (name) {
return carManager[name] && carManager[name].apply(null, [].slice.call(arguments, 1));
};
console.log(carManager.execute('buyVehicle', 'Ford Escort', '34232'));
9) Facade
var addMyEvent = function (element, eventName, callbackFunction) {
if (element.addEventListener) {element.addEventListener(eventName, callbackFunction, false);
} else if (element.attachEvent) {element.attachEvent('on' + eventName, callbackFunction);
} else {element['on' + eventName] = callbackFunction;
}
};
addMyEvent(document.getElementById('myButton'), 'click', function(){alert('OK');});
10) Factory
function Car (options) {
this.doors = options.doors || 4;
this.state = options.state || 'brand new';
this.color = options.color || 'silver';
}
function Truck (options) {
this.state = options.state || 'used';
this.wheelSize = options.wheelSize || 'large';
this.color = options.color || 'blue';
}
function VehicleFactory() {}
VehicleFactory.prototype.vehicleClass = Car;
VehicleFactory.prototype.createVehicle = function (options) {
switch(options.vehicleType){
case 'car':this.vehicleClass = Car; break;
case 'truck':this.vehicleClass = Truck; break;
}
return new this.vehicleClass(options);
};
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
vehicleType: 'car'
, color: 'yellow',
, doors: 6
});
var movingTruck = carFactory.createVehicle( {
vehicleType: 'truck'
, state: 'like new'
, color: 'red'
, wheelSize: 'small'
});
11) Decorator
function door () {
return 'metal door';
}
function decorator (fn) {
return function(){
return 'black ' + fn();
}
}
door = decorator(door);
console.log(door());
12) Iterator
function forEachInArrayDo (array, func) {
for (var i = 0, len = array.length; i < len; i++) {
func(array[i]);
}
}
var arr = [1,2,3,4,5];
forEachInArrayDo(arr, function(element){console.log(element);});
13) Curry
function curry (func) {
return function (args) {
return func (args);
};
}
['11','11','11','11'].map(parseInt) // [11, NaN, 3, 4]
['11','11','11','11'].map(curry(parseInt)); // [11, 11, 11, 11]
function curry2(fun) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg);
};
};
}
function div(n, d) {return n / d;}
var div10 = curry2(div)(10);
div10(50); // 5
function curry3(fun) {
return function(last) {
return function(middle) {
return function(first) {
return fun(first, middle, last);
};
};
};
};
var songsPlayed = curry3(_.uniq)(false)(songToString);
songsPlayed(plays);
// [{artist: "Burial", track: "Archangel"},
// {artist: "Ben Frost", track: "Stomp"},
// {artist: "Emeralds", track: "Snores"}]
14) Proxy
$('button').on('click', function () {
setTimeout($.proxy(function () {
$(this).addClass('active'); // "this" теперь ссылается на внешний "this" функции "click"
}, this), 500); // <-- Этот "this" передается из функции "click" во внутреннюю функцию внутри "proxy"
});
// jQuery's implementation of jQuery.proxy() can be found below:
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function(fn, context) {
if (typeof context === "string") {
var tmp = fn[context];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if (!jQuery.isFunction( fn ) ) {return undefined;}
// Simulated bind
var args = slice.call( arguments, 2 ),
proxy = function() {
return fn.apply( context, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
return proxy;
}
15) jQuery plugin
/*!
* jQuery lightweight plugin boilerplate
* Original author: @ajpiano
* Further changes, comments: @addyosmani
* Licensed under the MIT license
*/
// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {
// undefined is used here as the undefined global
// variable in ECMAScript 3 and is mutable (i.e. it can
// be changed by someone else). undefined isn't really
// being passed in so we can ensure that its value is
// truly undefined. In ES5, undefined can no longer be
// modified.
// window and document are passed through as local
// variables rather than as globals, because this (slightly)
// quickens the resolution process and can be more
// efficiently minified (especially when both are
// regularly referenced in our plugin).
// Create the defaults once
var pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
// jQuery has an extend method that merges the
// contents of two or more objects, storing the
// result in the first object. The first object
// is generally empty because we don't want to alter
// the default options for future instances of the plugin
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype.init = function () {
// Place initialization logic here
// We already have access to the DOM element and
// the options via the instance, e.g. this.element
// and this.options
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if ( !$.data(this, "plugin_" + pluginName )) {
$.data( this, "plugin_" + pluginName,
new Plugin( this, options ));
}
});
}
})( jQuery, window, document );
$("#elem").defaultPluginName({
propertyName: "a custom value"
});
16) jQuery UI widget factory
/*!
* jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
* Author: @addyosmani
* Further changes: @peolanha
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
// define our widget under a namespace of your choice
// with additional parameters e.g.
// $.widget( "namespace.widgetname", (optional) - an
// existing widget prototype to inherit from, an object
// literal to become the widget's prototype );
$.widget( "namespace.widgetname" , {
//Options to be used as defaults
options: {
someValue: null
},
//Setup widget (e.g. element creation, apply theming
// , bind events etc.)
_create: function () {
// _create will automatically run the first time
// this widget is called. Put the initial widget
// setup code here, then we can access the element
// on which the widget was called via this.element.
// The options defined above can be accessed
// via this.options this.element.addStuff();
},
// Destroy an instantiated plugin and clean up
// modifications the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call( this );
// For UI 1.9, define _destroy instead and don't
// worry about
// calling the base widget
},
methodB: function ( event ) {
//_trigger dispatches callbacks the plugin user
// can subscribe to
// signature: _trigger( "callbackName" , [eventObject],
// [uiObject] )
// e.g. this._trigger( "hover", e /*where e.type ==
// "mouseenter"*/, { hovered: $(e.target)});
this._trigger( "methodA", event, {
key: value
});
},
methodA: function ( event ) {
this._trigger( "dataChanged", event, {
key: value
});
},
// Respond to any changes the user makes to the
// option method
_setOption: function ( key, value ) {
switch ( key ) {
case "someValue":
// this.options.someValue = doSomethingWith( value );
break;
default:
// this.options[ key ] = value;
break;
}
// For UI 1.8, _setOption must be manually invoked
// from the base widget
$.Widget.prototype._setOption.apply( this, arguments );
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
}
});
})( jQuery, window, document );
var collection = $("#elem").widgetName({
foo: false
});
collection.widgetName("methodB");
1) Class
function Car (model) {
this.model = model;
this.year = 2012;
}
Car.prototype.getInfo = function () {
return this.model + ' ' + this.year;
};
var myCar = new Car('Ford');
myCar.year = 2010;
console.log(myCar.getInfo());
2) Mixin
var Person = function (firstName , lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = 'male';
};
var Superhero = function (firstName, lastName , powers) {
// Invoke the superclass constructor on the new object
// then use .call() to invoke the constructor as a method of
// the object to be initialized.
Person.call(this, firstName, lastName);
// Finally, store their powers, a new array of traits not found in a normal "Person"
this.powers = powers;
};
// или
function applyMixins(derivedCtor, baseCtors) {
baseCtors.forEach(function (baseCtor) {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(function (name) {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
// Disposable Mixin
var Disposable = (function () {
function Disposable() {}
Disposable.prototype.dispose = function () {this.isDisposed = true;};
return Disposable;
})();
// Activatable Mixin
var Activatable = (function () {
function Activatable() {}
Activatable.prototype.activate = function () {this.isActive = true;};
Activatable.prototype.deactivate = function () {this.isActive = false;};
return Activatable;
})();
var SmartObject = (function () {
function SmartObject() {
var _this = this;
this.isDisposed = false; // Disposable
this.isActive = false; // Activatable
setInterval(function () {return console.log(_this.isActive + " : " + _this.isDisposed);}, 500);
}
SmartObject.prototype.interact = function () {this.activate();};
return SmartObject;
})();
applyMixins(SmartObject, [Disposable, Activatable]);
var smartObj = new SmartObject();
3) Extend
var extend = this.extend || function (childClass, parentClass) {
for (var key in parentClass) {
if (parentClass.hasOwnProperty(key)) {
childClass[key] = parentClass[key];
}
}
function F () {this.constructor = childClass;}
F.prototype = parentClass.prototype;
childClass.prototype = new F();
};
var ParentClass = (function () {
// Constructor
function ParentClass(message) {
// Constructor body
this.greeting = message;
this.x = 1;
this.y = 2;
this.z = 3;
}
// Public function
ParentClass.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return ParentClass;
})();
var ChildClass = (function (parentClass) {
// Extend
extend(ChildClass, parentClass);
// Constructor
function ChildClass(message) {
parentClass.apply(this, arguments); // или можно сделать частично parentClass.call(this, message);
this.greeting = message;
}
// Public variable
ChildClass.prototype.name = 'Parent';
// Public function
ChildClass.prototype.greet = function () {
return 'Hello, ' + this.greeting;
};
// Public function
ChildClass.prototype.hey = function () {
return 'Hey! ' + privateFunc();
};
// Private variable
var privateVar = 'Hello';
// Private function
function privateFunc () {
return privateVar + ' my friend!';
}
// Static variable
ChildClass.staticVar = 'I am static variable';
// Static function
function staticFunc () {
return 'I am static function';
}
return ChildClass;
})(ParentClass);
var SubClass = (function (parentClass) {
extend(SubClass, parentClass);
function SubClass(message) {
parentClass.apply(this, arguments);
this.greeting = message;
}
SubClass.prototype.greet = function () {
return 'Hello, ' + this.greeting;
};
SubClass.prototype.hey = function () {
return 'Hey!';
};
return SubClass;
})(ChildClass);
var parentObject = new ParentClass('world');
var childObject = new ChildClass();
var subObject = new SubClass();
console.log(parentObject.greet());
console.log(childObject.hey());
console.log(subObject.hey());
4) Module
var module = (function($){
var privateVar = 1
, publicVar = 2 + privateVar;
function Boat (name) {
this.name = name;
this.year = 2012;
}
Boat.prototype.getInfo = function () {
return this.name + ' ' + this.year;
};
function Plane (name) {
this.name = name;
this.year = 2012;
}
Plane.prototype.getInfo = function () {
return this.name + ' ' + this.year;
};
function insertHTML (tag, HTMLcode) {
$(tag).html(HTMLcode);
}
return {
publicVar: publicVar
, Boat: Boat
, Plane: Plane
, insertHTML: insertHTML
};
})(jQuery);
console.log(model.publicVar);
var myBoat = new model.Boat('Ann');
myBoat.year = 2010;
console.log(myBoat.getInfo());
model.insertHTML('div', '<h1>Hello!</h1>');
// Using CommonJS, AMD or browser globals to create a module
(function (root, factory) {
if (typeof exports === 'object') {factory(exports, require('b')); // CommonJS
} else if (typeof define === 'function' && define.amd) {define(['exports', 'b'], factory); // AMD. Register as an anonymous module.
} else {factory((root.commonJsStrict = {}), root.b); // Browser globals
}
}(this, function (exports, b) {
// use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
5) Namespace
var myNamespace = myNamespace || {};
myNamespace.someVariable = 1;
myNamespace.someFunction = function () {alert(myNamespace.someVariable);};
// Namespace injection
var myApp = myApp || {};
myApp.utils = {};
(function () {
var val = 5;
this.getValue = function () {return val;};
this.setValue = function(newVal) {val = newVal;}
// also introduce a new sub-namespace
this.tools = {};
}).apply(myApp.utils);
(function () {
this.diagnose = function(){return "diagnosis";}
}).apply(myApp.utils.tools);
console.log(myApp); // Outputs: 5
console.log(myApp.utils.getValue());
myApp.utils.setValue(25);
console.log(myApp.utils.getValue());
console.log(myApp.utils.tools.diagnose());
6) Singleton
var singleton = (function(){
var instance;
function init () {
var privateVariable = 'I am private variable.';
function privatMethod () {
console.log('I am private method.');
}
return {
publicProperty: 'I am public property.'
, publicMethod: function () {
console.log('I am public method.');
}
};
}
return {
getInstance: function () {
if (!instance) {
instance = init();
}
return instance;
}
}
})();
var mySingleton1 = singleton.getInstance()
, mySingleton2 = singleton.getInstance();
console.log(mySingleton1 === mySingleton2); // true
console.log(mySingleton1.publicProperty);
mySingleton1.publicMethod();
7) Observer
function Observer () {
this.events = [];
}
Observer.prototype.on = function (eventName, callbackFunction) {
return this.events.push({
eventName: eventName
, callbackFunction: callbackFunction
});
};
Observer.prototype.off = function (eventName) {
var eventsLength = this.events.length;
for (;eventsLength--;) {
if (this.events[eventsLength].eventName === eventName) {
this.events.splice(eventsLength, 1);
}
}
};
Observer.prototype.trigger = function (eventName) {
var eventsLength = this.events.length;
for (;eventsLength--;) {
if (this.events[eventsLength].eventName === eventName) {
this.events.callbackFunction();
}
}
}
Observer.prototype.count = function () {
return this.events.length;
};
8) Command
var carManager = {
requestInfo: function (model, id) {
return "The information for " + model + " with ID " + id + " is foobar";
}
, buyVehicle: function (model, id) {
return "You have successfully purchased Item " + id + ", a " + model;
}
, arrangeViewing: function (model, id) {
return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
}
};
var car = {
model: 'Ford'
, id: 52738
};
console.log(carManager.call(car));
// или
carManager.execute = function (name) {
return carManager[name] && carManager[name].apply(null, [].slice.call(arguments, 1));
};
console.log(carManager.execute('buyVehicle', 'Ford Escort', '34232'));
9) Facade
var addMyEvent = function (element, eventName, callbackFunction) {
if (element.addEventListener) {element.addEventListener(eventName, callbackFunction, false);
} else if (element.attachEvent) {element.attachEvent('on' + eventName, callbackFunction);
} else {element['on' + eventName] = callbackFunction;
}
};
addMyEvent(document.getElementById('myButton'), 'click', function(){alert('OK');});
10) Factory
function Car (options) {
this.doors = options.doors || 4;
this.state = options.state || 'brand new';
this.color = options.color || 'silver';
}
function Truck (options) {
this.state = options.state || 'used';
this.wheelSize = options.wheelSize || 'large';
this.color = options.color || 'blue';
}
function VehicleFactory() {}
VehicleFactory.prototype.vehicleClass = Car;
VehicleFactory.prototype.createVehicle = function (options) {
switch(options.vehicleType){
case 'car':this.vehicleClass = Car; break;
case 'truck':this.vehicleClass = Truck; break;
}
return new this.vehicleClass(options);
};
var carFactory = new VehicleFactory();
var car = carFactory.createVehicle( {
vehicleType: 'car'
, color: 'yellow',
, doors: 6
});
var movingTruck = carFactory.createVehicle( {
vehicleType: 'truck'
, state: 'like new'
, color: 'red'
, wheelSize: 'small'
});
11) Decorator
function door () {
return 'metal door';
}
function decorator (fn) {
return function(){
return 'black ' + fn();
}
}
door = decorator(door);
console.log(door());
12) Iterator
function forEachInArrayDo (array, func) {
for (var i = 0, len = array.length; i < len; i++) {
func(array[i]);
}
}
var arr = [1,2,3,4,5];
forEachInArrayDo(arr, function(element){console.log(element);});
13) Curry
function curry (func) {
return function (args) {
return func (args);
};
}
['11','11','11','11'].map(parseInt) // [11, NaN, 3, 4]
['11','11','11','11'].map(curry(parseInt)); // [11, 11, 11, 11]
function curry2(fun) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg);
};
};
}
function div(n, d) {return n / d;}
var div10 = curry2(div)(10);
div10(50); // 5
function curry3(fun) {
return function(last) {
return function(middle) {
return function(first) {
return fun(first, middle, last);
};
};
};
};
var songsPlayed = curry3(_.uniq)(false)(songToString);
songsPlayed(plays);
// [{artist: "Burial", track: "Archangel"},
// {artist: "Ben Frost", track: "Stomp"},
// {artist: "Emeralds", track: "Snores"}]
14) Proxy
$('button').on('click', function () {
setTimeout($.proxy(function () {
$(this).addClass('active'); // "this" теперь ссылается на внешний "this" функции "click"
}, this), 500); // <-- Этот "this" передается из функции "click" во внутреннюю функцию внутри "proxy"
});
// jQuery's implementation of jQuery.proxy() can be found below:
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function(fn, context) {
if (typeof context === "string") {
var tmp = fn[context];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if (!jQuery.isFunction( fn ) ) {return undefined;}
// Simulated bind
var args = slice.call( arguments, 2 ),
proxy = function() {
return fn.apply( context, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
return proxy;
}
15) jQuery plugin
/*!
* jQuery lightweight plugin boilerplate
* Original author: @ajpiano
* Further changes, comments: @addyosmani
* Licensed under the MIT license
*/
// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {
// undefined is used here as the undefined global
// variable in ECMAScript 3 and is mutable (i.e. it can
// be changed by someone else). undefined isn't really
// being passed in so we can ensure that its value is
// truly undefined. In ES5, undefined can no longer be
// modified.
// window and document are passed through as local
// variables rather than as globals, because this (slightly)
// quickens the resolution process and can be more
// efficiently minified (especially when both are
// regularly referenced in our plugin).
// Create the defaults once
var pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
// jQuery has an extend method that merges the
// contents of two or more objects, storing the
// result in the first object. The first object
// is generally empty because we don't want to alter
// the default options for future instances of the plugin
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype.init = function () {
// Place initialization logic here
// We already have access to the DOM element and
// the options via the instance, e.g. this.element
// and this.options
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if ( !$.data(this, "plugin_" + pluginName )) {
$.data( this, "plugin_" + pluginName,
new Plugin( this, options ));
}
});
}
})( jQuery, window, document );
$("#elem").defaultPluginName({
propertyName: "a custom value"
});
16) jQuery UI widget factory
/*!
* jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
* Author: @addyosmani
* Further changes: @peolanha
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
// define our widget under a namespace of your choice
// with additional parameters e.g.
// $.widget( "namespace.widgetname", (optional) - an
// existing widget prototype to inherit from, an object
// literal to become the widget's prototype );
$.widget( "namespace.widgetname" , {
//Options to be used as defaults
options: {
someValue: null
},
//Setup widget (e.g. element creation, apply theming
// , bind events etc.)
_create: function () {
// _create will automatically run the first time
// this widget is called. Put the initial widget
// setup code here, then we can access the element
// on which the widget was called via this.element.
// The options defined above can be accessed
// via this.options this.element.addStuff();
},
// Destroy an instantiated plugin and clean up
// modifications the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call( this );
// For UI 1.9, define _destroy instead and don't
// worry about
// calling the base widget
},
methodB: function ( event ) {
//_trigger dispatches callbacks the plugin user
// can subscribe to
// signature: _trigger( "callbackName" , [eventObject],
// [uiObject] )
// e.g. this._trigger( "hover", e /*where e.type ==
// "mouseenter"*/, { hovered: $(e.target)});
this._trigger( "methodA", event, {
key: value
});
},
methodA: function ( event ) {
this._trigger( "dataChanged", event, {
key: value
});
},
// Respond to any changes the user makes to the
// option method
_setOption: function ( key, value ) {
switch ( key ) {
case "someValue":
// this.options.someValue = doSomethingWith( value );
break;
default:
// this.options[ key ] = value;
break;
}
// For UI 1.8, _setOption must be manually invoked
// from the base widget
$.Widget.prototype._setOption.apply( this, arguments );
// For UI 1.9 the _super method can be used instead
// this._super( "_setOption", key, value );
}
});
})( jQuery, window, document );
var collection = $("#elem").widgetName({
foo: false
});
collection.widgetName("methodB");
Подписаться на:
Сообщения (Atom)