docs: function _merge (opts, target, source) {
    var i
    var j
    var tmp
    var test
    var equal
    var key
    var s = { // hold booleans for source
      isObj: util.isObject(source),
      isFn: util.isFunction(source),
      isArr: util.isArray(source)
    }
    var t = { // hold booleans for target
      isObj: util.isObject(target),
      isFn: util.isFunction(target),
      isArr: util.isArray(target)
    }
  
    if (!opts._visited) {
      opts._visited = []
    }
  
    if (target === source || // for primitives or null
      undefined === source // target stays the same
    ) {
      return target
    }
  
    if (source === null) {
      if (!opts.ignoreNull || target === undefined) {
        target = source
      }
      return target
    }
  
    if (target === null || undefined === target) {
      if (s.isObj && s.isArr) {
        // clone into array
        target = _merge(opts, [], source)
      } else if (s.isObj) {
        // clone source
        target = _merge(opts, {}, source)
      } else {
        // copy primitives
        target = source
      }
      return target
    }
  
    // create new instances for "special" objects
    if (s.isObj) {
      if (util.isRegExp(source)) {
        // create new RegExp
        target = new RegExp(source)
        return target
      } else if (util.isDate(source)) {
        // create new Date
        target = new Date(source)
        return target
      } else if (util.isBuffer(source)) {
        // create new Buffer
        target = new Buffer(source)
        return target
      }
    }
  
    if (s.isFn) { // replace with source function
      target = source
      // TODO - clone possible keys
      return target
    }
  
    if (s.isArr) {
      if (t.isArr) {
        test = [].concat(target) // test for duplicates
        for (i = 0; i < source.length; i++) {
          tmp = source[i]
          if (!~test.indexOf(tmp)) {
            if (!util.isObject(tmp) || tmp === null) { // primitive or function or null or undefined
              target.push(tmp)
            } else {
              equal = false
              j = test.length
              while (j-- > 0 && !(equal = deepEqual(test[j], tmp)));
              if (!equal) target.push(_merge(opts, null, tmp))
            }
          }
        }
      } else {
        // if target is not array replace with source
        target = _merge(opts, [], source)
      }
      return target
    }
  
    if (!t.isFn && !t.isObj && !t.isArr) {
      // copy primitives
      target = source
      return target
    }
  
    // copy properties if not circular
    if (!~opts._visited.indexOf(source)) {
      opts._visited.push(source)
      for (key in source) {
        if (source.hasOwnProperty(key) && key !== '__proto__') {
          target[key] = _merge(opts, target[key], source[key])
        }
      }
      opts._visited.pop()
    } else if (!opts.ignoreCircular) {
      throw new Error('can not merge circular structures.')
    }
  
    return target
  }