└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Functions and Scope (Closure) in JavaScript 2 | 3 | 4 | ## Table of Contents 5 | 6 | * [First Class Functions](#first-class-functions) 7 | * [Scoping](#scoping) 8 | * [Invocation Context (`this`)](#invocation-context-this) 9 | * [Indirect Invocation](#indirect-invocation) 10 | * [`this` and Nested Function Issue](#this-and-nested-function-issues) 11 | * [Arguments](#arguments) 12 | 13 | 14 | ## First Class Functions 15 | 16 | ### Assign a Function to a Variable 17 | 18 | This function returns the circumference of a circle: 19 | 20 | ```javascript 21 | var circumference = function (circle) { 22 | return 2 * Math.PI * circle.radius; 23 | }; 24 | //define circle object 25 | var circle = {x: 100, y: 100, radius: 50}; 26 | 27 | //invoke the function 28 | console.log(circumference(circle)); //314.159 29 | ``` 30 | 31 | 32 | ### Passing a Function as an Argument to Another Function 33 | 34 | This example is a JSON replacer function which filters out property `radius` from object serialization process: 35 | 36 | ```javascript 37 | var filter = function (key, value) { 38 | //don't serialize property 'radius' 39 | if (key === 'radius') { 40 | return undefined; 41 | } else { 42 | return value; 43 | } 44 | }; 45 | 46 | //define circle object 47 | var circle = {x: 100, y: 200, radius: 50}; 48 | 49 | //get string version of circle object 50 | var info = JSON.stringify(circle, filter); 51 | 52 | //print the string equivalent of object 53 | console.log(info); // '{'x': 100, 'y': 200}' 54 | ``` 55 | 56 | ### Returning a Function as a Value From Another Function 57 | 58 | In this example an outer function is defined that returns inner function: 59 | 60 | ```javascript 61 | function outer(x) { 62 | //this secret is a closure 63 | var secret = 5; 64 | return function () { 65 | console.log(secret + x); 66 | } 67 | } 68 | 69 | //get the inner function 70 | var inner = outer(10); 71 | 72 | //invoke inner function 73 | inner(); //15 74 | ``` 75 | 76 | 77 | ## Scoping 78 | 79 | ### Variable Declaration Hoisting Rule 80 | 81 | This function tries to check the presence of variable `a` before and after its declaration: 82 | 83 | ```javascript 84 | function scopeTest() { 85 | console.log(a); // undefined - this means variable a is hoisted at this point. No ReferenceError 86 | var a = 1; 87 | console.log(a); //1 88 | } 89 | 90 | scopeTest(); 91 | ``` 92 | 93 | 94 | ### Function Declaration Statement Hoisting Rule 95 | 96 | This function illustrates the hoisting of function statement: 97 | 98 | ```javascript 99 | function outer() { 100 | //function inner is in scope here 101 | //it could be invoked at this place 102 | inner(); 103 | 104 | console.log(typeof inner === 'function'); 105 | //true 106 | 107 | function inner() { 108 | console.log('I can be invoked anywhere inside outer function'); 109 | } 110 | 111 | //function inner is in scope here 112 | //it could be invoked at this place 113 | inner(); 114 | console.log(typeof inner === 'function'); 115 | //true 116 | } 117 | 118 | outer(); 119 | ``` 120 | 121 | 122 | ### Function Definition Expression Hoisting Rule 123 | 124 | This function illustrates the hoisting of function definition expression. 125 | 126 | ```javascript 127 | function outer() { 128 | //function inner is not in scope here 129 | //it can not be invoked at this place 130 | inner(); //typeError 131 | 132 | console.log(typeof inner === 'function'); 133 | //false 134 | 135 | var inner = function () { 136 | console.log('I\'m not hoisted at the top'); 137 | }; 138 | 139 | //function inner is in scope here 140 | // it could be invoked at this place 141 | inner(); 142 | console.log(typeof inner === 'function'); 143 | //true 144 | } 145 | 146 | outer(); 147 | ``` 148 | 149 | ## Invocation Context (`this`) 150 | 151 | ### Function Invocation 152 | 153 | This function returns the word boundaries of a string: 154 | 155 | ```javascript 156 | //\w to \W or \W to \w transition is a word boundary 157 | function wordBoundaries(subject) { 158 | //regex for word boundary position 159 | var pattern = /\b/g; 160 | 161 | //invoke match method defined on the string 162 | return subject.match(pattern).length; 163 | } 164 | 165 | var book = 'JavaScript: The Good Parts'; 166 | console.log(wordBoundaries(book)); //8 167 | ``` 168 | 169 | 170 | ### Method Invocation 171 | 172 | ```javascript 173 | //define cylinder object 174 | var cylinder = { 175 | radius: 10, 176 | height: 20 177 | }; 178 | 179 | /* 180 | define function on cylinder object 181 | volume is the property of cylinder object 182 | this inside function is the cylinder object 183 | this.radius means radius property of the cylinder object 184 | this = invocation context = cylinder object 185 | */ 186 | cylinder.volume = function () { 187 | return Math.PI * Math.pow(this.radius, 2) * this.height; 188 | }; 189 | 190 | //invoke the method on the cylinder object 191 | console.log(cylinder.volume()); 192 | ``` 193 | 194 | 195 | ### Constructor Invocation 196 | 197 | This is a constructor function, and in this example it accepts 2 arguments and sets the newly created object's properties: 198 | 199 | ```javascript 200 | //`this` or invocation context is the newly created cylinder object 201 | function Cylinder(radius, height) { 202 | this.radius = radius; //object property 203 | this.height = height; //object property 204 | //object method 205 | this.volume = function () { 206 | return Math.PI * Math.pow(this.radius, 2) * this.height; 207 | }; 208 | } 209 | 210 | //create object using constructor function 211 | //this inside constructor = cylinder1 212 | var cylinder1 = new Cylinder(10, 20); 213 | console.log(cylinder1.volume()); 214 | 215 | //create another object 216 | //this inside constructor = cylinder2 217 | var cylinder2 = new Cylinder(20, 10); 218 | console.log(cylinder2.volume()); 219 | ``` 220 | 221 | ### Using Prototype Object 222 | 223 | ```javascript 224 | //Accepts 2 arguments and set the newly created object's properties 225 | //this or invocation context is the newly created cylinder object 226 | function Cylinder(radius, height) { 227 | this.radius = radius; //object property 228 | this.height = height; //object property 229 | } 230 | ``` 231 | 232 | Now volume method is defined on the prototype object. 233 | 234 | ```javascript 235 | //prototype object is the property defined on Cylinder constructor 236 | Cylinder.prototype.volume = function () { 237 | return Math.PI * Math.pow(this.radius, 2) * this.height; 238 | }; 239 | 240 | //create object using constructor function 241 | //this inside constructor = cylinder1 242 | var cylinder1 = new Cylinder(10, 20); 243 | console.log(cylinder1.volume()); 244 | 245 | //create another object 246 | //this inside constructor = cylinder2 247 | var cylinder2 = new Cylinder(20, 10); 248 | console.log(cylinder2.volume()); 249 | ``` 250 | 251 | 252 | ## Indirect Invocation 253 | 254 | ### Call Method 255 | 256 | This is a function that returns the circumference of a circle. 257 | 258 | ```javascript 259 | //`this` keyword is not associated with any object 260 | var circumference = function () { 261 | return 2 * Math.PI * this.radius; 262 | }; 263 | //define circle objects 264 | var circle1 = {x: 100, y: 200, radius: 50}; 265 | var circle2 = {x: 200, y: 300, radius: 25}; 266 | 267 | //invoke the function 268 | //this = circle1 269 | console.log(circumference.call(circle1)); //314.159 270 | //this = circle2 271 | console.log(circumference.call(circle2)); //157.079 272 | ``` 273 | 274 | 275 | ### Apply Method 276 | 277 | This function makes all arguments non enumerable. 278 | 279 | ```javascript 280 | var makeNonEnumerable = function () { 281 | //iterate over all arguments and change the enumerable attribute false 282 | for (var i = 0; i < arguments.length; i++){ 283 | Object.defineProperty(this,arguments[i],{enumerable:false}); 284 | } 285 | }; 286 | 287 | var testObject1 = {x:1,y:2,z:3}; 288 | 289 | //make x and y property non enumerable 290 | //We pass individual argument instead of array 291 | makeNonEnumerable.call(testObject1, 'x', 'y"); 292 | //check the enumerable attribute by console.log 293 | Object.getOwnPropertyDescriptor(testObject1, 'x').enumerable; //false 294 | Object.getOwnPropertyDescriptor(testObject1, 'y').enumerable; //false 295 | Object.getOwnPropertyDescriptor(testObject1, 'z').enumerable; //true 296 | 297 | var testObject2 = {p:1, q:2, r:3}; 298 | //We pass array instead of individual argument 299 | makeNonEnumerable.apply(testObject2,['p', 'q']); 300 | Object.getOwnPropertyDescriptor(testObject2, 'p').enumerable; //false 301 | Object.getOwnPropertyDescriptor(testObject2, 'q').enumerable; //false 302 | Object.getOwnPropertyDescriptor(testObject2, 'r').enumerable; //true 303 | 304 | var property; 305 | for(property in testObject1){ 306 | console.log(property); 307 | } 308 | ``` 309 | 310 | 311 | ### Call Method: Search Binary Numbers 312 | 313 | This function finds all binary numbers inside a string. 314 | 315 | ```javascript 316 | /* 317 | regex pattern checks digit (0 or 1) one or more times between word boundaries 318 | \b -> word boundary 319 | + -> repeat 1 or more time - you can make it lazy by +? 320 | [01]+ -> repeat 0 or 1 one or more time 321 | g -> global match 322 | match method -> return an array with all matches 323 | */ 324 | 325 | function binaryNumbers() { 326 | var pattern = /\b[01]+\b/g; 327 | //this keyword is not associated with any object 328 | this.result = this.subject.match(pattern); 329 | } 330 | 331 | //create 2 objects 332 | var object1 = {subject: '100 1234 1010 string'}, 333 | object2 = {subject: '1234 1112 1010 string'}; 334 | 335 | //associate this with object1 336 | //this.result will set result property on object1 337 | binaryNumbers.call(object1); 338 | 339 | //associate this with object2 340 | //this.result will set result property on object2 341 | binaryNumbers.call(object2); 342 | 343 | //query result property on object1 344 | console.log(object1.result); //[ '100', '1010' ] 345 | 346 | //query result property on object2 347 | console.log(object2.result); // [ '1010' ] 348 | ``` 349 | 350 | ### Call Method: Internals 351 | 352 | This function finds all binary numbers inside a string. 353 | 354 | ```javascript 355 | /* 356 | regex pattern checks digit (0 or 1) one or more times between word boundaries 357 | \b -> word boundary 358 | + -> repeat 1 or more time - you can make it lazy by +? 359 | [01]+ -> repeat 0 or 1 one or more time 360 | g -> global match 361 | match method -> return an array with all matches 362 | */ 363 | 364 | function binaryNumbers() { 365 | var pattern = /\b[01]+\b/g; 366 | //this keyword is not associated with any object 367 | this.result = this.subject.match(pattern); 368 | } 369 | 370 | //create 2 objects 371 | var object1 = {subject: '100 1234 1010 string'}, 372 | object2 = {subject: '1234 1112 1010 string'}; 373 | 374 | //associate this with object1 375 | //this.result will set result property on object1 376 | object1.method = binaryNumbers; 377 | object1.method(); 378 | delete object1.method; 379 | 380 | 381 | //associate this with object2 382 | //this.result will set result property on object2 383 | object2.method = binaryNumbers; 384 | object2.method(); 385 | delete object1.method; 386 | 387 | //query result property on object1 388 | console.log(object1.result); //[ '100', '1010' ] 389 | 390 | //query result property on object2 391 | console.log(object2.result); // [ '1010' ] 392 | ``` 393 | 394 | 395 | ## `'this` and Nested Function Issues 396 | 397 | ### Basic Reducer Function 398 | 399 | In this example the reducer object has one array and a method reduced: 400 | 401 | ```javascript 402 | //Below is the calculation using reduce method and array [100, 200, 300] 403 | //x = 100, y = 200 404 | //0.5 * (100 + 200) = 150 -> this will become x in next iteration 405 | //x = 150, y = 300 406 | //0.5 * (150 + 300) = 225 -> final value 407 | var reducer = { 408 | a: [100, 200, 300], 409 | reduce: function () { 410 | return this.a.reduce(function (x, y) { 411 | return 0.5 * (x + y); 412 | }); 413 | } 414 | }; 415 | console.log(reducer.reduce()); //225 416 | ``` 417 | 418 | ### Simulate Problem: Reducer Factor and `this` 419 | 420 | ```javascript 421 | 422 | //reducer object has one array a and method reduce 423 | //reduce does the job of reducing using reduce method 0.5 * (x + y) 424 | //Below is the calculation using reduce method and array [100, 200, 300] 425 | //x = 100, y = 200 426 | //0.5 * (100 + 200) = 150 -> this will become x in next iteration 427 | //x = 150, y = 300 428 | //0.5 * (150 + 300) = 225 -> expected value -> but we get NaN 429 | var reducer = { 430 | a: [100, 200, 300], 431 | factor: 0.5, 432 | reduce: function () { 433 | return this.a.reduce(function (x, y) { 434 | return this.factor * (x + y); 435 | }); 436 | } 437 | }; 438 | console.log(reducer.reduce()); //NaN 439 | ``` 440 | 441 | 442 | ### Using `this` Keyword Inside a Nested Function 443 | 444 | ```javascript 445 | //reducer object has one array a and method reduce 446 | //reduce does the job of reducing using reduce method 0.5 * (x + y) 447 | //Below is the calculation using reduce method and array [100, 200, 300] 448 | //x = 100, y = 200 449 | //0.5 * (100 + 200) = 150 -> this will become x in next iteration 450 | //x = 150, y = 300 451 | //0.5 * (150 + 300) = 225 452 | var reducer = { 453 | a: [100, 200, 300], 454 | factor: 0.5, 455 | reduce: function () { 456 | var self = this; 457 | return this.a.reduce(function (x, y) { 458 | return self.factor * (x + y); 459 | }); 460 | } 461 | }; 462 | console.log(reducer.reduce()); //225 463 | ``` 464 | 465 | 466 | ## Arguments 467 | 468 | ### Basics 469 | 470 | This function tries to explain the flexibility of arguments supplied: 471 | 472 | ```javascript 473 | function test(x, y) { 474 | // I don't do anything 475 | console.log(x); 476 | console.log(y); 477 | //print arguments object - Array like object 478 | console.log(arguments); 479 | } 480 | 481 | //no argument supplied 482 | test(); 483 | // x - undefined 484 | // y - undefined 485 | // { } 486 | 487 | //less arguments supplied 488 | test(1); 489 | // x - 1 490 | // y - undefined 491 | // { '0': 1} 492 | 493 | //arguments = parameters = 2 494 | test(1, 2); 495 | // x - 1 496 | // y - 2 497 | // {'0': 1, '1': 2 } 498 | 499 | //more argument supplied than actual parameters 500 | test(1, 2, 3); 501 | // x - 1 502 | // y - 2 503 | // {'0': 1, '1': 2, '2': 3} 504 | ``` 505 | 506 | ### Objects 507 | 508 | This function adds all the arguments supplied: 509 | 510 | ```javascript 511 | function add() { 512 | console.log(arguments.length); //3 513 | var sum = 0; 514 | //iterate over all arguments 515 | //trick - save arguments.length in some variable 516 | for (var i=0; i < arguments.length; i++){ 517 | sum +=arguments[i]; 518 | } 519 | return sum; 520 | } 521 | 522 | console.log(add(1,2,3)); //6 523 | ``` 524 | 525 | ### Default Parameters: Keys and `getOwnPropertyNames` 526 | 527 | This function returns object properties based on the flag onlyEnumerable: 528 | 529 | ```javascript 530 | /* 531 | getProperties(obj) -> return enumerable own properties 532 | getProperties(obj, false) -> return enumerable as well as non enumerable properties 533 | getProperties(obj, true) -> return enumerable own properties 534 | */ 535 | 536 | function getProperties(obj, onlyEnumerable) { 537 | //if onlyEnumerable is not passed, set it to true 538 | if (onlyEnumerable === undefined) { 539 | onlyEnumerable = true; 540 | } 541 | 542 | if (onlyEnumerable) { 543 | return Object.keys(obj); 544 | } else { 545 | // enumerable + non enumerable 546 | return Object.getOwnPropertyNames(obj); 547 | } 548 | } 549 | 550 | //define object with 2 properties 551 | //by default newly created properties are enumerable 552 | var obj = {x: 1, y: 2}; 553 | 554 | //define one non enumerable property "z" 555 | Object.defineProperty(obj, 'z', {enumerable: false, value: 3}); 556 | 557 | console.log(getProperties(obj)); // [ 'x', 'y' ] 558 | console.log(getProperties(obj, false)); // [ 'x', 'y', 'z' ] 559 | console.log(getProperties(obj, true)); // [ 'x', 'y' ] 560 | ``` --------------------------------------------------------------------------------