以下哪些代码执行后 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
对象转基本类型
当对象在参加运算时,会触发 “对象转基本类型” 机制。遵循两个原则:
- 对象在转换基本类型时,会调用 valueOf 和 toString,并且这两个方法你是可以重写的。
- 至于调用哪个方法,是看这个对象倾向于转换成什么,
- 如果倾向于转换为 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");
}
- 使用 toString 实现
let a = {
i: 1,
toString() {
return a.i++;
}
};
2.valueOf 的实现大同小异
let a = {
i: 1,
valueOf() {
return a.i++;
}
};
- 使用 Symbol.toPrimitive 属性实现,该属性会指向一个方法,当对象转换为原始类型的值时,就会调用这个方法,返回对象对应的原始类型值,且该方法在转基本类型时调用优先级最高。
let a = {
i: 1,
[Symbol.toPrimitive]() {
return a.i++;
}
};