Skip to content

Instantly share code, notes, and snippets.

@concise
Last active May 15, 2018 11:39
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save concise/a20b3e5d7e9c61dfbcc5 to your computer and use it in GitHub Desktop.
Save concise/a20b3e5d7e9c61dfbcc5 to your computer and use it in GitHub Desktop.
A quiz about JavaScript (js-quiz.txt) and some code snippets (your-cheat-sheet.js)
//
// .d88888b. d8b
// d88P" "Y88b Y8P
// 888 888
// 888 888 888 888 888 88888888
// 888 888 888 888 888 d88P
// 888 Y8b 888 888 888 888 d88P
// Y88b.Y8b88P Y88b 888 888 d88P
// "Y888888" "Y88888 888 88888888
// Y8b
//
//
// The slides for beginners at JavaScript:
// https://slides.com/concise/js/fullscreen
1 [10%] Equality of numbers
The following expression yields the boolean value true:
(1 + 2 === 3)
while the following expression yields false:
(0.1 + 0.2 === 0.3)
Would you explain why?
2 [10%] Values, references, and variables
After the following code execution:
a = 3; b = a; b = 4;
the value of a is still 3, unaffected by b. But in another case, after
the following code execution:
a.x = 3; b = a; b.x = 4;
the value of a.x becomes 4. Would you explain why?
3 [10%] Passing arguments to JavaScript functions
There is a standard bulit-in ECMAScript function called `parseInt` in
the global object which can be used to transform a string into a
number. For example, the following expression gives you 31:
parseInt('31')
And you can even parse integers with different radixes:
parseInt('A', 16); // 10
parseInt('11', 8); // 9
Now, given an array and a simple wrapper function in the global scope:
var arr = ['3', '4', '5', '6'];
var myParseInt = function (n) {
return parseInt(n);
};
The result of the following function call is [3, 4, 5, 6]
arr.map(myParseInt)
But the result of the following function call is [3, NaN, NaN, NaN]
arr.map(parseInt)
Based on what you see above, would you point out if there is any
possible cause that leads to the different results?
4 [10%] Variable lifetime
In JavaScript, when a function is invoked, it will not always work by
just storing all the local variables (those "references" to values) on
the "call stack" only.
Why?
Note: Stuff on top of the stack is destroyed once a function returns.
5 [10%] Lexical scopes and closures
Most of the real-world use cases of JavaScript are embedding a
single-threaded JavaScript runtime into an existing host application
with an "event loop" as well as a "job queue" to provide asynchronous
workflow.
For example, most JavaScript runtimes implement a function called
`setTimeout` that accepts two arguments. It can be invoked in the
following form:
setTimeout(callbackfunc, millisec);
The first argument is expected to be a function, and the second
argument should be a numeric value. When invoked, the `setTimeout()`
function will return immediately after registering a timeout event:
"`callbackfunc` should be invoked after `millisec` from now"
The timer/event mechanism is provided by the host application, not by
the JavaScript runtime. Once `millisec` time passes, the
`callbackfunc` will be pushed into the "job queue" we mentioned
earilier.
When the JavaScript execution stack is cleared out, or, when there is
nothing to do, the event loop will check whether there is any pending
task inside the job queue. If there is one, that task (a callback
function) will be pushed onto the execution stack, and JavaScript
runtime starts working again.
Now let's say that we want to print out the numbers 1, 2, 3, 4, and 5
accordingly after 1, 2, 3, 4, and 5 seconds. There was a careless
programmer who wrote:
for (var i = 1; i <= 5; i += 1) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
and found that the result was not what we expected:
6 (printed after 1 second)
6 (printed after 2 second)
6 (printed after 3 second)
6 (printed after 4 second)
6 (printed after 5 second)
(1) Explain the reason behind the above result.
(2) How would you fix the bug while still using a for-loop?
(3) Rewrite a working solution with Array.prototype.forEach but without
any for-loop or while-loop.
6 [10%] Prototype, inheritance, and this binding for method calls
One way to convert a string into an array of characters (where each
character is represented by a string of length 1) is to invoke the
`Array.prototype.slice` method on a String object:
String.prototype.sliceAsAnArray = Array.prototype.slice;
Now the `slice` method for arrays is inherited/shared/accessible by all
String objects because we attach it onto their prototype:
( 'hello' ).sliceAsAnArray(); // [ 'h', 'e', 'l', 'l', 'o' ]
( 'world' ).sliceAsAnArray(); // [ 'w', 'o', 'r', 'l', 'd' ]
In the above program, `sliceAsAnArray` (Array.prototype.slice) is
invoked when `this` is bound to an anonymous String object. Since
`Array.prototype.slice` is "generic", it can work on any object "like
an array" by returning a new instance of Array with elements:
this[0], this[1], this[2], ..., this[this.length - 1]
In fact, we can do the conversion by stealing `slice()` from arrays
without attaching a new method on to String.prototype. How?
Hint: Recall the usage of `call` and `apply` methods for functions.
Check the cheat sheet (your-cheat-sheet.js) below if you haven't
seen them before.
7 [20%] Let's do OOP without the `new` operator
Please implement a constructor-like function that can create a new
object each time it is invoked. It should work without the "new"
operator.
Don't forget to provide prototypal inheritance so that all the common
properties across all instances of the same "factory type" can be
shared from a parent object, and we can also have the possibility of
adding (or live-patching) new properties to all the created objects
later.
A function that provides these features is called a "factory" or a
"factory function".
Note: The function call `Object.create(proto)` returns a new object
with its internal [[Prototype]] property (i.e., the object's
prototype) set to the provided `proto`.
8 [20%] Let's do OOP with some privacy
Please implement a factory function, and those objects created by the
factory have some "private properties" and "private methods".
Here, by saying some properties of an object are "private", we mean they
are "hidden states" of some form that are only accessible by the methods
of the object itself. When a private property is a function, we say it
is a "private method."
Here is an example result of a correct implementation:
Construct a new object `myobj` by invoking `createMyObjectFactory`
function with some optional parameters encoded as an object literal:
var myobj = createMyObjectFactory({
param1: 100,
param2: "test",
param3: true
});
Within the object `myobj`, there is a private property, internally
named `counter`, which has a numeric value indicating how many times we
have invoked `myobj.getData()`, and the method `myobj.getCounter()`
can return the private `counter` value:
myobj.getData();
myobj.getData();
myobj.getData();
myobj.counter; // undefined
myobj.getCounter(); // 3
The expression `myobj.counter` gives us undefined becuase there is no
such a "public" property on the object, but the method `getCounter()`
should be able to access the "private" properties correctly.
Hint: We have closures in JavaScript.
//
// 8888888888 888 d8b
// 888 888 Y8P
// 888 888
// 8888888 888 888 88888b. .d8888b 888888 888 .d88b. 88888b.
// 888 888 888 888 "88b d88P" 888 888 d88""88b 888 "88b
// 888 888 888 888 888 888 888 888 888 888 888 888
// 888 Y88b 888 888 888 Y88b. Y88b. 888 Y88..88P 888 888
// 888 "Y88888 888 888 "Y8888P "Y888 888 "Y88P" 888 888
//
// To understand what is `this`, let's consider a function `fff` defined as:
var fff = function () {
var _type = typeof this;
if ((_type === 'object' && this !== null) || _type === 'function')
console.log(this.test); // print out the 'test' property of this
else
console.log('this is not an object');
};
// When a function is called by a direct reference when not in strict mode,
// `this` is bound to the global object which contains all global variables
fff(); // undefined is printed
test = 777;
fff(); // 777 is printed
// When a function is called as a method indirectly,
// `this` is bound to the object that contains the method
var obj = {
test: 888,
ggg: fff
};
obj.ggg(); // 888 is printed
// When a function is called with `new` operator,
// `this` is bound to that newly created object
fff.prototype.test = 999;
var myobj = new fff(); // 999 is printed
// We can use `call()` to call a function with a specified this binding
test = 777;
obj = { test: 888, ggg: fff };
fff(); // 777 is printed
fff.call(obj); // 888 is printed
// You can also pass arguments while using `call()`
var foo = { a: 100, b: 200 };
var bar = function (a, b) {
this.a = a;
this.b = b;
};
bar.call(foo, 'hello', 'world');
foo.a; // 'hello'
foo.b; // 'world'
// You can pass "an array" of arguments using `apply()`
var foo = { a: 100, b: 200 };
var bar = function (a, b) {
this.a = a;
this.b = b;
};
var args = [ 'hello', 'world' ];
bar.apply(foo, args);
foo.a; // 'hello'
foo.b; // 'world'
// We can fix the `this` binding inside a function
// for all non-`new` function calls in the future using `bind()`
//
//
// When we write:
//
// FuncB = FuncA.bind( obj );
//
// FuncB is a copy of FuncA, but when FuncB is invoked
// the `this` value in FuncB will always be `obj` no matter what
var obj1 = {};
var before = function () { return this; }; // this value depends
var after = before.bind(obj1); // this will always be obj1
after() === obj1; // true
var obj2 = { before: before, after: after };
obj2.before() === obj2; // true
obj2.after() === obj2; // false
obj2.after() === obj1; // true
// These special methods we've just seen that are
// shared across all function objects are:
Function.prototype.call
Function.prototype.apply
Function.prototype.bind
// Recall the OOP model with the "prototype" concept in JavaScript.
// Because `call`, `apply`, and `bind` listed above are inherited by
// all functions we created, we can access them in the form of
// `someFunc.call`, `someFunc.apply`, and `someFunc.bind`.
// About another special local variable----`arguments`
// You can get the "actual" number of arguments that get passed in
var bar = function (a, b) {
return arguments.length;
};
bar(); // 0
bar(9); // 1
bar(9, 8); // 2
bar(9, 8, 7, 6); // 4
// It is a special object (like an "array") containing all arguments that
// is actually passed into a function
var bar = function (a, b) {
return arguments[0] + arguments[1] + arguments[2];
};
bar(3, 4, 5); // 12
// Do not forget the fact that a JavaScript function is also a
// JavaScript object, which can store any number of properties.
var funcIsAlsoAnObject = function () {};
funcIsAlsoAnObject.someprop = 'test';
funcIsAlsoAnObject.someprop; // 'test'
// So, how about caching the ouputs of a function? Note that
// in the following example code we are using "a named function
// expression" so that the function can refer to itself safely.
var factorial = function factorial(n) {
var undef;
var cache = factorial.cacheTable;
if (!Number.isInteger(n)) {
throw new Error('n is not an integer');
}
if (n < 0) {
throw new Error('n is negative');
}
if (cache[n] === undef) {
cache[n] = n * factorial(n - 1);
}
return cache[n];
};
factorial.cacheTable = [ 0, 1 ]; // might be messed up outside the function
// Of course, we can achieve the same results by using a
// "closure" which might look cleaner and simpler.
var factorial = (function () {
var cache = [ 0, 1 ]; // Only accessible to functions inside this closure
// after the surrounding anonymous function returns.
return function factorial(n) {
var undef;
if (!Number.isInteger(n)) {
throw new Error('n is not an integer');
}
if (n < 0) {
throw new Error('n is negative');
}
if (cache[n] === undef) {
cache[n] = n * factorial(n - 1);
}
return cache[n];
};
}());
//
// d8888
// d88888
// d88P888
// d88P 888 888d888 888d888 8888b. 888 888
// d88P 888 888P" 888P" "88b 888 888
// d88P 888 888 888 .d888888 888 888
// d8888888888 888 888 888 888 Y88b 888
// d88P 888 888 888 "Y888888 "Y88888
// 888
// Y8b d88P
// "Y88P"
//
// Note that the methods of array objects which will be
// listed below are actually inherited from `Array.prototype`.
// Get the size of an array
var arr = ['hello', 'world', 123];
arr.length; // 3
// Extract an element
var arr = ['hello', 'world', 123];
arr[1]; // 'world'
// Forwards search an element starting from an index (default: 0)
var arr = [3,4,5,3];
arr.indexOf(3); // 0
arr.indexOf(5); // 2
arr.indexOf(7); // -1
arr.indexOf(3, 1); // 3
arr.indexOf(5, 1); // 2
// Backwards search an element starting from an index (default: this.length-1)
var arr = [3,4,5,3];
arr.lastIndexOf(3); // 3
arr.lastIndexOf(3, 3); // 3
arr.lastIndexOf(3, 2); // 0
arr.lastIndexOf(3, -1); // 3
arr.lastIndexOf(3, -2); // 0
// Concatenate current array with given values and/or given arrays of values
var arr = [1,2,3];
arr.concat(4,5,[6,7,8],[9],[]); // [1,2,3,4,5,6,7,8,9]
arr; // [1,2,3]
// (In-place) Append new elements to the end of an array
var arr = [3,4,5];
arr.push(6,7,[8,9]); // 6
arr; // [3,4,5,6,7,[8,9]]
arr[0]; // 3
arr[1]; // 4
arr[2]; // 5
arr[3]; // 6
arr[4]; // 7
arr[5]; // [8,9]
arr.length; // 6
arr[6]; // undefined
arr[7]; // undefined
arr.length; // 6
arr[8] = 'NinthElm';
arr[6]; // undefined
arr[7]; // undefined
arr[8]; // 'NinthElm'
arr.length; // 9
// (In-place) Prepend new elements to the beginning of an array
var arr = [3,4,5];
arr.unshift(6,7,[8,9]); // 6
arr; // [6,7,[8,9],3,4,5]
// (In-place) Remove (and return) the last element in an array
var arr = [3,4,5];
arr.pop(); // 5
arr; // [3,4]
// (In-place) Remove (and return) the first element in an array
var arr = [3,4,5];
arr.shift(); // 3
arr; // [4,5]
// (In-place) Pop out some elements (and optionally insert new ones at the location)
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(3, 0); // []
arr; // [0,1,2,3,4,5,6,7,8,9]
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(3, 2); // [3,4]
arr; // [0,1,2,5,6,7,8,9]
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(3); // [3,4,5,6,7,8,9]
arr; // [0,1,2]
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(-4); // [6,7,8,9]
arr; // [0,1,2,3,4,5]
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(-4, 2);// [6,7]
arr; // [0,1,2,3,4,5,8,9]
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.splice(3, 2, '3', [4], null); // [3,4]
arr; // [0,1,2,'3',[4],null,5,6,7,8,9]
// filter() selects some elements from an array
var arr = [1,2,3,4,5,7,9];
var isOdd = function (n) { return n % 2 === 1 };
arr.filter(isOdd); // [1,3,5,7,9]
arr; // [1,2,3,4,5,7,9]
// every() stops once it gets one false value
// some() stops once it gets one true value
// map() and forEach() go through the whole list
var arr1 = [1,3,5,7,9];
var arr2 = [1,2,5,7,9];
var isOdd = function (n) { return n % 2 === 1 };
var isEven = function (n) { return n % 2 === 0 };
arr1.every(isOdd); // true
arr2.every(isOdd); // false
arr1.some(isEven); // false
arr2.some(isEven); // true
var print = function (n) { console.log(n) };
var addTen = function (n) { return n + 10 };
arr1.forEach(print); // 1, 3, 5, 7, and 9 are printed
arr1.map(addTen); // [11,13,15,17,19]
// We can also get the current index within our functions during enumeration
// since those function are actually invoked with more arguments
var arr = ['hello', 'world', 'js'];
arr.map(function (elm, i, ref) {
console.log(elm, i, ref === arr);
return i + ' ' + elm;
});
//
// The following three lines are printed:
// hello 0 true
// world 1 true
// js 2 true
//
// And the array is returned:
// ["0 hello", "1 world", "2 js"]
// reduce() or reduceRight()
var squareSumInit = 0;
var squareSumReducer = function (accumulated, current) {
return accumulated + current * current;
};
[1,2,3,4].reduce(squareSumReducer, squareSumInit); // 30
var add = function (a, b) { return a + b; };
var square = function (n) { return n * n; };
[1,2,3,4].map(square).reduce(add, 0); // 30
// (In-place) Sort an array
var arr = ['b','d','a','c'];
arr.sort();
arr; // ['a','b','c','d']
var nums = [2,10,4,1,3];
nums.sort();
nums; // [1,10,2,3,4]
nums.sort(function(a,b){ return a-b });
nums; // [1,2,3,4,10]
// (In-place) Reverse an array
var arr = [3,4,5];
arr.reverse();
arr; // [5,4,3]
// Join array of strings into one string
var arr = ['hello', 'JS', 'world'];
arr.join(); // 'hello,JS,world'
arr.join('$'); // 'hello$JS$world'
arr.join(''); // 'helloJSworld'
// Extract a copy of subarray
//
//
// Zero-based index vs Negative index
//
// [ 'A', 'B', 'C', 'D', 'E' ]
//
// 0 1 2 3 4 -->
// <-- -5 -4 -3 -2 -1
//
var arr = [0,1,2,3,4];
arr.slice(); // [0,1,2,3,4]
arr.slice(0); // [0,1,2,3,4]
arr.slice(2); // [2,3,4]
arr.slice(-3); // [2,3,4]
arr.slice(1, 3); // [1,2]
arr.slice(1, -2); // [1,2]
arr.slice(-3, -1); // [2,3]
//
// .d8888b. 888 d8b
// d88P Y88b 888 Y8P
// Y88b. 888
// "Y888b. 888888 888d888 888 88888b. .d88b.
// "Y88b. 888 888P" 888 888 "88b d88P"88b
// "888 888 888 888 888 888 888 888
// Y88b d88P Y88b. 888 888 888 888 Y88b 888
// "Y8888P" "Y888 888 888 888 888 "Y88888
// 888
// Y8b d88P
// "Y88P"
//
// Although a String value is primitive, the wrapper constructor `String` will
// be used automatically for "ToObject" conversion to create an anonymous
// temporary object that inherits many useful methods from `String.prototype`,
// so we can easily work with any String primitive value while having handy
// properties/methods just like we are dealing with an object.
// Get the length of a string (similar to an array of characters)
var str = 'Hello';
str.length; // 5
'Hello'.length; // 5
// Extract a character from a string (similar to an array of characters)
'ABCDE'.charAt(3); // 'D'
'ABCDE'[3]; // 'D'
// Extract a substring
'ABCDE'.slice(2); // 'CDE'
'ABCDE'.slice(-3); // 'CDE'
'ABCDE'.slice(1, 3); // 'BC'
'ABCDE'.slice(1, -2); // 'BC'
'ABCDE'.slice(-3, -1); // 'CD'
'ABCDE'.substr(2); // 'CDE'
'ABCDE'.substr(-3); // 'CDE'
'ABCDE'.substr(1, 2); // 'BC'
'ABCDE'.substr(-3, 2); // 'CD'
'ABCDE'.substring(2); // 'CDE'
'ABCDE'.substring(1, 3); // 'BC'
// Get numeric Unicode value of a character
'ABCDE'.charCodeAt(3); // 68
// From numeric Unicode value
String.fromCharCode(68); // 'D'
String.fromCharCode(65,66,67,68); // 'ABCD'
// Search for a substring
'To be, or not to be'.indexOf('BE'); // -1
'To be, or not to be'.indexOf('be'); // 3
'To be, or not to be'.indexOf('be', 4); // 17
'To be, or not to be'.lastIndexOf('be'); // 17
'To be, or not to be'.lastIndexOf('be', 16); // 3
// Find the first matched index for a RegExp
'To be, or not to be'.search(/be/); // 3
'To be, or not to be'.search(/be(?!,)/); // 17
// Use RegExp to match a string
'To be, or not to be'.match(/be/); // ['be']
'To be, or not to be'.match(/be/g); // ['be', 'be']
'To be, or not to be'.match(/o./g); // ['o ', 'or', 'ot', 'o ']
// Split a string into an array
'Apple,Berry,Cherry'.split(','); // [ 'Apple', 'Berry', 'Cherry' ]
// Concatenate multiple strings
'Hello' + ' ' + 'world!'; // 'Hello world!'
// Transform between upper case and lower case
'Hello'.toUpperCase(); // 'HELLO'
'Hello'.toLowerCase(); // 'hello'
// Trim whitespaces from both ends
' Hello world! '.trim(); // 'Hello world!'
// Replace a portion of a string using RegExp
var str = 'Apples are round, and apples are juicy.';
str.replace(/[Aa]pple/g, 'orange'); // 'oranges are round, and oranges are juicy.'
@LouisWayne
Copy link

Do we have answers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment