Nodejs源码解析之util

本文将探讨Node.js的util模块,这是一个内部API,用于提供各种工具函数。内容涵盖判断类型、格式化对象和字符串以及对象关系。通过源码分析,我们发现判断类型主要依赖于typeof、instanceof和Object.prototype.toString方法。在对象关系部分,重点讲解了inherits和_extend函数的实现细节。对于格式化,util提供了format和inspect函数,其源码包含复杂的递归处理和特殊场景应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

util是nodejs中的工具类,属于内部API,被nodejs其他模块使用。 可以浏览官方文档:https://nodejs.org/dist/latest-v5.x/docs/api/util.html#util_util_inspect_object_options

其基本的API可以分成以下类型:

  • 判断类型, 返回值为布尔类型,判断一个值是否为特定类型,如isBoolean,isBuffer,isDate等。
  • 格式化字符串,对象
  • 对象关系,如inherits,_extend

分成以上部分进行源码分析:

判断类型

// 判断一个对象是否为布尔类型
function isBoolean(arg) {
  // 使用typeof关键字,如果是boolean就为boolean类型
  return typeof arg === 'boolean';
}

// 导出函数
exports.isBoolean = isBoolean;

// 判断是否为null,
function isNull(arg) {
   // 请注意使用的是 ===
  return arg === null;
}
// 导出函数
exports.isNull = isNull;

// 判断是否为null或者undefined
function isNullOrUndefined(arg) {
  // 注意,使用的是 ==
  return arg == null;
}
// 导出函数
exports.isNullOrUndefined = isNullOrUndefined;

// 是否为数字类型
function isNumber(arg) {
  //注意是 === 
  return typeof arg === 'number';
}
// 导出函数
exports.isNumber = isNumber;

// 是否为string类型
function isString(arg) {
// 使用的依然是typeof类型
  return typeof arg === 'string';
}

// 导出函数
exports.isString = isString;

// 是否为symbol类型
function isSymbol(arg) {
  return typeof arg === 'symbol';
}
// 导出函数
exports.isSymbol = isSymbol;

// 是否为undefined类型
function isUndefined(arg) {
   // 注意,使用的是void 0, 其实可以用typeof。个人理解应该是性能问题
   // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
  return arg === void 0;
}
// 导出isUndefined 
exports.isUndefined = isUndefined;

// 是否为正则表达式
function isRegExp(re) {
  // 是否为object
  // 是否为RegExp
  return isObject(re) && objectToString(re) === '[object RegExp]';
}
// 导出
exports.isRegExp = isRegExp;

// 是否为object对象
function isObject(arg) {
   // typeof对象,需要不是null
  return typeof arg === 'object' && arg !== null;
}
// 导出函数
exports.isObject = isObject;

// 是否为Date
function isDate(d) {

  return isObject(d) && objectToString(d) === '[object Date]';
}
// 导出是否为Date
exports.isDate = isDate;

// 是否为Error对象
function isError(e) {
  return isObject(e) &&
      (objectToString(e) === '[object Error]' || e instanceof Error);
}
// 导出
exports.isError = isError;

// 是否为函数
function isFunction(arg) {
  return typeof arg === 'function';
}
// 导出函数
exports.isFunction = isFunction;

// 是否为原对象
function isPrimitive(arg) {
  // 具体的原对象为
  return arg === null ||
         typeof arg === 'boolean' ||
         typeof arg === 'number' ||
         typeof arg === 'string' ||
         typeof arg === 'symbol' ||  // ES6 symbol
         typeof arg === 'undefined';
}
//导出原子对象
exports.isPrimitive = isPrimitive;

// 是否为Buffer对象
function isBuffer(arg) {
 // 注意这里用的是
  return arg instanceof Buffer;
}
exports.isBuffer = isBuffer;

// 帮组函数或获取对象的表达字符串
function objectToString(o) {
  return Object.prototype.toString.call(o);
}

从上面可以看出,注意使用的是typeof,instanceof, 与Object.prototype.toString方法来完成对象的判断。

格式化对象,字符串

格式化对象,从API上,仅仅导出了两个函数format和 inspect,介绍一个格式,ctx的属性,在API文档中:

showHidden - if true then the object's non-enumerable and symbol properties will be shown too. Defaults to false. 当为true的时候,不可枚举的和symbol的属性也会显示出来,默认的值为false

depth - tells inspect how many times to recurse while formatting the object. This is useful for inspecting large complicated objects. Defaults to 2. To make it recurse indefinitely pass null. 相当于需要多少层的递归format这个对象,这个在解析大的复杂的对象的时候会用到,默认为2, 如果想无限递归,用null。

colors - if true, then the output will be styled with ANSI color codes. Defaults to false. Colors are customizable, see below. 如果为true,将要用颜色模式,默认是false

customInspect - if false, then custom inspect(depth, opts) functions defined on the objects being inspected won't be called. Defaults to true.如果为假,定制的inspect格式将不会在对象上使用,默认为真

/**
 * Echos the value of a value. Trys to print the value out
 * in the best way possible given the different types.
 *
 * @param {Object} obj The object to print out.
 * @param {Object} opts Optional options object that alters the output.
 */
/* legacy: obj, showHidden, depth, colors*/
// 输出一个值,以obj为基础,opts为可选参数。 尝试以不同方式以最好的形式输出值
function inspect(obj, opts) {
  // default options
  // 默认的选项
  var ctx = {
    seen: [],
    stylize: stylizeNoColor
  };
  // legacy...
  // legacy的接口,如果参数大于或者等于3个,第三个参数为深度。
  if (arguments.length >= 3) ctx.depth = arguments[2];
    // legacy的接口,如果参数大于或者等于4个,第四个参数为颜色。
  if (arguments.length >= 4) ctx.colors = arguments[3];
  if (isBoolean(opts)) {
    // legacy...
     // 如果第二个参数为布尔类型,直接就是表示showHidden,上述为是否显示不可枚举的属性
    ctx.showHidden = opts;
  } else if (opts) {
     // 否则,直接是 扩展属性,将扩展属性放入到ctx对象中
    // got an "options" object
    exports._extend(ctx, opts);
  }
  // set default options
  // 加入默认属性值
  // 也就是说,有五种不同的属性类型,四种在API文档中有
  // 在有颜色的哦前提下,加入了stylizeWithColor,详见下面的函数
  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
  if (isUndefined(ctx.depth)) ctx.depth = 2;
  if (isUndefined(ctx.colors)) ctx.colors = false;
  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
  if (ctx.colors) ctx.stylize = stylizeWithColor;
  // 通过formatValue来返回最后的值,有三个参数
  // 分别为ctx,格式符
  // obj为原始需要输出的对象
  // ctx.depth:深度
  return formatValue(ctx, obj, ctx.depth);
}
// 导出函数。
exports.inspect = inspect;

// 以某个颜色格式化字符串
function stylizeWithColor(str, styleType) {
  //从表中获取颜色
  var style = inspect.styles[styleType];
  // 如果有
  if (style) {
  // 生成需要的颜色字符串
    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
           '\u001b[' + inspect.colors[style][1] + 'm';
  } else {
    // 否则,直接原样返回字符串。
    return str;
  }
}
// 格式化字符串
// @ctx 为格式化字符串的具体格式选项
// @value 具体需要被格式的对象
// @recurseTimes  递归的次数
function formatValue(ctx, value, recurseTimes) {
  // Provide a hook for user-specified inspect functions.
  // Check that value is an object with an inspect function on it
  // 检查传入是否有定制化的inspect函数,
  if (ctx.customInspect &&
      value &&
      isFunction(value.inspect) &&
      // Filter out the util module, it's inspect function is special
      value.inspect !== exports.inspect &&
      // Also filter out any prototype objects using the circular check.
      !(value.constructor && value.constructor.prototype === value)) {
    var ret = value.inspect(recurseTimes, ctx);
    // 如果定制化的inspect函数返回值为字符串,字节返回
    // 否则,递归地用,返回字符串
    if (!isString(ret)) {
      ret = formatValue(ctx, ret, recurseTimes);
    }
    return ret;
  }

  // Primitive types cannot have properties
  // 格式化原始对象,如果是原始对象,不会返回null,这里可以直接返回了
  var primitive = formatPrimitive(ctx, value);
  if (primitive) {
    return primitive;
  }

  // Look up the keys of the object.
  // 返回具体的属性关键字。
  var keys = Object.keys(value);
  // 创建关键字的hash表。
  var visibleKeys = arrayToHash(keys);
  // 是否显示隐藏属性
  if (ctx.showHidden) {
    // 如果显示隐藏属性,直接从Object上获取属性关键字
    keys = Object.getOwnPropertyNames(value);
  }

  // This could be a boxed primitive (new String(), etc.), check valueOf()
  // NOTE: Avoid calling `valueOf` on `Date` instance because it will return
  // a number which, when object has some additional user-stored `keys`,
  // will be printed out.
  var formatted;
  var raw = value;
  try {
    // the .valueOf() call can fail for a multitude of reasons
    if (!isDate(value))
      raw = value.valueOf();
  } catch (e) {
    // ignore...
  }
  // 处理是string的情况
  if (isString(raw)) {
    // for boxed Strings, we have to remove the 0-n indexed entries,
    // since they just noisey up the output and are redundant
    // 将没有关键字的属性除掉 如 { '01', '02', test: '03'}; 这样的值会把 01, 02
    //filter 掉
    keys = keys.filter(function(key) {
      return !(key >= 0 && key < raw.length);
    });
  }

  // Some type of object without properties can be shortcutted.
  // 如果最后的关键字为空,相当于是一个原始类型或者简单的对象
  if (keys.length === 0) {
      // 对于是函数的情况,
    if (isFunction(value)) {
       // 直接以输出函数名,和'special'格式输出
      var name = value.name ? ': ' + value.name : '';
      return ctx.stylize('[Function' + name + ']', 'special');
    }
    // 对于正则表达式的情况
    if (isRegExp(value)) {
       // 直接输出正则表达式的输出,以‘regexp’格式
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    }
    //处理日期形式
    if (isDate(value)) {
      return ctx.stylize(Date.prototype.toString.call(value), 'date');
    }
    // 处理错误对象
    if (isError(value)) {
      return formatError(value);
    }
    // now check the `raw` value to handle boxed primitives
    // 处理是字符串的情况
    if (isString(raw)) {
      formatted = formatPrimitiveNoColor(ctx, raw);
      return ctx.stylize('[String: ' + formatted + ']', 'string');
    }
    //处理是数字的情况
    if (isNumber(raw)) {
      formatted = formatPrimitiveNoColor(ctx, raw);
      return ctx.stylize('[Number: ' + formatted + ']', 'number');
    }
    // 处理的布尔值的情况
    if (isBoolean(raw)) {
      formatted = formatPrimitiveNoColor(ctx, raw);
      return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
    }
  }
  // 运行到这,说明对象有很多不同的属性。
  // base是下文中要返回的字符串源,array表示是否为array,braces表示是输出的前后占位符。 
  var base = '', array = false, braces = ['{', '}'];

  // Make Array say that they are Array
  // 对于array的特殊处理
  if (isArray(value)) {
    array = true;
    braces = ['[', ']'];
  }

  // Make functions say that they are functions
  // 处理函数的case, 将函数的base换成[function + ...]
  if (isFunction(value)) {
    var n = value.name ? ': ' + value.name : '';
    base = ' [Function' + n + ']';
  }

  // Make RegExps say that they are RegExps
  // 处理正则表达式的情况
  if (isRegExp(value)) {
    base = ' ' + RegExp.prototype.toString.call(value);
  }

  // Make dates with properties first say the date
  // 处理日期类型
  if (isDate(value)) {
    base = ' ' + Date.prototype.toUTCString.call(value);
  }

  // Make error with message first say the error
  // 处理错误类型,
  if (isError(value)) {
    // 后面分析错误如何格式化
    base = ' ' + formatError(value);
  }

  // Make boxed primitive Strings look like such
  // 如果是字符串的情况
  if (isString(raw)) {
     // 直接输出字符串的格式
    formatted = formatPrimitiveNoColor(ctx, raw);
    base = ' ' + '[String: ' + formatted + ']';
  }

  // Make boxed primitive Numbers look like such
  // 处理数字的情况
  if (isNumber(raw)) {
    formatted = formatPrimitiveNoColor(ctx, raw);
    base = ' ' + '[Number: ' + formatted + ']';
  }

  // Make boxed primitive Booleans look like such
  // 处理布尔类型的情况
  if (isBoolean(raw)) {
    formatted = formatPrimitiveNoColor(ctx, raw);
    base = ' ' + '[Boolean: ' + formatted + ']';
  }
  // 处理对象中没有属性关键字
  // 和 不是数组或者值为空的情况 
  if (keys.length === 0 && (!array || value.length === 0)) {
    return braces[0] + base + braces[1];
  }
  // 检查递归次数,如果小于零,需要返回了
  if (recurseTimes < 0) {
    // 检查正则表达式的情况 
    if (isRegExp(value)) {
      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
    } else {
      // 其他的一律用Object和special的形式输入
      return ctx.stylize('[Object]', 'special');
    }
  }
  // 将要显示的值压入栈
  ctx.seen.push(value);

  //这个就是最终的输出 
  var output;
  if (array) {
    // 处理array队列的情况
    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
  } else {
    // 处理普通对象的情况
    output = keys.map(function(key) {
      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
    });
  }
  // 将要显示的值推出栈,这么用,其实值在formatArray或者formatProperty中使用到了
  // 详情见formatArray和formatProperty的源码解析
  ctx.seen.pop();
  // 处理返回的值
  return reduceToSingleString(output, base, braces);
}

// 简化到格式化Array的情况
//@ctx 格式控制,以及部分当前结果
//@value 需要格式化的值
//@recurseTimes 递归的次数
//@visibleKeys 可见的key值的hash表
//@keys 关键字的队列 
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  var output = [];
  // 处理关键字
  for (var i = 0, l = value.length; i < l; ++i) {
    // 如果是index (0, 1,2....)为关键字的情况
    if (hasOwnProperty(value, String(i))) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
          String(i), true));
    } else {
      // 不是,就没有关键字的输出
      output.push('');
    }
  }
  // 处理值的输出的情况到数组中
  keys.forEach(function(key) {
     // 符合不以数字开头的一个或多个数字且以数字结尾的字符串才处理输出
    if (!key.match(/^\d+$/)) {
      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
          key, true));
    }
  });
  return output;
}

// 简化到格式化对象属性的情况
//@ctx 格式控制,以及部分当前结果
//@value 需要格式化的值
//@recurseTimes 递归的次数
//@visibleKeys 可见的key值的hash表
//@keys 关键字的队列 
//@array 布尔值表示是否输出到队列
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
  // name表示的属性名, str表示的是属性需要表示的值,desc表示的是描述符
  var name, str, desc;
  // 获取属性描述符
  desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
  // 日过有属性get的描述函数
  if (desc.get) {
     // 处理有get和set的描述函数的情况
    if (desc.set) {
      str = ctx.stylize('[Getter/Setter]', 'special');
    } else {
    // 处理有get的描述函数的情况
      str = ctx.stylize('[Getter]', 'special');
    }
  } else {
     //处理只有set的情况
    if (desc.set) {
      str = ctx.stylize('[Setter]', 'special');
    }
  }
  // 获取的属性名字,
  if (!hasOwnProperty(visibleKeys, key)) {
    name = '[' + key + ']';
  }
  // 如果str没有的情况,也就是没有get set属性的情况
  if (!str) {
    if (ctx.seen.indexOf(desc.value) < 0) {
      // 如果没有递归的情况了
      if (isNull(recurseTimes)) {
        str = formatValue(ctx, desc.value, null);
      } else {
        // 有递归情况下的处理,只在递归次数下降
        str = formatValue(ctx, desc.value, recurseTimes - 1);
      }

      if (str.indexOf('\n') > -1) {
        if (array) {
          str = str.split('\n').map(function(line) {
            return '  ' + line;
          }).join('\n').substr(2);
        } else {
          str = '\n' + str.split('\n').map(function(line) {
            return '   ' + line;
          }).join('\n');
        }
      }
    } else {
      str = ctx.stylize('[Circular]', 'special');
    }
   //处理没有属性关键字的情况 
  if (isUndefined(name)) {
    //处理队列情况,关键字是数字的,直接返回
    if (array && key.match(/^\d+$/)) {
      return str;
    }
    // 关键字处理
    name = JSON.stringify('' + key);
    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
      name = name.substr(1, name.length - 2);
      name = ctx.stylize(name, 'name');
    } else {
      name = name.replace(/'/g, "\\'")
                 .replace(/\\"/g, '"')
                 .replace(/(^"|"$)/g, "'")
                 .replace(/\\\\/g, '\\');
      name = ctx.stylize(name, 'string');
    }
  }
  // 处理返回
  return name + ': ' + str;
}

// 帮助函数,将array换成hash表。 
// 其实就是一个对象, hash对象。对象的关键字为array里的value
function arrayToHash(array) {

  // 默认的返回对象
  var hash = {};

  array.forEach(function(val, idx) {
    // 将返回对象中增加以值为关键字的属性,值为true
    hash[val] = true;
  });
   // 返回 hash对象
  return hash;
}

可以看出,源码比较复杂,很多递归和特殊情况的处理与使用。这个再介绍一个format函数的源码:

// 需要处理的正
var formatRegExp = /%[sdj%]/g;
exports.format = function(f) {
  // 如果首个参数不是字符串的情况
  if (!isString(f)) {
  // 直接以数组的形式阁书画字符串。
    var objects = [];
    for (var i = 0; i < arguments.length; i++) {
      objects.push(inspect(arguments[i]));
    }
    // 最后以 空格的形式连接起来字符串
    return objects.join(' ');
  }

  // 处理有
  var i = 1;
  var args = arguments;
  var len = args.length;
  // 处理有格式输出符的情况,基本支持的是 %s, %d, %j 以及%%
  var str = String(f).replace(formatRegExp, function(x) {
    if (x === '%%') return '%';
    if (i >= len) return x;
    switch (x) {
      case '%s': return String(args[i++]);
      case '%d': return Number(args[i++]);
      case '%j':
        try {
          return JSON.stringify(args[i++]);
        } catch (_) {
          return '[Circular]';
        }
      default:
        return x;
    }
  });
  // 对剩余的参数进行处理连接。
  for (var x = args[i]; i < len; x = args[++i]) {
    if (isNull(x) || !isObject(x)) {
      str += ' ' + x;
    } else {
      str += ' ' + inspect(x);
    }
  }
  //返回格式字符串
  return str;
};

对象关系

主要有两个函数,请看如下源代码:

// 继承一个函数的原始方法
exports.inherits = function(ctor, superCtor) {
  // super_ 属性为超类的方法
  ctor.super_ = superCtor;
  // 重新定义原型
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};
// 扩展属性, 将add中的是属性,全部加入到origin中
exports._extend = function(origin, add) {
  // Don't do anything if add isn't an object
  // 判断是否为Object对象,如果不是Object对象,直接返回
  if (!add || !isObject(add)) return origin;
  // 等到object下所有的关键字
  var keys = Object.keys(add);
  var i = keys.length;
  // 将add中的属性全部加入到origin中
  while (i--) {
    origin[keys[i]] = add[keys[i]];
  }
  // 返回origin
  return origin;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值