(function (tree) {

    tree.visitor = function(implementation) {
        this._implementation = implementation;
    };

    tree.visitor.prototype = {
        visit: function(node) {

            if (node instanceof Array) {
                return this.visitArray(node);
            }

            if (!node || !node.type) {
                return node;
            }

            var funcName = "visit" + node.type,
                func = this._implementation[funcName],
                visitArgs, newNode;
            if (func) {
                visitArgs = {visitDeeper: true};
                newNode = func.call(this._implementation, node, visitArgs);
                if (this._implementation.isReplacing) {
                    node = newNode;
                }
            }
            if ((!visitArgs || visitArgs.visitDeeper) && node && node.accept) {
                node.accept(this);
            }
            funcName = funcName + "Out";
            if (this._implementation[funcName]) {
                this._implementation[funcName](node);
            }
            return node;
        },
        visitArray: function(nodes) {
            var i, newNodes = [];
            for(i = 0; i < nodes.length; i++) {
                var evald = this.visit(nodes[i]);
                if (evald instanceof Array) {
                    evald = this.flatten(evald);
                    newNodes = newNodes.concat(evald);
                } else {
                    newNodes.push(evald);
                }
            }
            if (this._implementation.isReplacing) {
                return newNodes;
            }
            return nodes;
        },
        doAccept: function (node) {
            node.accept(this);
        },
        flatten: function(arr, master) {
            return arr.reduce(this.flattenReduce.bind(this), master || []);
        },
        flattenReduce: function(sum, element) {
            if (element instanceof Array) {
                sum = this.flatten(element, sum);
            } else {
                sum.push(element);
            }
            return sum;
        }
    };

})(require('./tree'));
@@ -21,96 +21,961 @@
     
-tree.visitor = function(implementation) %7B%0A        this._implementation = implementation;
+var _visitArgs = %7B visitDeeper: true %7D,%0A        _hasIndexed = false;%0A%0A    function _noop(node) %7B%0A        return node;%0A    %7D%0A%0A    function indexNodeTypes(parent, ticker) %7B%0A        // add .typeIndex to tree node types for lookup table%0A        var key, child;%0A        for (key in parent) %7B%0A            child = parent%5Bkey%5D;%0A            switch (typeof child) %7B%0A                case %22function%22:%0A                    if (child.prototype.type) %7B%0A                        child.prototype.typeIndex = ticker++;%0A                    %7D%0A                    break;%0A                case %22object%22:%0A                    ticker = indexNodeTypes(child, ticker);%0A                    break;%0A            %7D%0A        %7D%0A        return ticker;%0A    %7D%0A%0A    tree.visitor = function(implementation) %7B%0A        this._implementation = implementation;%0A        this._visitFnCache = %5B%5D;%0A%0A        if (!_hasIndexed) %7B%0A            indexNodeTypes(tree, 1);%0A            _hasIndexed = true;%0A        %7D
 %0A   
@@ -1170,22 +1170,8 @@
 node
- %7C%7C !node.type
 ) %7B%0A
@@ -1234,39 +1234,73 @@
 var 
-funcName = %22visit%22 + 
+nodeTypeIndex = node.typeIndex;%0A            if (!
 node
-.type,
+TypeIndex) %7B
 %0A   
@@ -1316,83 +1316,399 @@
     
-func = this._implementation%5BfuncName%5D,%0A                visitArgs, newNod
+return node;%0A            %7D%0A%0A            var visitFnCache = this._visitFnCache,%0A                impl = this._implementation,%0A                aryIndx = nodeTypeIndex %3C%3C 1,%0A                func = visitFnCache%5BaryIndx%5D,%0A                funcOut = visitFnCache%5BaryIndx %7C 1%5D,%0A                visitArgs = _visitArgs, noop = _noop,%0A                fnName;%0A%0A            visitArgs.visitDeeper = tru
 e;%0A
+%0A
     
@@ -1719,16 +1719,17 @@
     if (
+!
 func) %7B%0A
@@ -1748,56 +1748,304 @@
     
-visitArgs = %7BvisitDeeper: true%7D;%0A               
+fnName = %22visit%22 + node.type;%0A                func = impl%5BfnName%5D %7C%7C noop;%0A                funcOut = impl%5BfnName + %22Out%22%5D %7C%7C noop;%0A                visitFnCache%5BaryIndx%5D = func;%0A                visitFnCache%5BaryIndx %7C 1%5D = funcOut;%0A            %7D%0A%0A            if (func != noop) %7B%0A                var
  new
@@ -2061,36 +2061,20 @@
 nc.call(
-this._implementation
+impl
 , node, 
@@ -2105,36 +2105,20 @@
     if (
-this._implementation
+impl
 .isRepla
@@ -2185,32 +2185,33 @@
 %7D%0A            %7D%0A
+%0A
             if (
@@ -2214,23 +2214,8 @@
 if (
-(!visitArgs %7C%7C 
 visi
@@ -2231,17 +2231,16 @@
 itDeeper
-)
  && node
@@ -2299,32 +2299,33 @@
 ;%0A            %7D%0A
+%0A
             func
@@ -2324,134 +2324,66 @@
     
-funcName = funcName + %22Out%22;%0A            if (this._implementation%5BfuncName%5D) %7B%0A                this._implementation%5BfuncName%5D(
+if (funcOut != noop) %7B%0A                funcOut.call(impl, 
 node
@@ -2391,32 +2391,33 @@
 ;%0A            %7D%0A
+%0A
             retu

Add visitor.js array-based function cache for faster lookups. perf / #1615