timoxley / functional-javascript-workshop

A functional javascript workshop. No libraries required (i.e. no underscore), just ES5.
2.06k stars 441 forks source link

Partial application without Bind - why provide "console" as this? #163

Open peterorosz opened 8 years ago

peterorosz commented 8 years ago

Why is the official solution console.log.apply(console, [namespace].concat(slice.call(arguments)))?

This works just as well: console.log.apply(null, [namespace].concat(slice.call(arguments)))

Thank you for the answer.

justsml commented 7 years ago

I know this is old, but I just was writing on the subject:

JavaScript's context (this) is implicit, meaning:

// function born w/o context (not required)
function getCategory() { return `I'm ${this._category}` }

// let's create objects with implied context 
// so calls to `Porsche.getCategory` automatically wire up `this === Porsche`
var Porsche = {
  _category: 'fast',
  getCategory: getCategory 
}
var Dodge = {
  _category: 'american',
  getCategory: getCategory
}
// Here are some examples how context works & can be manipulated:
Porsche.getCategory() // => I'm fast
getCategory.call(Porsche) // => I'm fast

// Override at runtime using `.call(this, ..args)` 
Porsche.getCategory.call(Dodge) // => I'm american

// "Extract" getCategory from its implicit parent `Porsche` object. 
var getCat = Porsche.getCategory
//   `getCat` is merely a context-free alias of the `getCategory` method.
getCat() //=> I'm undefined

// Attach/Save context for later using `Function.bind`
var imposter = getCat.bind(Dodge)
imposter() // => I'm american
imposter.call(Porsche) // => I'm american
// ^^ Re-binding won't work like this
//   .bind essentially returns a **new function** wrapped with the scope specified.

// Watch out for this pitfall: passing a callback like this will lose the fn's implied context:
setTimeout(Porsche.getCategory, 10); //=> I'm undefined
setTimeout(getCategory, 10); //=> I'm undefined
// ^^ Previous 2 lines show how subtle these bugs can be

// 1st Fix: using `.bind` - returns new func pointer with context locked in
setTimeout(getCategory.bind(Porsche), 5); // I'm fast
setTimeout(getCategory.bind(Dodge), 1000); // I'm american

// 2nd Fix: using a closure, call with implied JS context
setTimeout(() => Porsche.getCategory(), 5); // I'm fast

// WARNING: Be careful designing with this complexity however, it's rarely necessary. 
// +++ Plus: attackers can send surprises:
var payload = {_category: 'here for your Porsche', name: '', email: ''}; // from unchecked form
getCategory.call(payload) // => I'm here for your Porsche

// Ahh!!

image

@peterorosz question's answer needs a bit more info, the console is a little different in NodeJS vs Chrome.

In the browser implementation of console, its methods are usually pre-bound to console.

I try not to rely on automatic context binding of console.log - as I often only discover moving to synthetic browser or unit tests. Unit tests in particular will need console.log.bind(console).