Advanced JavaScript
ShevaXu opened this issue · comments
Advanced JavaScript
bind(), apply() & call()
It's all about this.
All three methods can set this value for a function.
bind()
fun.bind(thisArg[, arg1[, arg2[, ...]]])
The key of bind()
is that it creates a new function for later calls, or no immediate invocation.
- currying/partial functions - when provided with arguments
- async callbacks - e.g., keep context in
window.setTimeout()
apply() & call()
These two methods call a function with a given this value and arguments.
The fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
fun.apply(thisArg, [argsArray])
fun.call(thisArg[, arg1[, arg2[, ...]]])
- chain constructors
- invoke build-in/anonymous functions
- monkey patch
Prototypal Inheritance
Eric Elliott advocates prototypal inheritance as one of the two pillars of JavaScript (the other one is functional programming).
In the article How to Escape the 7th Circle of Hell he addressed that classical inheritance suffers from the Gorilla/Banana problem ("You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."), the too-tight coupling between parent and child, and your ending up building new classes for subtle differences.
In class inheritance, instances inherit from a blueprint (the class), and create sub-class relationships. In other words, you can’t use the class like you would use an instance. You can’t invoke instance methods on a class definition itself. You must first create an instance and then invoke methods on that instance.
In prototypal inheritance, instances inherit from other instances. Using delegate prototypes (setting the prototype of one instance to refer to an examplar object), it’s literally Objects Linking to Other Objects, or OLOO, as Kyle Simpson calls it. Using concatenative inheritance, you just copy properties from an exemplar object to a new instance.
As a quote from Design Patterns: Elements of Reusable Object-Oriented Software says, "favor object composition (assembling smaller functionalities, thinking can-do, has-a, or uses-a relationships) over class inheritance (is-a)".
Three Different Kinds Of Prototypal OO
There are three kinds of prototypal OO according to Eric and he built stampit (factory) as a sugar & abstraction on top of prototype. They are implemented as a factory - a function that creates and returns objects (but NOT a constructor).
1. Delegation / Differential Inheritance - Methods
A delegate prototype is an object that serves as a base for another object. Method delegation is a fantastic way to preserve memory resources, because you only need one copy of each method to be shared by all instances.
var proto = {
hello: function () {
return 'Hello, my name is ' + this.name
}
}
var george = Object.create(proto) // the factory creates an instance
george.name = 'George' // JS has dynamic object extension
george.hello() // 'Hello, my name is George'
Object.create() - creates a new object with the specified prototype object and properties.
2. Cloning / Concatenative Inheritance / Mixins - States
Prototype cloning copy the properties from one object to another WITHOUT retaining a reference between the two objects.
var someone = Object.create(proto)
var george = Object.assign(someone, { name: 'George' })
george.hello() // 'Hello, my name is George'
Object.assign() - [ES6] copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
3. Closure Prototypes / Functional Inheritance - Encapsulation / Privacy
Closure prototypes are functions that can be run against a target object in order to extend it. It’s a function prototype. Think of it as an alternative to a constructor
or init
function.
var age = function (value) {
var privacy = value
this.getAge = function () {
return privacy
}
this.aging = function () {
privacy++
}
}
age.call(george, 20)
george.getAge() // 20
george.aging()
george.getAge() // 21
Takeaways
- A class is a blueprint.
- A prototype is an object instance.
Just don't use new
, constructor
, super
and ES6 class
.
Three types of prototypal OO:
- Delegate - prototype link - methods
- cloning/concatenate - deep copy - states
- functional - closure - encapsulation
The stampit form:
const stamp = stampit({ methods, refs, init, props })
const instance = stamp({ count: 0 }) // factory with provided refs
(Here's the demonstration using stamps, but API outdated, see the lastest.)
Concatenative Inheritance
Concatenative inheritance is the easiest way to achieve JavaScript’s simplified form of object composition (this is what most people mean by "mixins" in JavaScript).
The process of inheriting features directly from one object to another by copying the source objects properties. In JavaScript, source prototypes are commonly referred to as mixins. Since ES6, this feature has a convenience utility in JavaScript called
Object.assign()
. Prior to ES6, this was commonly done with Underscore/Lodash’s.extend()
jQuery’s$.extend()
, and so on ...-- The Heart & Soul of Prototypal OO: Concatenative Inheritance
const distortion = { distortion: 1 }
const volume = { volume: 1 }
const cabinet = { cabinet: 'maple' }
const lowCut = { lowCut: 1 }
const inputLevel = { inputLevel: 1 }
const GuitarAmp = (options) => {
return Object.assign({}, distortion, volume, cabinet, options)
}
const BassAmp = (options) => {
return Object.assign({}, lowCut, volume, cabinet, options)
}
const ChannelStrip = (options) => {
return Object.assign({}, inputLevel, lowCut, volume, options)
}
Objects can be built by adding properties to existing instances ad-hoc.
Dynamic object extension is the foundation of concatenative inheritance.
Middleware & Decorator
From this blog. Also Aspect-oriented Programming AOP.
Middleware
The most flexible/controlled way.
function myComponentFactory() {
let suffix = ''
const instance = {
setSuffix: suff => suffix = suff,
printValue: value => console.log(`value is ${value + suffix}`),
addDecorators: decorators => {
let printValue = instance.printValue
decorators.slice().reverse().forEach(decorator => printValue = decorator(printValue))
instance.printValue = printValue
}
}
return instance
}
function toLowerDecorator(inner) {
return value => inner(value.toLowerCase())
}
function validatorDecorator(inner) {
return value => {
const isValid = ~value.indexOf('My')
setTimeout(() => {
if (isValid) inner(value)
else console.log('not valid man...')
}, 500)
}
}
const component = myComponentFactory()
component.addDecorators([toLowerDecorator, validatorDecorator])
component.setSuffix('!')
component.printValue('My Value')
component.printValue('Invalid Value')
More famous examples: express, and Redux
Monkey Patching
function myComponentFactory() {
let suffix = ''
return {
setSuffix: suf => suffix = suf,
printValue: value => console.log(`value is ${value + suffix}`)
}
}
function decorateWithToLower(inner) {
const originalPrintValue = inner.printValue
inner.printValue = value => originalPrintValue(value.toLowerCase())
}
function decorateWithValidator(inner) {
const originalPrintValue = inner.printValue
inner.printValue = value => {
const isValid = ~value.indexOf('My')
setTimeout(() => {
if (isValid) originalPrintValue(value)
else console.log('not valid man...')
}, 500)
}
}
const component = myComponentFactory()
decorateWithToLower(component)
decorateWithValidator(component)
component.setSuffix('!')
component.printValue('My Value')
component.printValue('Invalid Value')
It's consider harmful ... NO MONKEY PATCHING
Others
- Plain closures
- Prototypal inheritance (recommended for simple use)
- ES6 Proxy (overkilled)
Misc
Scoping and Hoisting (Ref)
JavaScript has function-level scope, which is radically different from the C family. Blocks, such as if statements, do not create a new scope. Only functions do.
On runtime, all var and FunctionDeclarations function foo() {...}
are moved by the interpreter to the beginning of each function (its scope) - this is known as Hoisting.
So the good practise is:
function foo (a, b, c) {
var x = 1 // declare all the vars altogether on the first line
var bar
var baz = "something"
}
Immediately-Invoked Function Expression (IIFE)
When the parser encounters the
function
keyword in the global scope or inside a function, it treats it as a function declaration (statement), and not as a function expression, by default.
So, function foo(){ /* code */ }();
would causes a SyntaxError: Unexpected token )
.
The most widely accepted way to fix this is just to wrap it in parentheses, because in JavaScript, parens can’t contain statements.
(function () { /* code */ } ());
(function () { /* code */ }) ();
This is useful for saving state with closures.
Read this for more detail.
(module.)exports
if you want your module to be of a specific object type, use
module.exports
; if you want your module to be a typical module instance, useexports
.
-- ref
exports
is the recommended object unless you are planning to change the object type of your module from the traditional 'module instance' to something else.- As long are you don't overwrite the
module.exports
object with an assignment operation, anything attached tomodule.exports
andexports
will be available in the 'required' module.
If you want the root of your module's export to be a function (such as a constructor) or if you want to export a complete object in one assignment instead of building it one property at a time, assign it to
module.exports
instead ofexports
. -- nodejs api
(Non-)Blocking of Node.js
From hashnode
- Node.js uses multiple threading strategies. The libuv thread pool size equals to the number of logical CPU cores.
- Node.js has many different queues. When the thread pool exceeds, each additional IO tasks is appended to the queue (FiFO).
- A slow Node.js app rarely has something to do with thread pool size.
setTimeout( {...}, 0)
trick moves the slow code to the end of the message queue, allows other code to execute before our slow code blocks everything else, thus speed up synchronous code execution.- As a developer just work with the Event Loop which is single threaded and non-blocking. It's Node's responsibility to delegate the blocking tasks to worker threads.
Operator Precedence
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
Hacks
- Converting to boolean using
!!
- Converting to number using
+
- Short-circuits conditionals
connected && login()
- Default values with
||
- Caching
array.length
in the loop (avoid large array length recalculation) - Detecting properties with
in
- Replace all
str.replace(/this/g, 'that')
- Merging array
arr1.push.apply(arr1, arr2)
instead ofarr1.concat(arr2)
(to save memory) - Shuffling array
arr.sort(() => Math.random() - 0.5)
Parameters Handling
http://exploringjs.com/es6/ch_parameter-handling.html
function mandatory() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = mandatory()) {
return mustBeProvided;
}
Resources
Hub
- JavaScript the right way: code styles, good parts, patterns, frameworks and news, almost everything about JS
- Learning Advanced JavaScript by John Resig, the creater of JQuery
- MDN's learn the web: for API references and tutorials (kind of official)
News feed
- DailyJS: I read it for a while, till it ended on July 7th, 2015
- javascript.com: more like a community
- EchoJS: HN-like ranked news
Free eBooks
A complete list can be found at jsbooks
Selected
- Single page app (online)
- Building a framework (pdf, 77 pages)
- You Don't Know JS (book series) on Github
- Programming JavaScript Applications by Eric Elliott
- Learning JavaScript Design Patterns (online)
Node
- Stream handbook (Github markdown)
- Mixu's Node book: Mixu has written other books