초기화 함수 init(3)

김대웅·2021년 7월 13일
0

express 분석

목록 보기
7/14

express/application/init

app.init = function init() {
  this.cache = {};
  this.engines = {};
  this.settings = {};

  this.defaultConfiguration();
};

/**
 * Initialize application configuration.
 * @private
 */

app.defaultConfiguration = function defaultConfiguration() {
  var env = process.env.NODE_ENV || "development";
  
  // default settings
  this.enable("x-powered-by");
  this.set("etag", "weak");
  this.set("env", env);
  this.set("query parser", "extended");
  this.set("subdomain offset", 2);
  this.set("trust proxy", false);

  /**
   * settings개체에 기본 값을 설정한다.
   *
   * x-powered-by : true
   * etag : "weak"
   * etag fn : ETag생성 함수
   * env : "development"
   * query parser : "extended"
   * query parser fn : 함수
   * subdomain offset : 2
   * trust proxy : false
   * trust proxy fn : 함수
   */

  // trust proxy inherit back-compat
  Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
    configurable: true,
    value: true,
  });

  debug("booting in %s mode", env);

  this.on("mount", function onmount(parent) {
    // inherit trust proxy
    if (
      this.settings[trustProxyDefaultSymbol] === true &&
      typeof parent.settings["trust proxy fn"] === "function"
    ) {
      delete this.settings["trust proxy"];
      delete this.settings["trust proxy fn"];
    }

    // inherit protos
    setPrototypeOf(this.request, parent.request);
    setPrototypeOf(this.response, parent.response);
    setPrototypeOf(this.engines, parent.engines);
    setPrototypeOf(this.settings, parent.settings);
  });

  // setup locals
  this.locals = Object.create(null);

  // top-most app is mounted at /
  this.mountpath = "/";

  // default locals
  this.locals.settings = this.settings;

  // default configuration
  this.set("view", View);
  this.set("views", resolve("views"));
  this.set("jsonp callback name", "callback");

  if (env === "production") {
    this.enable("view cache");
  }

  Object.defineProperty(this, "router", {
    get: function () {
      throw new Error(
        "'app.router' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app."
      );
    },
  });
};

express/application/set

app.set = function set(setting, val) {
  /**
   * setting : settings의 key 값으로 사용
   * val : settings 변수의 value 값으로 사용
   */

  if (arguments.length === 1) {
    // app.get(setting)
    return this.settings[setting];
    /**
     * set함수에 입력된 인자의 개수가 하나인경우 입력된 인자를 키로하여 저장된 값을 리턴한다.
     */
  }

  debug('set "%s" to %o', setting, val);

  // set value
  this.settings[setting] = val;
  /**
   * settings에 값을 설정한다.
   */

  // trigger matched settings
  switch (setting) {
    case "etag":
      this.set("etag fn", compileETag(val));
      break;
    case "query parser":
      this.set("query parser fn", compileQueryParser(val));
      break;
    case "trust proxy":
      this.set("trust proxy fn", compileTrust(val));

      // trust proxy inherit back-compat
      Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
        configurable: true,
        value: false,
      });

      break;
  }

  /**
   * setting값이 etag, query parser, trust proxy인경우
   * val값을 기반으로 etag fn, query parser fn, truest proxy fn값을 설정한다.
   * 이때 setting값이 trust proxy인경우 프로퍼티를 하나 더 추가한다.
   * "@@symbol:trust_proxy_default" : true
   *
   * 이때 @@symbol:trust_proxy_default의 경우 Object.defineProperty로 추가하였기 때문에 enumerable이 기본값인 false로 설정되어있다.
   */

  return this;
};

express/utils/compileQueryParser

/**
 * val의 타입이 함수인경우 해당 함수를 그대로 리턴하고,
 * true simple => querystring.parse
 * false => newObject
 * extended => parseExtendedQueryString
 * 리턴한다.
 *
 * parseExtendedQueryString에서 사용하는 쿼리 파싱 라이브러리가
 * querystring라이브러리와 다르다
 */

exports.compileQueryParser = function compileQueryParser(val) {
  var fn;

  if (typeof val === "function") {
    return val;
  }

  switch (val) {
    case true:
      fn = querystring.parse;
      break;
    case false:
      fn = newObject;
      break;
    case "extended":
      fn = parseExtendedQueryString;
      break;
    case "simple":
      fn = querystring.parse;
      break;
    default:
      throw new TypeError("unknown value for query parser function: " + val);
  }

  return fn;
};

express/utils/parseExtendedQueryString

/**
 * qs를 사용하는데 굳이 해당 라이브러리를 사용하는 이유가 무었일까
 * npm 페이지에서는 보안에 좀 더 신경을 썼다고 나와 있긴 하다.
 *
 * allowPrototypes의 기본값은 false이다.
 *
 * qs라이브러리의 parse함수의 경우 기존에 가지고 있던 프로퍼티에 파싱된 값을 덮어쓰지 않는다.
 * 예) "a[hasOwnProperty]=b" -> hasOwnProperty의 경우 Object.create(null)을 통하여 만들어진 개체를 제외한 모든 개체가 가지고 있는 프로퍼티이므로 hasOwnProperty=b로 덮어쓰지 않는다.
 *
 * allowPrototypes: true인 경우 프로퍼티를 덮어쓴다.
 * {a: { hasOwnProperyt: 'b'}} === qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
 *
 * express에서 파싱된 쿼리는 값으로써 사용되므로, 또한 클라이언트가 충분히 a[hasOwnProperty]=b 형태의 값을 보낼수 있으므로(개체의 hasOwnProperty프로퍼티에 함수가있는것은 js의 특징인데 백엔드 코드가 모두 js로 되어있지는 않으므로)
 * 기존의 프로퍼티를 덮어쓴다는 위험성에도 불구하고 allowPrototypes : true 옵션을 준것 같다.
 *
 * 반대로 말한다면 굳이 qs라이브러리를 사용한 이유는 queryString이 기존의 프로퍼티에 값을 덮어쓰지 않기 때문이라고 추측할수 있지 않을까 싶다.
 */

/**
 * 실제 테스트
 *
 * console.log(parser('hasOwnProperty=hasOwnProperty', { allowPrototypes: false })); -> {}
 *
 * console.log(qs.parser('hasOwnProperty=hasOwnProperty', { allowPrototypes: true })); -> { hasOwnProperty: 'hasOwnProperty' }
 *
 * console.log(queryString.parse('hasOwnProperty=hasOwnProperty')); -> [Object: null prototype] { hasOwnProperty: 'hasOwnProperty' }
 *
 * [Object: null prototype]의 의미
 *
 * Object.create(null)로 개체를 만들었다는 의미
 * 내장 함수가 없다.
 *
 * 결론
 *
 * 1. qs를 디폴트로 사용하는 이유
 * 파싱후 얻은 개체 사용시 내장 메서드를 사용하기 위해
 *
 * 2. allowPrototypes : true로 설정한 이유
 * 쿼리의 키값이 내장 매서드와 겹치는 경우 메서드를 쿼리의 벨류로 대체하기 위해
 *
 */

function parseExtendedQueryString(str) {
  return qs.parse(str, {
    allowPrototypes: true,
  });
}

express/utils/newObject

/**
 * Return new empty object.
 *
 * @return {Object}
 * @api private
 */

function newObject() {
  return {};
}

qs/lib/parse

'use strict';

var utils = require('./utils');

var has = Object.prototype.hasOwnProperty;
var isArray = Array.isArray;

var defaults = {
    allowDots: false,
    allowPrototypes: false,
    allowSparse: false,
    arrayLimit: 20,
    charset: 'utf-8',
    charsetSentinel: false,
    comma: false,
    decoder: utils.decode,
    delimiter: '&',
    depth: 5,
    ignoreQueryPrefix: false,
    interpretNumericEntities: false,
    parameterLimit: 1000,
    parseArrays: true,
    plainObjects: false,
    strictNullHandling: false,
};

var interpretNumericEntities = function (str) {
    /**
     * () : 그룹을 표현함
     * \d : 숫자를 의미함
     * + : 반복을 표현함 d문자가 한번 이상 반복됨을 의미한다.
     */
    /**
     * { 형태의 문자열을 변경한다.
     *
     * fromCharCode = UTF-16 코드를 문자열로 변경한다.
     *
     * 예: &#123 -> {
     *
     */
    /**
     * 문제점 : &의 구분자로써 사용이 된다. 따라서 val에 {형태로 값이 들어올 가능성은 전무하다.
     */
    return str.replace(/&#(\d+);/g, function ($0, numberStr) {
        return String.fromCharCode(parseInt(numberStr, 10));
    });
};

/**
 * options.comma가 true인경우 ','가 입력되어 있는 문자열을 배열로 바꾼다.
 */
var parseArrayValue = function (val, options) {
    if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
        return val.split(',');
    }

    return val;
};

// This is what browsers will submit when the ✓ character occurs in an
// application/x-www-form-urlencoded body and the encoding of the page containing
// the form is iso-8859-1, or when the submitted form has an accept-charset
// attribute of iso-8859-1. Presumably also with other charsets that do not contain
// the ✓ character, such as us-ascii.
var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')

// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.
var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')

var parseValues = function parseQueryStringValues(str, options) {
    var obj = {};
    var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
    /**
     * 쿼리의 시작점으로 사용되는 기호 포함 여부를 확인하여
     * 파싱할 문자열을 결정한다.
     *
     * express를 구동시키고 프리픽스테스트용 쿼리(?te?st=123)를 보냈을 때 파싱된 값  = { 'te?st': '123' }
     * qs에 동일한 쿼리(?te?st=123)를 보냈을 때 파싱된 값 = { '?te?st': '123' }
     *
     * express에서 입력받은 url에서 쿼리 부분만 따로 파싱할때 맨 처음 구분자(?)를 없애고(에: te?st=123) 주는걸까?
     */
    var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
    /**
     * 파라미터 제한이 무한대이면 limit값을 설정하지 않는다.
     *
     * 최대 url길이
     *
     * RFC2616 3.2.1
     * The HTTP protocol does not place any a priori limit on the length of a URI.
     * Servers MUST be able to handle the URI of any resource they serve, and SHOULD be able to handle URIs of unbounded length if they provide GET-based forms that could generate such URIs.
     * A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle (see section 10.4.15).
     *
     * => 제한되어 있지 않음 다만, 서버가 다룰수 있는 최대 길이를 넘어가는경우 414에러를 리턴하여야함
     *
     * RFC7230
     *
     * Various ad hoc limitations on request-line length are found in practice.
     * It is RECOMMENDED that all HTTP senders and recipients support, at a minimum, request-line lengths of 8000 octets.
     *
     * => 최소 8000옥텟(=8비트)
     *
     * 실세계의 브라우저은 2000자가 넘어갈 경우 제대로 작동하지 않을수 있다.(2006년 기준)
     *
     * 크롬의 최대 url 길이 = 32779
     *
     * https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers 참고
     *
     * url의 길이는 RFC문서상 제한이 없지만, 호스트명의 경우 256 characters로 제한된다(DNS 제약 때문)
     *
     * url의 길이는 대부분 브라우저와 서버에 따라 다르다
     *
     * node.js에 설정되어 있는 기본 길이는 80kb(Request header + URL)를 넘을 수 없다.
     *
     * -DHTTP_MAX_HEADER_SIZE=<value> 옵션을 통해 바꿀수 있는것 같다.
     *
     * https://github.com/nodejs/http-parser/blob/main/http_parser.h#L55 참고
     *
     */
    var parts = cleanStr.split(options.delimiter, limit);
    /**
     * delimiter : "&"이므로 get query를 qs에서 파싱하는것 같다.
     * limit: 최대 분할 개수
     */
    var skipIndex = -1; // Keep track of where the utf8 sentinel was found
    var i;

    var charset = options.charset;
    if (options.charsetSentinel) {
        for (i = 0; i < parts.length; ++i) {
            if (parts[i].indexOf('utf8=') === 0) {
                /**
                 * indexof : 지정된 요소를 찾은 뒤 첫번째 인덱스를 반환한다.
                 * query가 utf8=로 시작하는 경우만 체크한다.
                 */
                if (parts[i] === charsetSentinel) {
                    charset = 'utf-8';
                } else if (parts[i] === isoSentinel) {
                    charset = 'iso-8859-1';
                }
                skipIndex = i;
                /**
                 * charset을 저장하고 해당 인덱스의 값은 무시한다.
                 */
                i = parts.length; // The eslint settings do not allow break;
            }
        }
    }
    /**
     * charsetSentinel의 값은 false이므로 기본세팅에서는 작동하지 않음
     *
     * 기본 인코딩 설정은 옵션에 설정되어 있는 값(기본값: "utf-8")으로 하고
     * charsetSentinel이 설정되어 있는경우 실제 인코딩 여부를 확인한다.
     */

    for (i = 0; i < parts.length; ++i) {
        if (i === skipIndex) {
            /**
             * i의 경우 기본값 -1이고, utf8= 형식의 쿼리가 발견되었을 경우
             * 해당 쿼리를 무시하는 용도로써 사용된다.
             */
            continue;
        }
        var part = parts[i];

        var bracketEqualsPos = part.indexOf(']=');
        var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
        /**
         * 굳이 breaketEqualsPos를 찾은뒤 pos를 찾는 이유
         *
         * test[te=st]=123인경우 첫번째 =의 위치가 아닌 두번째 =의 위치를 찾기 위하여
         * 하지만 test[te=st] =123인 경우 첫번째 =의 위치를 찾는다.
         */

        var key, val;
        if (pos === -1) {
            /**
             * =가 없는경우 입력된 값은 key로 설정한다.
             * val의 경우 strictNullHandling이 ture인 경우 null로 설정을 하고 아닌경우 빈 문자열로 설정한다.
             */
            key = options.decoder(part, defaults.decoder, charset, 'key');
            /**
             * options.decoder = utils.decode
             *
             * utils.decode 매개변수는 3개를 받는다. 마지막 인자는 key인지 value인지 구분하기위해 사용하는것 같다.
             * 두번째 인자의 경우 사용하지 않는다. 왜 입력을 받는것일가?
             */
            /**
             * 쿼리에서 '+'는 ' '으로 대체된다.
             *
             * utf-8의 경우 js라이브러리 함수를 통하여 디코딩한다.
             *  -> 디코딩에 실패한경우 '+'를 ' '로 대체한 문자열을 리턴한다.
             *
             * iso-8859-1인경우 이스케이프 문자만 대체하여 리턴한다.
             */
            val = options.strictNullHandling ? null : '';
        } else {
            key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
            /**
             * '='가 있는경우 '='전까지를 키로 하여 키로 설정한다.
             */
            val = utils.maybeMap(parseArrayValue(part.slice(pos + 1), options), function (encodedVal) {
                return options.decoder(encodedVal, defaults.decoder, charset, 'value');
            });
            /**
             * maybeMap
             *
             * val이 배열인 경우 각각의 값을 배열에 담아 리턴하고
             * 배열이 아닌경우 그냥 리턴한다.
             * 이때 리턴되는 val은 decoder에 의해 디코딩되어 리턴된다.
             *
             */
            /**
             * parseArrayValue
             *
             * options.comma가 true인경우 ','가 입력되어 있는 문자열을 배열로 바꾼다.
             *
             * express에서 options.comma는 false이므로 ','가 있다고 하더라고 배열로 바꾸지 않는다.
             *
             * ?test=1,2,3,4,5 -> { test: '1,2,3,4,5' }
             */
        }

        if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
            /**
             * express 기본세팅 사용시 해당 조건으로 들어오지 않는다.
             */
            val = interpretNumericEntities(val);
        }

        if (part.indexOf('[]=') > -1) {
            val = isArray(val) ? [val] : val;
            /**
             * key가 []=형태일때 val가 배열이면 배열을 배열로 감싼다.
             *
             * 왜?
             *
             * npm i로 설치한 경우 해당 코드가 없다.
             */

            /**
             * ?test[key]=abc,abc 이런 형태도 포함하는줄 알았다.
             *
             * ?test[]=abc,abc 이런 형태만 의미하는것 같다.
             *
             * -> test[]가 test 배열의 의미로 사용한 것 같다.
             *
             * -> 키가 test[] 형태이고 실제 갑이 여러개 들어와 있으면 배열로 만든다.
             */
            /**
             * console.log(qs.parse("test=1,2,3,4,5")); -> { test: '1,2,3,4,5' }
             * console.log(qs.parse("test[]=1,2,3,4,5")); -> { test: [ '1,2,3,4,5' ] }
             * console.log(qs.parse("test=1,2,3,4,5", { comma: true })); -> { test: [ '1', '2', '3', '4', '5' ] }
             * console.log(qs.parse("test[]=1,2,3,4,5", { comma: true })); -> { test: [ [ '1', '2', '3', '4', '5' ] ] } <- 이 차이가 npm으로 설치했을 때 이 코드가 없는 이유를 설명하는 것 같다.
             */
        }

        if (has.call(obj, key)) {
            obj[key] = utils.combine(obj[key], val);
            /**
             * combine = [].concat(a, b);
             * obj[key]와 val을 합쳐 배열로 만든다.
             */
            /**
             * hasOwnProperty=123 형식인 경우 이곳으로 들어올거라 생각하였음
             * 하지만 들어오지 않았음
             *
             * 기본적으로 있는 프로퍼티를 처리하는 로직인줄 알았음
             *
             * test=123&test=abc 이런 형태 파싱에 필요한 구문임
             * -> test : [123, abc]
             */
        } else {
            obj[key] = val;
        }
        /**
         * obejct가 key에 해당하는 프로퍼티를 가지고 있는지 여부
         */
    }

    return obj;
};

var parseObject = function (chain, val, options, valuesParsed) {
    /**
     * valuesParsed : typeof str === 'string'
     *
     * 문자열인경우 parseValues를 통해 파싱
     * 아닌경우 parseArrayValue를 통해 파싱한다.
     *
     * 하지만 parseArrayValue의 경우 typeof === 'string'이 아닌경우 작동하지 않고 값을 그대로 넘겨준다.
     * 의미없는 코드가 아닌가 싶다.
     */
    var leaf = valuesParsed ? val : parseArrayValue(val, options);

    /**
     * chain == parseKey를 통해 얻은 키들의 배열
     * chain을 역순회한다.
     *
     * 역순회하는 이유
     *
     *  asb.abc[123]=123 -> { 'asb.abc': { '123': '123' } } 이므로 역순회가 더 유리함
     *
     */
    /**
     * var leaf = valuesParsed ? val : parseArrayValue(val, options);
     *
     * var obj;
     *
     * leaf = obj;
     *
     * 키값을 순회하며 value를 key의 형식에 맞게 지속적으로 감싸준다.
     */
    for (var i = chain.length - 1; i >= 0; --i) {
        var obj;
        var root = chain[i];

        if (root === '[]' && options.parseArrays) {
            obj = [].concat(leaf);
            /**
             * key가 '[]'배열을 의미하는 경우
             * value를 감싼다.
             */
        } else {
            obj = options.plainObjects ? Object.create(null) : {};
            /**
             * obj를 조건에 맞게 개체로 만든다.
             */
            var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
            /**
             * 키가 '[값]' 형식이면 앞 뒤의 괄호를 없애준다.
             */
            var index = parseInt(cleanRoot, 10);
            /**
             * 키를 숫자로 파싱한다.
             */
            if (!options.parseArrays && cleanRoot === '') {
                /**
                 * 키가 []이고 !parseArrays인경우 개체에 프로퍼티 0으로 저장한다.
                 */
                obj = { 0: leaf };
            } else if (!isNaN(index) && root !== cleanRoot && String(index) === cleanRoot && index >= 0 && options.parseArrays && index <= options.arrayLimit) {
                /**
                 * 키가 [숫자] 형태이고 다른 옵션을 어기지 않는경우
                 * 배열로 만들고 저장한다.
                 */
                obj = [];
                obj[index] = leaf;
            } else {
                obj[cleanRoot] = leaf;
                /**
                 * 그 이외의 경우 개체의 프로퍼티로 저장한다.
                 */
            }
        }

        leaf = obj;
    }

    return leaf;
};

var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
    if (!givenKey) {
        return;
        /**
         * 키가 없으면 함수를 종료한다.
         */
    }

    // Transform dot notation to bracket notation
    var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
    /**
     * /\.([^.[]+)/g
     *
     * \. -> .을 만나는 경우
     * () -> 괄호 안의 조건을 만족하는 경우로 그룹화 한다.
     * [^] -> 괄호 안에 있는 경우를 제외한 모든 경우
     * + -> 1개 이상 만나는 경우 모두 선택
     *
     * /\.([^.[]+) -> .을 만나는 경우 . 또는 [이 나오기 전까지 그룹화 한다.
     *
     * console.log(qs.parse("asb.abc[123]=123&sdf.sdfs=fas", {allowDots: true})); -> asb[abc][123], sdf[sdfs] -> { asb: { abc: { '123': '123' } }, sdf: { sdfs: 'fas' } }
     * console.log(qs.parse("asb.abc[123]=123")); -> asb.abc[123] -> { 'asb.abc': { '123': '123' } }
     *
     */

    // The regex chunks

    var brackets = /(\[[^[\]]*])/;
    /**
     * () -> 그룹을 캡쳐한다.
     * \[^ -> '['를 만나는 경우
     * [^] -> 괄호 안의 경우와 맞지 않은경우 검사한다.
     * [\] -> "[[]]"인 경우 겉의 괄호를 무시한다.
     *
     * abc[123] -> [123]을 선택한다.
     * abc[123[def]] -> [def]를 선택한다.
     *
     * console.log(qs.parse("[abc[def]]=123")); -> { '[abc': { def: '123' } }
     */
    var child = /(\[[^[\]]*])/g;
    /**
     * brakets의 경우 가장 처음 경우를 찾고 child는 모든 경우를 찾음
     */

    // Get the parent

    var segment = options.depth > 0 && brackets.exec(key);
    /**
     * RegExp.prototype.exec() 메서드는 주어진 문자열에서 일치 탐색을 수행한 결과를 배열 혹은 null로 반환한다.
     */
    var parent = segment ? key.slice(0, segment.index) : key;

    // Stash the parent if it exists

    var keys = [];
    if (parent) {
        // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties
        if (!options.plainObjects && has.call(Object.prototype, parent)) {
            if (!options.allowPrototypes) {
                return;
            }
        }
        /**
         * plainObjects가 false이고 allowPrototypes가 false인경우 parent에 해당하는 프로토 타입이 있는경우
         * 파싱을 종료한다.
         */

        keys.push(parent);
        /**
         * parent 키를 keys에 넣는다.
         */
    }

    // Loop through children appending to the array until we hit depth

    var i = 0;
    while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {
        /**
         * segment를 찾은경우 && options.depth 조건을 만족하는 경우 반복문을 진행시킨다.
         */
        /**
         * child.exec()의 경우 조건에 맞는 문자열을 리턴하고 child내부의 lastIndex를 업데이트하므로
         * 조건에 맞는 다음값을 얻기위해 key값을 변경할 필요가 없다.
         */
        /**
         * 정규표현식이 /quick\s(brown).+?(jumps)/ig 이고 찾은 문자열이 'The Quick Brown Fox Jumps Over The Lazy Dog'인경우
         * segment[0] -> "Quick Brown Fox Jumps" 일치한 전체 문자
         * segment[1] -> "Brown"
         * segment[2] -> "Jumps"
         *
         * segment[1] - segment[n] 괄호로 감싼 부분 '()'
         *
         * 0번 인덱스와 1번 인덱스가 다를 경우가 있기 때문에 1번 인덱스의 값을 가져온다.
         */
        i += 1;
        if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
            /**
             * segment[1].slice(1, -1) : "[def]" -> "def"괄호를 벗겨낸다.
             */
            if (!options.allowPrototypes) {
                return;
            }
        }
        keys.push(segment[1]);
    }

    // If there's a remainder, just add whatever is left

    if (segment) {
        /**
         * depth때문에 얻지 못한 키값
         *
         * console.log(qs.parse("abc[def][ghi][123]=asdg", {depth:1})) -> key.slice(segment.index) : "[ghi][123]" -> "[" + key.slice(segment.index) + "]" : "[[ghi][123]]"
         *
         * parseObject에서 앞뒤 대괄호가 있는경우 해당 대괄호를 없애기 때문
         * [ghi][123]으로 들어가면 ghi][123 형태로 나오게 됨
         */
        keys.push('[' + key.slice(segment.index) + ']');
    }

    return parseObject(keys, val, options, valuesParsed);
};

var normalizeParseOptions = function normalizeParseOptions(opts) {
    if (!opts) {
        return defaults;
    }

    /**
     * parser에 str만 들어오고 opts가 들어오지 않은경우
     * 기본 옵션으로 설정한다.
     */

    if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') {
        throw new TypeError('Decoder has to be a function.');
    }
    /**
     * decoder가 함수가 아닌경우 에러를 리턴한다.
     */

    if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
        throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
    }
    /**
     * 문자셋은 utf-8이거나 iso-8859-1 undefined이어야 한다.
     *
     * iso-8859-1 : 첫 256개의 유니코드 문자를 나타낼 수 있는 1바이트 인코딩
     *
     * utf-8 iso-8859-1 모두 ascii를 같은 방식으로 인코딩한다.
     */
    var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;
    /**
     * charset이 undefined인 경우 utf-8로 설정한다.
     */

    /**
     * 조건에 따라 defaults와 opts중 적절한 값을 선택한다.
     */
    return {
        allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
        allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,
        allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,
        arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,
        charset: charset,
        charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
        comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,
        decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,
        delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,
        // eslint-disable-next-line no-implicit-coercion, no-extra-parens
        depth: typeof opts.depth === 'number' || opts.depth === false ? +opts.depth : defaults.depth,
        ignoreQueryPrefix: opts.ignoreQueryPrefix === true,
        interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,
        parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
        parseArrays: opts.parseArrays !== false,
        plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
        strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling,
    };
};

module.exports = function (str, opts) {
    var options = normalizeParseOptions(opts);
    /**
     * {
     *  allowDots: false,
     *  allowPrototypes: true,
     *  arrayLimit: 20,
     *  charset: 'utf-8',
     *  charsetSentinel: false,
     *  comma: false,
     *  decoder: [Function: decode],
     *  delimiter: '&',
     *  depth: 5,
     *  ignoreQueryPrefix: false,
     *  interpretNumericEntities: false,
     *  parameterLimit: 1000,
     *  parseArrays: true,
     *  plainObjects: false,
     *  strictNullHandling: false
     * }
     *
     * opts = { allowPrototypes : true };인 경우 options세팅값
     */

    if (str === '' || str === null || typeof str === 'undefined') {
        return options.plainObjects ? Object.create(null) : {};
        /**
         * 기본값의 경우 false이므로 빈 개체({})를 리턴한다.
         */

        /**
         * {}와 Object.create(null)의 차이
         *
         * {}.constructor.prototype === Object.prototype이지만
         * Object.create(null)의 경우 아무것도 상속받지 않는다.
         *
         * {} === Object.create(Object.prototype)을 의미함
         */
    }
    /**
     * str에 빈 문자열 null undefined가 들어온 경우
     */

    var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
    /**
     * 문자열인경우 쿼리를 개체로 만든다.
     */
    var obj = options.plainObjects ? Object.create(null) : {};
    /**
     * plainObjects 옵션이 설정되어 있는 경우 prototype이 null인 배열을 리턴한다.
     */

    // Iterate over the keys and setup the new object

    var keys = Object.keys(tempObj);
    /**
     * tempObj가 숫자인경우 keys = []; 빈 배열을 리턴한다.
     */
    for (var i = 0; i < keys.length; ++i) {
        var key = keys[i];
        var newObj = parseKeys(key, tempObj[key], options, typeof str === 'string');
        /**
         * parseKeys -> allowDots이 설정되어 있지 않은 경우 "[]" allowDots이 설정되어 있는 경우 "." "[]"을 기준으로 key를 잘라서 배열에 저장한다.
         * 그 뒤 parseObject함수를 부른다.
         */
        obj = utils.merge(obj, newObj, options);
    }

    if (options.allowSparse === true) {
        return obj;
    }

    return utils.compact(obj);
};
profile
42seoul cadet

0개의 댓글