Search code examples
javascriptdefault-parametersdefault-arguments

Using a function call as default parameter javascript


Help me understand why I can't do the following:

function rgb(r = 0, g = 0, b = 0) {
  this.r = r % 255
  this.g = g % 255
  this.b = b % 255,
    this.str = function() {
      return `rgb(${this.r}, ${this.g}, ${this.b})`
    }
}

function rect(w, h, x, y, rgb = new rgb()) {
  this.dim = {
    w: w,
    h: h
  }
  this.pos = {
    x: x,
    y: y
  }
  this.rgb = rgb
}

I get the following error (when calling rect()):

ReferenceError: can't access lexical declaration `rgb' before initialization

function rgb(r = 0, g = 0, b = 0) {
  this.r = r % 255
  this.g = g % 255
  this.b = b % 255,
    this.str = function() {
      return `rgb(${this.r}, ${this.g}, ${this.b})`
    }
}

function rect(w, h, x, y, rgb = new rgb()) {
  this.dim = {
    w: w,
    h: h
  }
  this.pos = {
    x: x,
    y: y
  }
  this.rgb = rgb
}

const r = new rect(1, 2, 3, 4);

Both rgb and rect are defined in the same file, and rgb is defined before rect.


Solution

  • Your variable names are conflicting. You have a rgb function on the top level, but you also have a rgb parameter in rect's parameter list. When inside a parameter list, when referencing a variable name, the interpreter will try to find what the variable name binds to - if the parameter list already has that variable name, it will reference that binding. So new rgb() is referencing the parameter rgb, which hasn't been initialized yet.

    This isn't exactly what's happening, but the scope of a parameter list looks a bit like if the parameter names are declared with let, and then assigned values, eg the scope of

    const fn = (a, b, c = 'bar') => {
      console.log('fn invoked');
    };
    

    is similar to:

    const fn = (argA, argB, argC) => {
      let a;
      let b;
      let c;
      a = argA;
      b = argB;
      c = argC === undefined ? 'bar' : argC;
      fnBody(a, b, c);
    };
    const fnBody = (a, b, c) => {
      console.log('fn invoked');
    }
    

    So doing rgb = new rgb() is like

    let rgb;
    rgb = argRGB === undefined ? new rgb() : argRGB
    //                               ^^^ reference to un-initialized variable
    

    For similar reasons, you can do:

    const fn = (a, b = a) => {
      console.log(a, b);
    };
    
    fn('foo');

    Also similarly, when declaring a variable, you can't reference the name of the variable you're declaring until the variable has finished initializing:

    const foo = null || foo;

    Precise function names can help prevent bugs as well. Consider changing to something like:

    function makeColors(r = 0, g = 0, b = 0) {
      this.r = r % 255;
      this.g = g % 255;
      this.b = b % 255;
      this.str = function() {
        return `rgb(${this.r}, ${this.g}, ${this.b})`
      };
    }
    
    function rect(w, h, x, y, colors = new makeColors()) {
      this.dim = {
        w: w,
        h: h
      }
      this.pos = {
        x: x,
        y: y
      }
      this.colors = colors
    }
    
    const r = new rect(3, 4, 5, 6);
    console.log(r);