a closure gives you access to an outer function’s scope from an inner function
Combination of an inner fn and its outer LE forms a closure
a closure gives you access to an outer function’s scope from an inner function
Combination of an inner fn and its outer LE forms a closure
What if it could access the outer variables?
variables from new Function's outer LE.
JS prevents new Function from accessing those outer variables to prevent errors with minifiers.
function counter() { return counter.count++; };
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
function sayHi() { alert(phrase); var phrase = "Hello"; } sayHi();
let x= 1;
function func() {
alert(x); // error : cannot access 'x' before initialization
let x= 2;
}
func();
n the past there was only var, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called “immediately-invoked function expressions” (abbreviated as IIFE).
Reason for having IIFE
variable phrase exists.
alert( ) will not give error even if there is no declaration of phrase variable inside the fn but we are using it.
sayHi();
undefined
So in the example above, if (false) branch never executes, but that doesn’t matter. The var inside it is processed in the beginning of the function, so at the moment of (*) the variable exists.
Here if we had if(true) { var phrase; } then also we would get same result. All the below cases would give same result. So using var is pathetic.
function sayHi() { phrase= "Hello"; if(false) { var phrase; } alert(phrase); } sayHi(); // Hello
function sayHi() { phrase= "Hello"; if(true) { var phrase; } alert(phrase); } sayHi(); // Hello
function sayHi() { phrase= "Hello"; alert(phrase); } sayHi(); // Hello
Write function sum that works like this: sum(a)(b) = a+b.
function sum(a) {
return function (b) {
return a+b;
}
}
alert(sum(2)(3)); // 5, sum(a) returns a fn body, that fn can be named or anonymous and that returned fn gets the argument from the second parenthesis. Here we could have written, return function god(b){} and all other things same, no need to call by name
Look at the code. What will be the result of the call at the last line?
'use strict';
let phrase= "Hello";
if(true) {
let user= "John";
function sayHi() {
alert(`${phrase}, ${user}`);
}
}
sayHi();
// Hello, John, for older versions of JS
// error: sayHi is not defined, for newer versions to get the correct result we need to be in strict mode and as sayHi() fn is defined inside the if block so it is not a nested fn and outside of that if block, the sayHi() fn does not exist so we will get error
let count = 0;
Here we would get same result if we had used this.count= 0 instead of let count= 0 but this.count= 0 would have made count as property of the Counter fn object whereas let count= 0 makes count as local variable of the Counter fn.
all functions have the hidden property named [[Environment]], that keeps the reference to the Lexical Environment where the function was created:
All functions “on birth” receive a hidden property [[Environment]] with a reference to the Lexical Environment of their creation.
During the execution of makeCounter(), a tiny nested function is created.
It doesn’t matter whether the function is created using Function Declaration or Function Expression. All functions get the [[Environment]] property that references the Lexical Environment in which they were made. So our new tiny nested function gets it as well.
For our new nested function the value of [[Environment]] is the current Lexical Environment of makeCounter() (where it was born)
A variable is updated in the Lexical Environment where it lives.
Calling the makeCounter() fn returns a fn which gets assigned to counter variable as fn expression. Now during the first call of the counter(), count variable is found inside the makeCounter() fn as count= 0 and it is modified to count= 1 in the lexical environment inside makeCounter() fn because count variable lives there. But that modification of count variable does not affect the count inside the inner fn (inner fn gets count= 0 initial value) that's why although in the first call count becomes 1 inside makeCounter() fn but we get 0 in return from calling the counter() fn.
And if we call counter() for the second time then inner fn will search for count variable and will find it inside makeCounter() fn as count= 1 then inside that outer fn environment count becomes 2, but the inner fn gets the initial value only because count variable does not live inside the inner fn, so we get 1 in return from calling counter() fn for the second time. And this increases by 1 for calling counter() fn multiple times.
function makeCounter() { let count = 0; return function() { return count++; }; }
// function makeCounter() { // let count= 0; // return function() { // return count++; // }; // }
// let counter= makeCounter() ; // the inner fn gets assigned to counter variable
// count value never gets reassigned to 0 as count= 0 and count++ exists in different fns. We would get all 0s if the inner fn also contained : count= 0 // alert(counter()); // 0 // alert(counter()); // 1 // alert(counter()); // 2
/* function makeCounter() { let count= 0; return count++; }
// with each call count variable gets reassigned to 0 as count= 0 and count++ exists in same fn alert(makeCounter()); // 0<br> alert(makeCounter()); // 0 alert(makeCounter()); // 0 */
There’s an easy way to distinguish between them: When ... is at the end of function parameters, it’s “rest parameters” and gathers the rest of the list of arguments into an array. When ... occurs in a function call or alike, it’s called a “spread syntax” and expands an array into a list.
Except at the end of fn parameters in all other places ... denotes spread operator
The MIME type for JSON text is "application/json"
MIME stands for Multi-purpose Internet Mail Extensions. MIME types form a standard way of classifying file types on the Internet. Internet programs such as Web servers and browsers all have a list of MIME types, so that they can transfer files of the same type in the same way, no matter what operating system they are working in.
A MIME type has two parts: a type and a subtype. They are separated by a slash (/). For example, the MIME type for Microsoft Word files is application and the subtype is msword. Together, the complete MIME type is application/msword.
Although there is a complete list of MIME types, it does not list the extensions associated with the files, nor a description of the file type. This means that if you want to find the MIME type for a certain kind of file, it can be difficult. Sometimes you have to look through the list and make a guess as to the MIME type of the file you are concerned with.
A MIME type is a label used to identify a type of data. It is used so software can know how to handle the data. It serves the same purpose on the Internet that file extensions do on Microsoft Windows.
So if a server says "This is text/html" the client can go "Ah, this is an HTML document, I can render that internally", while if the server says "This is application/pdf" the client can go "Ah, I need to launch the FoxIt PDF Reader plugin that the user has installed and that has registered itself as the application/pdf handler."
You'll most commonly find them in the headers of HTTP messages (to describe the content that an HTTP server is responding with or the formatting of the data that is being POSTed in a request) and in email headers (to describe the message format and attachments).
JSON-encoded object has several important differences from the object literal:
no new keyword is allowed inside a JSON encoded object
or instance, here we JSON.stringify a student:
let student= {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json= JSON.stringify(student);
alert(typeof json); // string
alert(json); // {"name":"John","age":30,"isAdmin":false,"courses":["html","css","js"],"wife":null}
alert(student); // [object Object]
Create a function getDateAgo(date, days) to return the day of month days ago from the date.
let date= new Date(2015, 0, 2);
function getDateAgo(date, days) {
return new Date(date - days 24 3600 * 1000).getDate();
}
alert(getDateAgo(date, 2)); // 31, 31st Dec 2014
alert(date); // Fri Jan 02 2015 00:00:00 GMT+0530 (India Standard Time)
To clone a date, take the date in a variable named say date, then
let dateCopy= new Date(date) // dateCopy will contain the whole string of the date, for example,
let dateCopy= new Date(2015, 0, 2) alert(dateCopy); // Fri Jan 02 2015 00:00:00 GMT+0530 (India Standard Time)
Now from this date string stored in dateCopy, we can get just the date number by, dateCopy.getDate(). For example,
alert(dateCopy.getDate( )); // 2
We also can change or set any of the content of the dateCopy string, for example if we want to set the date to 5 then we can write,
dateCopy.setDate(5) alert(dateCopy); // Mon Jan 05 2015 00:00:00 GMT+0530 (India Standard Time)
"2017-01-26")
mmddyy
yymmdd (preferred as it is decreasing order: year > month > date)
Create the function topSalary(salaries) that returns the name of the top-paid person.
let salaries= { 'John': 100, 'Pete': 300, 'Mary': 250 }
function topSalary(salObj) { let maxSal= 0; let maxName= null;
for(const [name, salary] of Object.entries(salObj)) {
if(salary > maxSal) {
maxSal= salary;
maxName= name;
}
}
return maxName;
}
alert(topSalary(salaries));
. . .
My solution :
function topSalary(salObj) {
if(Object.entries(salObj).length=== 0) return null;
let nameArr= [];
let salArr= [];
for(let [name, salary] of Object.entries(salObj)) {
nameArr.push(name);
salArr.push(salary);
}
let topSal= Math.max(...salArr);
for(let key in salObj) {
if(salObj[key] === topSal) {
return key;
}
}
}
alert(topSalary(salaries));
alert(arr); // 1,2,3,4,5 (array toString conversion works)
let range= { 0: 28, 1: 44, 3: 55, duni: 'uny', length: 6 }
let arr= Array.from(range);
alert(arr); // [28, 44] alert(arr); // [28, 44, , 55] for length: 4 alert(arr); // [28, 44, , 55, , ] for length: 6
The result of obj[Symbol.iterator] is called an iterator. It handles the further iteration process.
The return value of objSymbol.iterator is called the iterator object which handles the iteration process instead of the original object obj.
// assuming that range is taken from the example above
everything except the for...of part. We will get 1, 2, 3, 4, 5 as value of arr.
Now range[Symbol.iterator]() returns the range object itself: it has the necessary next() method and remembers the current iteration progress in this.current. Shorter? Yes. And sometimes that’s fine too.
for...of calls Symbol.iterator( ) method of range object. This Symbol.iterator( ) method returns an object which contains next( ) method. Instead of returning any object we can set the Symbol.iterator( ) to return the range object itself and that's why we also need to set range object so that it also contains next( ) method inside it. Then there will be no difference from previous.
let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // get name by symbol alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id
// getting or creating symbol by key name Symbol.for(key name in string) >> for(key)
// getting key name by the symbol variable Symbol.keyFor(symbol name variable) >> keyFor(symbol)
for(key) &&& keyFor(symbol)
arr.sort((a, b) => a.age > b.age ? 1 : -1);
array.sort((a, b) => a.age - b.age );
.. your code to sort it in decreasing order
negative returned value means decreasing order, 2 - 5 = -3 so decreasing order maintained, do not interchange position of 5 and 2
Write the function camelize(str) that changes dash-separated words like “my-short-string” into camel-cased “myShortString”.
function camelize(str) {
let arr= str.split('-');
for(let i= 1; i < arr.length; i++) {
let firstLtr= arr[i][0].toUpperCase();
let newOne= firstLtr + arr[i].slice(1);
arr.splice(i, 1, newOne);
}
alert(arr.join(''));
}
camelize('chi-huan-ti'); // chiHuanTi
A call to users.filter(army.canJoin, army) can be replaced with users.filter(user => army.canJoin(user)), that does the same. T
let army= {
minAge: 18,
maxAge: 27,
canJoin(useri) {
return useri.age >= this.minAge && useri.age < this.maxAge;
}
};
let users= [{age: 16}, {age: 20}, {age: 23}, {age: 30}];
let soldiers= users.filter(function(user) {return army.canJoin(user)});
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
It splits the string into an array by the given delimiter delim.
split(delim) method returns an array from splitting a string where delim occurs.
[1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); });
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
alert(${a} <> ${b}); // -2 <> 1 15 <> -2 2 <> 15 0 <> 2 8 <> 0<br>
})
It calls the function for each element of the array and returns the array of results.
find(), filter(), map() - all of these methods takes a fn as argument, that fn has 3 parameters : item, index, array. These 3 methods run this fn over all of the elements of the array.
find() searches for single matching element,
filter() searches for multiple matching elements, and
map() transforms the elements of the array with the help of the argument fn.
arr.splice(-1, 0, 3, 4);
the first argument of splice( ) indicates the index whose value needs to be replaced by other values
Write the function sumInput() that:
function sumInput() {
let arr= [];
let sum= 0;
while(true) {
let input= prompt('Give the array a numeric value', '');
if(!isFinite(input) || input=== null || input=== '') {
return sum;
}
arr.push((input));
sum+= parseInt(input);
}
return sum;
}
alert(sumInput());
Create an array styles with items “Jazz” and “Blues”. Append “Rock-n-Roll” to the end. Replace the value in the middle by “Classics”. Your code for finding the middle value should work for any arrays with odd length. Strip off the first value of the array and show it. Prepend Rap and Reggae to the array.
let styles= ['Jazz', 'Blues'];
styles.push('Rock-n-Roll');
let len= styles.length;
if(len % 2 !== 0) { let mid= Math.floor(len / 2); styles[mid]= 'Classics';<br> }
alert(styles.shift()); // 'Jazz'
styles.unshift('Rap', 'Reggae');
console.log(styles); // ['Rap', 'Reggae', 'Classics', 'Rock-n-Roll']
The for..of doesn’t give access to the number of the current element, just its value, but in most cases that’s enough. And it’s shorter. Technically, because arrays are objects, it is also possible to use for..in:
let fruits= ['Apple', 'Orange', 'Peach'];
for(let fruit of fruits) { alert(fruit); // Apple Orange Peach }
// We know for...of is used for arrays (arr + of= arif) and for...in is used for objects. When we use for...of loop for arrays, the iterator variable represents an array element, not indexes. And as for...in is used for objects that's why the iterator variable represents key. But if we use for...in for arrays also then the iterator variable will represent the indexes which are similar to the keys as in objects.
for(let fruit in fruits) { alert(fruits[fruit]); // Apple Orange Peach }
alert( 'Österreich'.localeCompare('Zealand') ); // -1
After this write an exercise for reversing a string,
function revStr(str) { return str.split('').reverse().join(''); }
alert(revStr('I am a gentleman')); // nameltneg a ma I
Create a function truncate(str, maxlength) that checks the length of the str and, if it exceeds maxlength – replaces the end of str with the ellipsis character "…", to make its length equal to maxlength.
consider '...' as a character, after adding ... at the end of truncated string, we must have length of whole string including '...' as the maxlength.
str.repeat(n) – repeats the string n times.
alert('Australia '.repeat(7));
each character has a corresponding numeric code
This numeric code for each character is called codePoint
slice(start, end) from start to end (not including end) allows negatives substring(start, end) between start and end negative values mean 0 substr(start, length) from start get length characters allows negative start
substring( ) also from start to end (not including end). Differences between slice( ) and substring( ) are : substring( ) cannot take negative values as start and end index, for substring( ) negative index means 0, but slice( ) can take negative arguments.
And if we swap start and end indexes for substring( ) then also it will return the same result, i.e. start index can be larger than end index, but that's not true for slice( ).
let str = "Widget"; if (~str.indexOf("Widget")) { alert( 'Found it!' ); // works }
let str= 'Widget with id'; if(~str.indexOf('Widget')) { alert('We found it'); // We found it, so both ! or ~ are valid before indexOf() }
let str = "Widget with id"; if (str.indexOf("Widget")) { alert("We found it"); // doesn't work! }
let str= 'Widget with id'; if(!str.indexOf('Widget')) { alert('We found it'); // We found it }
let str = 'Hi'; str[0] = 'h'; // error alert( str[0] ); // doesn't work
// Changing 'Hi' to 'hi'
// Reassigning a character in string literal is not possible let str= 'Hi'; str[0]= 'h'; // Not possible alert(str[0]); // H, same as before alert(str); // Hi, same as before
// Other way by reassigning the variable holding that string let str= 'Hi'; str= 'h' + str[1]; // Reassigning the variable alert(str); // hi, Changed !
6.35.toFixed(1) == 6.3?
alert(6.35.toFixed(1)); // 6.3, wrong one
alert(Math.round(6.35 * 10) / 10); // 6.4, correct one
There are more functions and constants in Math object, including trigonometry, which you can find in the docs for the Math object.
Math.sqrt(), trunc(), round(), floor(), ceil(), abs(), see mdn
alert( parseInt('100px') ); // 100
alert(parseInt('100px4')); // 100
Values 0 and -0 are different: Object.is(0, -0) === false,
alert(Object.is(+0, -0)); // false, because sign bits are different
works with NaN: Object.is(NaN, NaN) === true, that’s a good thing.
alert(Object.is(NaN, NaN)); // true
alert(NaN == NaN); // false
Please note that an empty or a space-only string is treated as 0 in all numeric functions including isFinite.
alert(isFinite('')); // true, because '' or ' ' represents 0
alert( isNaN("str") ); // true
alert(isNaN('2')); // false
alert(isNaN('two')); // true
y consequence of the internal representation of numbers is the existence of two zeroes: 0 and -0.
alert(+0 == -0); // true
alert( 9999999999999999 ); // shows 10000000000000000
alert(9999999999999999 == 10000000000000000); // true
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
alert(0.28 + 0.14); // 0.42000000000000004
alert((0.28 x 100 + 0.14 x 100)/100); // 0.4200000000000001, so error reduces
let sum = 0.1 + 0.2; alert( sum.toFixed(2) ); // 0.30 Please
let sum= 0.1 + 0.2 ; alert(sum.toFixed(5) == 0.3); // true
There’s just no way to store exactly 0.1 or exactly 0.2 using the binary system, just like there is no way to store one-third as a decimal fraction.
convert decimal 0.2 into binary then we can see endless repetition of binary 1s and 0s .
0 .1 . 2 0 2 2 0 4 2 0 8 2 1 6 2 1 2 2 0 4 2 0 8 2 1 6 2 1 2 2 0 4 2 0 8 2
If we count the zeroes in 0.000001, there are 6 of them. So naturally it’s 1e-6.
For 1e-6 put 6 zeroes before the number 1, and on the right side of the last zero give the decimal point.
hi there