Skip to content

以下哪些代码执行后 i 的值为 10:

// A. 
    let i = 1 + {
       valueOf() { return 9; }
    };
// B. 
    let i = 0;
    new Array(10).forEach(() => {
       i++;
    });
// C. 
    let i = 5;
    function a(i) {
       i *= 2;
    }
    a(i);

解析:

A. 数字 1 和一个对象相加,会触发 “对象转基本类型” 机制。 由于是加法运算,且该对象重写了 valueOf 方法,那么在对这个对象进行 valueOf 操作时,就转变成 Number 类型,返回 9,再运行 1+9,结果为 10。

B.new Array(10) 创建了数组,长度为 10,但是没有进行初始化赋值,数组的每一项都是 empty,forEach 会自动跳过空元素,也就不会执行回调函数重的操作,因此结果还是 0。

C. 调用 a 函数,传入行参 i,在函数作用域内改变这个行参的值,是不会影响全局中的 i(如果传入的是复杂数据类型,就会影响全局中的变量),因此结果为 5。

答案:A

对象转基本类型

当对象在参加运算时,会触发 “对象转基本类型” 机制。遵循两个原则:

  1. 对象在转换基本类型时,会调用 valueOf 和 toString,并且这两个方法你是可以重写的。
  2. 至于调用哪个方法,是看这个对象倾向于转换成什么,
    • 如果倾向于转换为 Number 类型就会优先调用 valueOf;
    • 如果倾向于转换为 String 类型,就会调用 toString 了。

看看下面这段代码:

let obj = {
    toString () {
      return 'tostring'
    },
    valueOf () {
      return 'valueof'
    }
}
console.log(`abc${obj}`)    // abctostring
console.log(1 + obj)           // 1valueof
console.log('1' + obj)        // 1valueof
console.log(obj + 'abc')        // valueofabc

看到这也许你会有一个疑问,上面说到“至于调用哪个方法,是看这个对象倾向于转换成什么”,在打印'1' + obj 和 obj + 'abc'时,明显我想让这个对象转换成 String 类型进行运算,但是最后却调了 valueOf。 原因在于对于这个 “+” 操作,本身就是代表加法 - 运算,也就是说,本来就想转换为 Number 类型,只不过 “+” 的另一侧是字符串,执行 valueOf 后再把 return 的值也转成字符串。

上面的例子中重写了 toString 和 valueOf 方法,但是如果只重写其中之一,就会只调用重写的那个方法。 需要注意的是,调用 toString 或者 valueOf 时,需要 return 原始类型的值。 如果重写两个方法中,有一个没有返回原始类型的值,就会自动去调另一个方法,如果两个方法都没有返回原始类型的值,就会报错。如:

let obj = {
  toString() {
    console.log("toString");
    return {};
  },
  valueOf() {
    console.log("valueOf");
    return {};
  },
};

console.log(1 + obj);
// Uncaught TypeError: Cannot convert object to primitive value

但是如果有 Symbol.toPrimitive 属性的话,那么会优先调用它。

let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return "1";
  },
  [Symbol.toPrimitive]() {
    return 1;
  },
};

console.log(1 + a); // => 2

并且 Symbol.toPrimitive 也只能返回原始类型的值,不然也会报错。

只有当加法运算时,其中一方是字符串类型,就会把另一方也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。

扩展题

// 请在问号处填写你的答案,使下方等式成立
let a = ?;
if(a == 1 && a == 2 && a == 3) {
    console.log("Hi, I'm Echi");
}
  1. 使用 toString 实现
let a = {
    i: 1,
    toString() {
        return a.i++;
    }
};

2.valueOf 的实现大同小异

let a = {
    i: 1,
    valueOf() {
        return a.i++;
    }
};
  1. 使用 Symbol.toPrimitive 属性实现,该属性会指向一个方法,当对象转换为原始类型的值时,就会调用这个方法,返回对象对应的原始类型值,且该方法在转基本类型时调用优先级最高。
let a = {
    i: 1,
    [Symbol.toPrimitive]() {
        return a.i++;
    }
};