lodash의 _.get 동작

lodash _.get은 object에서 원하는 값을 뽑아 낼때 유용한 method이다.

object에 해당 프로퍼티 이름으로 값이 존재 하지 않을 때 예상치 못한 오류들을 막아주고 하위 depth로 갈 수 있으며 안전하게 undefined를 반환해준다. 또한 default값을 세팅할 수 있다.

문뜩, 별거아니지만 프로퍼티를 찾을 때 어떻게 동작하는지 궁금해졌다.

다음과 같은 object가 있다.

var obj = {
    "three": {
        "four": 4
    },
    "one.two": 1
}
console.log(_.get(obj, 'one.two')) //1

console.log(_.get(obj, 'three.four')) // 4

여기서 _.get은 해당 프로퍼티를 찾거나 .이 있으면 하위 depth로 들어간다.

그런데 같은 이름으로 하위 프로퍼티가 있으면 어느게 먼저 일까

var obj = {
    "one": {
        "two": 4
    },
    "one.two": 1
}
console.log(_.get(obj, 'one.two')) //1

답은 해당이름의 프로퍼티 먼저이다.

 function get(object, path, defaultValue) {
  var result = object == null ? undefined : baseGet(object, path);
  return result === undefined ? defaultValue : result;
}

function baseGet(object, path) {
  path = isKey(path, object) ? [path] : castPath(path);

  var index = 0,
      length = path.length;

  while (object != null && index < length) {
    object = object[toKey(path[index++])];
  }
  return (index && index == length) ? object : undefined;
}

lodash 소스를 보면 isKey method에서 key인지를 보고 key이면 해당 프로퍼티 하나만 아니면 path들이 쭉 담긴 arr를 반환한다.
그래서 "a.b"를 key로 판단하고 반환이 된 것이다.
그럼 키인지를 구분하는 isKey 메소드를 보자

function isKey(value, object) {
  if (isArray(value)) {
    return false;
  }
  var type = typeof value;
  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
      value == null || isSymbol(value)) {
    return true;
  }
  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
    (object != null && value in Object(object));
}

일단 "a.b"는 마지막줄에 value in Object기 때문에 key로 취급했다. 그럼 key가 아니라 path일때 path array를 만들어주는 castPath를 보자.

function castPath(value) {
  return isArray(value) ? value : stringToPath(value);
}
function stringToPath(string) {
  var result = [];
  toString(string).replace(rePropName, function(match, number, quote, string) {
    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
  });
  return result;
}

castPath가 string이면 stringToPath가 호출된다. rePropName은 정규식이다.

 rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;

복잡하지만 잘못된 형식을 보정하고 key만 뽑아내는 것이다. 그래서 path만 들어있는 배열이 나온다.

_.set은 어떨까. set의 경우는 같은 이름의 프로퍼티가 있으면 해당 프로퍼티를 만들고 없으면 마찬가지로 path만 뽑아서 하위 depth로 생성한다.

function baseSet(object, path, value, customizer) {
  path = isKey(path, object) ? [path + ''] : baseToPath(path);

  var index = -1,
      length = path.length,
      lastIndex = length - 1,
      nested = object;

  while (nested != null && ++index < length) {
    var key = path[index];
    if (isObject(nested)) {
      var newValue = value;
      if (index != lastIndex) {
        var objValue = nested[key];
        newValue = customizer ? customizer(objValue, key, nested) : undefined;
        if (newValue === undefined) {
          newValue = objValue == null ? (isIndex(path[index + 1]) ? [] : {}) : objValue;
        }
      }
      assignValue(nested, key, newValue);
    }
    nested = nested[key];
  }
  return object;
}

Comments

comments powered by Disqus
comments powered by Disqus