docs: function parseObj (d, result, node, init) {
    if (init) {
      result.nodes = [];
      result.ref = {};
      if (node) result.diff = {};
    }
  
    node = node || {};
  
    node.obj = d;
  
    if (type.call(d) == ARRAY) {
      var nodes = [];
      /* for array type, each children have a parent that not on the virtual tree,
         see test case of @media-array for example, the array node obj=Array, but have node.selPart(no selText)
         So have to set the right node.at/node.type from the node.key, to get right selText for children */
      node.at = reAtRule.exec(node.key);
      for(var i = 0; i < d.length; i++) {
        var prev = node[i];
        var n = parseObj(d[i], result, node[i] || {parent: node, src: d, parentNode: nodes, index: i});
        if(result.diff && prev!=n) arrayKV(result.diff, n ? 'added' : 'removed', n||prev);
        nodes.push(n);
      }
      return nodes
    } else {
      if (d[KEY_ID]) result.ref[d[KEY_ID]] = node;
      // it's no need to check (type.call(d) == OBJECT)
      // isIterable will filter only ARRAY/OBJECT
      // other types will goto parseProp function
      var prevVal = node.prevVal = node.lastVal;
      // at first stage check $test
      if (KEY_TEST in d) {
        var test = isFunction(d[KEY_TEST]) ? d[KEY_TEST](!node.disabled, node, result) : d[KEY_TEST];
        // if test false, remove node completely
        // if it's return function, going to stage 2 where all prop rendered
        if(!test) {
          return
        }
        node.test = test;
      }
      var children = node.children = node.children || {};
      node.lastRaw = node.rawVal || {};
      node.lastVal = {};
      node.rawVal = {};
      node.prop = {};
      node.diff = {};
      var order = d[KEY_ORDER] | 0;
      var funcArr = [];
  
      var processObj = function (obj, k, nodeObj) {
        var haveOldChild = k in children;
        var newNode = extendObj(children, k, nodeObj);
        // don't overwrite selPart for previous node
        newNode.selPart = newNode.selPart || splitSelector(k, ',');
        var n = parseObj(obj, result, newNode);
        if(n) children[k] = n;
        // it's new added node
        if (prevVal) !haveOldChild
          ? n && arrayKV(result.diff, 'added', n)
          : !n && arrayKV(result.diff, 'removed', children[k]);
        // for first time check, remove from parent (no diff)
        if(!n) delete nodeObj.parent.children[k];
      };
  
      // only there's no selText, getSel
      if(!('selText' in node)) getSel(node, result);
  
      for (var k in d) {
        // here $key start with $ is special
        // k[0] == '$' ... but the core will calc it into node.
        // Plugins should take $ with care and mark as a special case. e.g. ignore it
        if (!own(d, k)) continue
        if (!isIterable(d[k]) || type.call(d[k]) == ARRAY && !isIterable(d[k][0])) {
  
          // it's inline at-rule: @import etc.
          if (k[0]=='@') {
            processObj(
              // map @import: [a,b,c] into {a:1, b:1, c:1}
              [].concat(d[k]).reduce(function(prev, cur) {
                prev[cur] = ';';
                return prev
              }, {}), k, {parent: node, src: d, key: k, inline:true});
            continue
          }
  
          var r = function (_k) {
            // skip $test key
            if(_k != KEY_TEST) parseProp(node, d, _k, result);
          };
          order
            ? funcArr.push([r, k])
            : r(k);
        } else {
          processObj(d[k], k, {parent: node, src: d, key: k});
        }
      }
  
      // when it's second time visit node
      if (prevVal) {
        // children removed
        for (k in children) {
          if (!(k in d)) {
            arrayKV(result.diff, 'removed', children[k]);
            delete children[k];
          }
        }
  
        // prop changed
        var diffProp = function () {
          var newKeys = keys(node.lastVal);
          var removed = keys(prevVal).filter(function (x) { return newKeys.indexOf(x) < 0 });
          if (removed.length) node.diff.removed = removed;
          if (keys(node.diff).length) arrayKV(result.diff, 'changed', node);
        };
        order
          ? funcArr.push([diffProp, null])
          : diffProp();
      }
  
      if (order) arrayKV(result, '_order', {order: order, func: funcArr});
      result.nodes.push(node);
      return node
    }
  
  }