skip to Main Content

I was just experimenting around with the Function.prototype.call and Function.prototype.apply function, when I stumbled upon an error that I didn’t understand:

I wanted to trim all the elements in an array, Naturally, I would have written:

const arr = [' text 1   ', 'nntext 2 '];
arr.map(str => str.trim());

Which works just fine.

But, for some reason, all the following options give me an error :

arr.map(String.prototype.trim.call);
arr.map(''.trim.call);
// => Uncaught TypeError: undefined is not a function

arr.map(String.prototype.trim.apply);
arr.map(''.trim.apply);
// Uncaught:
// TypeError: Function.prototype.apply was called on undefined, which is a undefined and not a function
//     at Array.map (<anonymous>)

But, when I call them outside of the map, or in the map but in a function, like that:

arr.map(str => ''.trim.call(str))

It runs without issue… Why is that?

2

Answers


  1. You do not want to apply the String.prototype.trim to an array. You will want to map the array items. This is not the same as Math.max(...numberArray), because trim is neither static, nor does it take an array of arguments.

    You have two options:

    1. An inline lambda within the Array.prototype.map call:
    const data = ['foo   ', '   bar', '   baz   '];
    const trimmed = data.map(s => s.trim());
    
    console.log(trimmed);
    1. A callback reference Array.prototype.map call:
    const trimFn = (s) => s?.trim();
    
    const data = ['foo   ', '   bar', '   baz   '];
    const trimmed = data.map(trimFn);
    
    console.log(trimmed);

    If you really want this behavior, you could add a static method to the String class/object.

    // Add a static trim method to the String class.
    Object.defineProperty(String, 'Trim', {
      value(str) {
        // OR return String.prototype.trim.call(str ?? '');
        // OR return String.prototype.trim.apply(str ?? '');
        return String.prototype.trim.bind(str ?? '')();
      },
      writable: false,
    });
    
    const data = ['foo   ', '   bar', '   baz   ', null];
    const trimmed = data.map(String.Trim); // Use the static method as a callback
    
    console.log(trimmed);
    Login or Signup to reply.
  2. Why is that?

    There is a difference between calling a function as a method of an object or standalone. If you call it as a method the this will value inside the function will be set automatically to the object. If you call it "standalone" this will be undefined (in strict mode).

    It might not look like it, but in

    String.prototoype.trim.call('foo')
    

    you are calling the .call method of the trim function. The this value inside call will refer to String.prototype.trim, i.e. the function you want to call.

    In

    const call = String.prototype.trim.call
    call('foo')
    

    (which is what happens in your arr.map example), this inside call call will be undefined. call doesn’t know which function should be executed.

    Simplified example:

    'use strict'
    
    const obj = {
       call() {
         console.log('this', this)
       }
    }
    
    obj.call()
    const call = obj.call
    call()

    There are multiple ways to solve this (as you discovered yourself). Another way is to bind the this value of the function to a specific value (which returns a new function). It’s a bit unwieldy though:

    // Might as well refer to Function.prototype.call directly
    arr.map(Function.prototype.call.bind(String.prototype.trim));
    

    Of course you can create a helper function for that:

    function callable(func) {
      return func.call.bind(func)
    }
    
    const arr = [' text 1   ', 'nntext 2 ']
    console.log(arr.map(callable(String.prototype.trim)));
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search