林秀栋的技术博客

valueOf和toString

隐式转换本质就是调用了 valueOf 或者 toString 方法

toString:toString()函数的作用是返回 object 的字符串表示

如下代码演示(以下演示情况是 toString 方法没有被重写)

let num = 1;
let str = "a";
let bool = true;
let obj = {};
let date = new Date();
let reg = /\d/;
let arr = [1, 2, 3];
let fun = function () {};

console.log(num.toString()); // '1'
console.log(str.toString()); // 'a'
console.log(bool.toString()); // 'true'
console.log(obj.toString()); // '[object Object]'
console.log(date.toString()); // 'Thu Mar 28 2019 17:07:40 GMT+0800 (中国标准时间)'
console.log(reg.toString()); // '/\d/'
console.log(arr.toString()); // '1,2,3'
console.log(fun.toString()); // 'function(){}'

valueOf:valueOf()函数将对象转换为原始值

如下代码演示(以下演示情况是 valueOf 方法没有被重写)

console.log(num.valueOf()); // 1
console.log(str.valueOf()); // 'a'
console.log(bool.valueOf()); // true
console.log(obj.valueOf()); // {}
console.log(date.valueOf()); // 1553766610534
console.log(reg.valueOf()); // /\d/
console.log(arr.valueOf()); // [1, 2, 3]
console.log(fun.valueOf()); // fun()

隐式转换规则

以下为隐式转换时的规则:

字符串连接符和算法运算符混淆

let a = 1;
console.log(a + "1"); // '11'
console.log(a + null); // 1
console.log(a + undefined); // NaN (Number(undefined) = NaN)
console.log(a + true); // 2
console.log(a + {}); // '1[object Object]'
console.log(a + [1, 2, 3]); // '11,2,3'
console.log(a + new Date()); // '1Fri Mar 29 2019 10:12:41 GMT+0800 (中国标准时间)'
console.log(a + /\d/); // '1/\d/'
console.log(a + function () {}); // '1function(){}'

从打印的结果可以知道

在这里我们需要注意的是 null、布尔值和 undefined 这三类对象使用 + 进行操作,当有一边确定为数字的时候,这三类值会尝试用 Number()进行转化,如果有一边类型确定为字符串的时候,直接就是进行字符串相加。

let a = "1";
console.log(a + null); // '1null'
console.log(a + undefined); // '1undefined'
console.log(a + true); // '1true'

关系运算符会把其他数据类型转换成 number 之后再比较关系

console.log("2" > 10); // false
console.log("2" > "10"); // true
console.log("a" > "b"); // false
console.log("ab" > "aa"); // true

从打印的结果可以知道

console.log("a" > "b"); // false
// 'a'.charCodeAt() > 'b'.charCodeAt()
console.log("ab" > "aa"); // true
// 第一位都是a相等,所以比较第二位的 b.charCodeAt() > a.charCodeAt()

复杂数据类型在隐式转换时会先转成 String,然后再转成 Number 运算

复杂类型数据指的是对象或数组这类数据进行隐式转换时,会先调用 valueOf 后调用 toString 方法转化成数据,再调用 Number()转化成数字进行运算。

如果这个对象的 valueOf 方法和 toString 方法被重写过,则会根据 valueOf 返回的数据类型判断是否执行 toString。

let a = {
  valueOf: function () {
    console.log("执行valueOf");
    return "a";
  },
  toString: function () {
    console.log("执行toString");
    return "a";
  },
};
console.log(a == "a");
// 执行valueOf
// true

接下来尝试把 valueOf 返回值改成数字:

let a = {
  valueOf: function () {
    console.log("执行valueOf");
    return 1;
  },
  toString: function () {
    console.log("执行toString");
    return "a";
  },
};
console.log(a == "a");
// 执行valueOf
// false

尝试把 valueOf 返回值改成对象

let a = {
  valueOf: function () {
    console.log("执行valueOf");
    return {};
  },
  toString: function () {
    console.log("执行toString");
    return "a";
  },
};
console.log(a == "a");
// 执行valueOf
// 执行toString
// true

通过上面的例子我们可以得出结论:

这里还有个问题就是如果 toString 方法返回不是基础类型,进行比较的时候则会报错。

逻辑非隐式转换与关系运算符隐式转换混淆

当使用!逻辑非运算符进行转化的时候,会尝试把数据转化成布尔值

以下情况使用 Boolean()转化将会得到 false

0、-0、undefined、null、NaN、false、’‘(空字符串)、document.all

console.log([] == 0); // true
console.log(![] == 0); // true
// [] == 0 --> [].valueOf().toString()得到空字符串,Number('') == 0 成立
// ![] == 0 --> Boolean([])得到true再取反,最后转化成数字0,Number(!true) == 0 成立

console.log([] == ![]); // true
console.log([] == []); // false
// [] == ![] --> [].valueOf().toString()得到空字符串,Number('')取得0,Boolean([])得到true再取反,转化成数字0,最后Number('') == Number(!true) 成立
// [] == [] --> 两个数组比较是因为两个数据的引用指向不一致,所以 [] == [] 不成立

console.log({} == !{}); // false
console.log({} == {}); // false
// {} == !{} --> {}.valueOf().toString()得到'[object Object]',Boolean({})得到true再取反,所以 '[object Object]' == false 不成立
// {} == {} --> 两个对象比较是因为两个数据的引用指向不一致,所以 {} == {} 不成立

最后总结一下,在复杂数据类型隐式转化过程中会调用 valueOf 和 toString 方法,所以如果这两个方法被改写了往往会得到一些意料外的结果。