We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through JavaScript Works average a 15% increase in salary 🚀

Blog hero image

The Most Clever Line of JavaScript

Seva Zaikov 13 November, 2017 | 3 min read

Recently my friend sent me a very interesting JS snippet, which he found in one open-source library:

addressParts.map(Function.prototype.call, String.prototype.trim);

At first, I laughed and thought “nice try”. Second thought was that map accepts only one argument, and I went to MDN docs, where I realized that you can pass context as a second argument. At this point I was really puzzled, and after running my confusion increased to the limit – it worked as expected!

I’ve spent at least half an hour playing with it, and it was an interesting example how magical JavaScript can be, even after years spent writing it. Feel free to figure it out by yourself, and if you want to check out my understanding, keep reading!

So, how does it work? Let’s start with super naive implementation (which is actually shorter and easier to read :)):

addressParts.map(str => str.trim());

But as it is clear, we strive here to avoid creating new functions, so let’s move on. Function.prototype.call is a function in prototype of all JavaScript functions, and it invokes function, using first argument as this argument, passing rest arguments as parameters of function invocation. To illustrate:

// this function has `Function` in prototype chain
// so `call` is available
function multiply(x, y) {
  return x * y;
}

multiply.call(null, 3, 5); // 15
multiply(3, 5); // same, 15

Typical usage of the second argument can be the following – imagine you have a class-based react component, and you want to render list of buttons:

class ExampleComponent extends Component {
  renderButton({ title, name }) {
    // without proper `this` it will fail
    const { isActive } = this.props;
    return (
      <Button key={title} title={title}>
        {name}
      </Button>
    );
  }

  render() {
    const { buttons } = this.props;

    // without second param our function won't be able
    // to access `this` inside
    const buttonsMarkup = buttons.map(this.renderButton, this);
  }
}

However, from my experience, it is not that common to use this second argument, usually class properties or decorators are used to avoid binding all the time.

There is one similar method – Function.prototype.apply, which works the same, except that the second argument should be an array, which will be transformed to a normal list of arguments, separated by comma. So, let’s see how we can use it to calculate maximum:

Math.max(1, 2, 3); // if we know all numbers upfront
// we can call it like that

Math.max([1, 2, 3]); // won't work!

Math.max.apply(null, [1, 2, 3]); // will work!

// however, ES2015 array destructuring works as well:
Math.max(...[1, 2, 3]);

Now, let’s try to recreate a call which will solve our problem. We want to trim the string, and it is a method in String.prototype, so we call it using . notation (however, strings are primitives, but when we call methods, they are converted to objects internally). Let’s go back to our console:

// let's try to imagine how trim method is implemented
// on String.prototype
String.prototype.trim = function() {
  // string itself is contained inside `this`!
  const str = this;
  // this is a very naive implementation
  return str.replace(/(^\s+)|(\s+$)/g, '');
};

// let's try to use `.call` method to invoke `trim`
" aa ".trim.call(thisArg);

// but `this` is our string itself!
// so, next two calls are equivalent:
" aa ".trim.call(" aa ");
String.prototype.trim.call(" aa ");

We are now one step closer, but still not yet there to understand our initial snippet:

addressParts.map(Function.prototype.call, String.prototype.trim);

Let’s try to implement Function.prototype.call itself:

Function.prototype.call = function(thisArg, ...args) {
  // `this` in our case is actually our function!
  const fn = this;

  // also, pretty naive implementation
  return fn.bind(thisArg)(...args);
};

So, now we can put all pieces together. When we declare function inside the .map, we pass Function.prototype.call with bound <String.prototype.trim> as this context, and then we invoke this function on each element in the collection, passing each string as thisArg to the call. It means that String.prototype.trim will be invoked using string as this context! We already found that it will work, using our example:

String.prototype.trim.call(" aa "); // "aa"

Problem solved! However, I think it is not the best example how one should write such a code in JavaScript, and instead just pass a simple anonymous function:

addressParts.map(str => str.trim()); // same effect

If you’re passionate about Front End development, check out the JavaScript Works job-board here!

Originally published on blog.bloomca.me

Author's avatar
Seva Zaikov
Self-taught programmer, with a big passion to learn
    JavaScript
    Clojure
    Python
    Elixir

Related Jobs

Related Issues

xapix-io / devops
xapix-io / devops
  • Open
  • 0
  • 0
  • Intermediate
  • HCL
xapix-io / devops
  • Open
  • 0
  • 0
  • Intermediate
  • HCL
xapix-io / devops
  • Open
  • 0
  • 0
  • Intermediate
  • HCL
xapix-io / devops
  • Open
  • 0
  • 0
  • Intermediate
  • HCL
xapix-io / devops
  • Open
  • 0
  • 0
  • Intermediate
  • HCL
xapix-io / axel-f
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
xapix-io / axel-f
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
WorksHub / client
  • 1
  • 1
  • Intermediate
  • Clojure
  • $50
WorksHub / client
  • 1
  • 0
  • Intermediate
  • Clojure

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through JavaScript Works average a 15% increase in salary.

Start with GithubStart with Stack OverflowStart with Email