JavaScript 在对象中查找指定值并返回引用路径

该函数用于在对象中查找指定的数值, 返回包含该值的引用路径的数组.

参数allowCircleStruct用于设定是否递归环状结构, 通常设为false即可, 若设为true在一些情况下可能造成堆栈溢出.

参数allowEnumProto用于设定是否允许枚举Prototype中的可枚举属性, 设为true时将使用for-in循环, 设为false将使用Object.keys返回的数组循环.

参数depth用于设定递归深度.

据说ES6支持尾递归, 不过看了下好像也改造不成尾递归, 就没有继续做进一步优化.

例子:

GetValueReferencePath({'a': {'b': 1}, 'b': 1, 'c': [1]}, 1)  
//Return ["['a']['b']", "['b']", "['c']['0']"]

CoffeeScript:

GetValueReferencePath = (obj, value, allowCircleStruct = false, allowEnumProto = false, depth = 10) ->  
  'use strict'
  result = []
  path = []
  cache = []
  level = 0

  IsArray = Array.isArray

  IsObject = (obj, type = typeof obj) ->
    type is 'function' or type is 'object' and !!obj

  Find = do ->
    if allowEnumProto
      (obj, name) ->
        level++
        path.push name.replace(/'/g, '\\\'') if name?
        unless allowCircleStruct
          return if cache.indexOf(obj) isnt -1
          cache.push obj
          for k, v of obj when obj.hasOwnProperty k
            if v is value
              result.push "#{ if path.length then "['#{ path.join "']['" }']" else '' }['#{ k }']"
            if IsArray(v) or IsObject(v) and level < depth
              Find v, k
              path.pop()
            level--
    else
      (obj, name) ->
        level++
        path.push name.replace(/'/g, '\\\'') if name?
        unless allowCircleStruct
          return if cache.indexOf(obj) isnt -1
          cache.push obj
          for k in Object.keys obj
            v = obj[k]
            if v is value
              result.push "#{ if path.length then "['#{ path.join "']['" }']" else '' }['#{ k }']"
            if IsArray(v) or IsObject(v) and level < depth
              Find v, k
              path.pop()
              level--

  Find obj
  cache = null
  result

JavaScript:

// Generated by CoffeeScript 1.9.1
var GetValueReferencePath;

GetValueReferencePath = function(obj, value, allowCircleStruct, allowEnumProto, depth) {  
  var Find, IsArray, IsObject, cache, level, path, result;
  if (allowCircleStruct == null) {
    allowCircleStruct = false;
  }
  if (allowEnumProto == null) {
    allowEnumProto = false;
  }
  if (depth == null) {
    depth = 10;
  }
  'use strict';
  result = [];
  path = [];
  cache = [];
  level = 0;
  IsArray = Array.isArray;
  IsObject = function(obj, type) {
    if (type == null) {
      type = typeof obj;
    }
    return type === 'function' || type === 'object' && !!obj;
  };
  Find = (function() {
    if (allowEnumProto) {
      return function(obj, name) {
        var k, results, v;
        level++;
        if (name != null) {
          path.push(name.replace(/'/g, '\\\''));
        }
        if (!allowCircleStruct) {
          if (cache.indexOf(obj) !== -1) {
            return;
          }
          cache.push(obj);
          results = [];
          for (k in obj) {
            v = obj[k];
            if (!(obj.hasOwnProperty(k))) {
              continue;
            }
            if (v === value) {
              result.push((path.length ? "['" + (path.join("']['")) + "']" : '') + "['" + k + "']");
            }
            if (IsArray(v) || IsObject(v) && level < depth) {
              Find(v, k);
              path.pop();
            }
            results.push(level--);
          }
          return results;
        }
      };
    } else {
      return function(obj, name) {
        var i, k, len, ref, results, v;
        level++;
        if (name != null) {
          path.push(name.replace(/'/g, '\\\''));
        }
        if (!allowCircleStruct) {
          if (cache.indexOf(obj) !== -1) {
            return;
          }
          cache.push(obj);
          ref = Object.keys(obj);
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            k = ref[i];
            v = obj[k];
            if (v === value) {
              result.push((path.length ? "['" + (path.join("']['")) + "']" : '') + "['" + k + "']");
            }
            if (IsArray(v) || IsObject(v) && level < depth) {
              Find(v, k);
              path.pop();
              results.push(level--);
            } else {
              results.push(void 0);
            }
          }
          return results;
        }
      };
    }
  })();
  Find(obj);
  cache = null;
  return result;
};