Skip to content
登录后刷题更便捷

类型转换的原理是什么?

难度:

类型转换指的是将一种类型转换为另一种类型,例如:

js
var b = 2;
var a = String(b);
console.log(typeof a); //string

当然,类型转换分为显式和隐式,但是不管是隐式转换还是显式转换,都会遵循一定的原理,由于 JavaScript 是一门动态类型的语言,可以随时赋予任意值,但是各种运算符或条件判断中是需要特定类型的,因此 JavaScript 引擎会在运算时为变量设定类型.

这看起来很美好,JavaScript 引擎帮我们搞定了类型的问题,但是引擎毕竟不是 ASI(超级人工智能),它的很多动作会跟我们预期相去甚远,我们可以从一到面试题开始.

js
{
}
+[]; //0

答案是 0

是什么原因造成了上述结果呢?那么我们得从 ECMA-262 中提到的转换规则和抽象操作说起,有兴趣的童鞋可以仔细阅读下这浩如烟海的语言规范,如果没这个耐心还是往下看.

这是 JavaScript 种类型转换可以从原始类型转为引用类型,同样可以将引用类型转为原始类型,转为原始类型的抽象操作为 ToPrimitive,而后续更加细分的操作为:ToNumber ToString ToBoolean。

为了更深入的探究 JavaScript 引擎是如何处理代码中类型转换问题的,就需要看 ECMA-262 详细的规范,从而探究其内部原理,我们从这段内部原理示意代码开始.

js
// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {
  // Fast case check.
  if (IS_STRING(x)) return x;
  // Normal behavior.
  if (!IS_SPEC_OBJECT(x)) return x;
  if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError(kSymbolToPrimitive);
  if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
  return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {
  if (!IS_SYMBOL_WRAPPER(x)) {
    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (IsPrimitive(v)) return v;
    }

    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (IsPrimitive(s)) return s;
    }
  }
  throw MakeTypeError(kCannotConvertToPrimitive);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultString(x) {
  if (!IS_SYMBOL_WRAPPER(x)) {
    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (IsPrimitive(s)) return s;
    }

    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (IsPrimitive(v)) return v;
    }
  }
  throw MakeTypeError(kCannotConvertToPrimitive);
}

上面代码的逻辑是这样的:

如果变量为字符串,直接返回. 如果!IS_SPEC_OBJECT(x),直接返回. 如果 IS_SYMBOL_WRAPPER(x),则抛出异常. 否则会根据传入的 hint 来调用 DefaultNumberDefaultString,比如如果为 Date 对象,会调用 DefaultString. DefaultNumber:首先 x.valueOf,如果为 primitive,则返回 valueOf 后的值,否则继续调用 x.toString,如果为 primitive,则返回 toString 后的值,否则抛出异常 DefaultString:和 DefaultNumber 正好相反,先调用 toString,如果不是 primitive 再调用 valueOf. 那讲了实现原理,这个 ToPrimitive 有什么用呢?实际很多操作会调用 ToPrimitive,比如加、相等或比较操。在进行加操作时会将左右操作数转换为 primitive,然后进行相加。

下面来个实例,({}) + 1(将{}放在括号中是为了内核将其认为一个代码块)会输出啥?可能日常写代码并不会这样写,不过网上出过类似的面试题。

加操作只有左右运算符同时为 StringNumber 时会执行对应的%_StringAdd%NumberAdd,下面看下({}) + 1 内部会经过哪些步骤:

{}1 首先会调用 ToPrimitive {}会走到 DefaultNumber,首先会调用 valueOf,返回的是 Object {},不是 primitive 类型,从而继续走到 toString,返回[object Object],是 String 类型 最后加操作,结果为[object Object]1 再比如有人问你[] + 1 输出啥时,你可能知道应该怎么去计算了,先对[]调用 ToPrimitive,返回空字符串,最后结果为"1"

内容仅供参考,难免有不恰当的地方,如果有问题欢迎及时反馈
部分内容来自网络,如果不慎侵犯您的权益,请联系我们,以便及时删除侵权内容