I see this was the second javascript question you posted here and it is unfortunate that there have been no responses in the nearly four years since then. Perhaps you have learned a few things about JavaScript since then.

Looking at the `draw` method I see that it stores a reference to `this` in a variable called `self`. This pattern happens frequently in JavaScript code but it is unnecessary. I see that some of the functions in `table` utilize `self` but that could be avoided by binding those functions to `this` with [`Function.bind()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind). 

It also appears that `ctx` is passed to each function as a parameter, which is also unnecessary because `ctx` is hoisted at the top-level and visible within each function as well. 

The functions in `table` associated with keys `"["` and `"]"` could be simply set to `ctx.save.bind(ctx)` and `ctx.restore.bind(ctx)`, respectively. Furthermore, the functions in `table` associated with keys `"+"` and `"-"` can be set to `ctx.rotate.bind(ctx, this.angle)` and `ctx.rotate.bind(ctx, -this.angle)`, respectively.

I'm not sure if those changes will be enough to reduce the CPU load. If not, then consider taking out function calls (i.e. calling `table[char]`) and using a series of `if` statements or `switch` statement in the `for` loop. 

## Demo

While it may not be much, see the demo below which hopefully provides some proof that the suggestions lead to a slight performance gain (see the `drawB()` method). I considered using jsPerf but that might be a bit of a stretch to use for this code.

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

    var rules = [
      //["F", "F[+F]F[-F]F"]

      //["F", "F+F--F+F"]

      //["1", "1F1F"],
      //["0", "1F[+0][-0]"]

      //["F", "F+F-F-F+F"]

      ["F", "FF"],
      ["X", "F-[[X]+X]+F[+FX]-X"]
    ];
    var angle = 10;
    const times = [{
      y: [],
      line: {
        color: "blue",
        width: 4,
        shape: "line"
      }
    }, {
      y: [],
      line: {
        color: "green",
        width: 4,
        shape: "line"
      }
    }];

    function LSystem(rules, angle, init, level) {
      this.rules = {};
      this.angle = angle * Math.PI / 180;
      this.parseRules(rules);
      this.path = this.generateLevel(init, level);
    }
    LSystem.prototype.parseRules = function(rules) {
      var self = this;
      rules.forEach(function(rule) {
        self.rules[rule[0]] = rule[1];
      });
    };
    LSystem.prototype.applyRule = function(input) {
      var output = "";
      for (var i = 0; i < input.length; i++) {
        var char = input[i];
        if (char in this.rules) {
          output += this.rules[char];
        } else {
          output += char;
        }
      }
      return output;
    };
    LSystem.prototype.generateLevel = function(input, level) {
      if (level == 0) return input;
      return this.generateLevel(this.applyRule(input), level - 1);
    };
    LSystem.prototype.draw = function(x, y) {
      var start = +new Date();
      var self = this;
      var path = this.path;
      var table = {
        "F": function(ctx) {
          ctx.moveTo(0, 0);
          ctx.lineTo(5, 0);
          ctx.translate(5, 0);
        },
        "+": function(ctx) {
          ctx.rotate(self.angle);
        },
        "-": function(ctx) {
          ctx.rotate(-self.angle);
        },
        "[": function(ctx) {
          ctx.save();
        },
        "]": function(ctx) {
          //ctx.stroke();
          ctx.restore();
          //ctx.beginPath();
        }
      };
      ctx.save();
      ctx.clearRect(0, 0, 500, 500);
      ctx.beginPath();
      ctx.moveTo(x + 0.5, y + 0.5);
      ctx.translate(x + 0.5, y + 0.5);
      for (var i = 0; i < path.length; i++) {
        var char = path[i];
        var cmd = table[char];
        cmd && cmd(ctx);
      }
      ctx.stroke();
      ctx.restore();
      const end = +new Date();
      times[0].y.push((end - start));
      if (times[0].y.length > 1) {
        avg.innerText = times[0].y.reduce((a, b) => a + b) / times[0].y.length;
      }
    };
    LSystem.prototype.drawB = function(x, y) {
      var start = +new Date();
      var table = {
        "F": function() {
          ctxB.moveTo(0, 0);
          ctxB.lineTo(5, 0);
          ctxB.translate(5, 0);
        },
        "+": ctxB.rotate.bind(ctxB, this.angle),
        "-": ctxB.rotate.bind(ctxB, -this.angle),
        "[": ctxB.save.bind(ctxB),
        "]": ctxB.restore.bind(ctxB)
      };
      ctxB.save();
      ctxB.clearRect(0, 0, 500, 500);
      ctxB.beginPath();
      ctxB.moveTo(x + 0.5, y + 0.5);
      ctxB.translate(x + 0.5, y + 0.5);
      for (var i = 0; i < this.path.length; i++) {
        var char = this.path[i];
        var cmd = table[char];
        cmd && cmd();
      }
      ctxB.stroke();
      ctxB.restore();
      const end = +new Date();
      times[1].y.push((end - start));
      if (times[1].y.length > 1) {
        avgB.innerText = times[1].y.reduce((a, b) => a + b) / times[1].y.length;
      }
      /*if (timesB.length > 1) {
        avgB.innerText = timesB.reduce((a, b) => a + b) / timesB.length;
        avgB.classList.toggle('faster', parseFloat(avgB.innerText) < parseFloat(avg.innerText));
        avgB.classList.toggle('slower', parseFloat(avgB.innerText) > parseFloat(avg.innerText));
        avg.classList.toggle('faster', parseFloat(avg.innerText) < parseFloat(avgB.innerText));
        avg.classList.toggle('slower', parseFloat(avg.innerText) > parseFloat(avgB.innerText));
      }*/
    }


    // main
    var ctx,
      ctxB,
      s = new LSystem(rules, angle, "X", 5),
      sB = new LSystem(rules, angle, "X", 5),
      output;
    window.onload = function() {
      ctx = document.querySelector("canvas").getContext("2d");
      ctxB = document.getElementById("cnvB").getContext("2d");
      output = document.querySelector("pre");

      ctx.translate(50, 500);
      ctx.rotate(Math.PI / 180 * -65);
      ctx.translate(0, -250);

      ctxB.translate(50, 500);
      ctxB.rotate(Math.PI / 180 * -65);
      ctxB.translate(0, -250);

      //ctx.translate(250, 500);
      function d(t) {
        requestAnimationFrame(d);
        ctx.clearRect(0, 0, 500, 500);
        ctxB.clearRect(0, 0, 500, 500);
        s.draw(0, 250);
        sB.drawB(0, 250);

        s.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
        sB.angle = Math.PI / 180 * (25 + Math.sin(t / 1000));
      }
      output.textContent = s.path.length;
      requestAnimationFrame(d);
    };
    const layout = {
      title: 'Comparison of original draw and modified draw method timing',
      xaxis: {
        title: 'time'
      },
      yaxis: {
        title: 'avg. draw time'
      }
    };
    const chartApp = new Vue({
      el: '#chart',
      data() {
        return {
          times: times
        }
      },
      mounted() {
        Plotly.plot('comparison', times, layout);
      },
      watch: {
        times: {
          handler: function() {
            Plotly.react('comparison', times, layout);
          },
          deep: true
        }
      }
    });

<!-- language: lang-css -->

    canvas {
      border: 1px solid black;
    }

    output.slower {
      background-color: red;
      color: white;
    }

    output.faster {
      background-color: green;
      color: white;
    }

<!-- language: lang-html -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.39.1/plotly.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="chart">
      <div id="comparison"></div>
    </div>
    <div>
      Avg time for draw() <output id="avg"></output> ms
    </div>
    <div>
      Avg time for draw() with Simplifications <output id="avgB"></output> ms
    </div>
    <canvas width="500" height="500"></canvas>
    <canvas width="500" height="500" id="cnvB"></canvas>
    <pre></pre>

<!-- end snippet -->