Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从JS中的valueOf谈开 #10

Open
renaesop opened this issue Sep 14, 2016 · 0 comments
Open

从JS中的valueOf谈开 #10

renaesop opened this issue Sep 14, 2016 · 0 comments

Comments

@renaesop
Copy link
Owner

renaesop commented Sep 14, 2016

ValueOf是JavaScript中Object原型上少数几个方法之一,应该不能算是很偏门的函数,但是只有《JavaScript权威指南》上面有只言片语的描述,而对ES2015中相应的Symbol.toPrimitive更是鲜有提及。

首先,给出一个结论,valueOftoString几乎都是在出现操作符(+-*/==><)时被调用, 并且valueOf几乎没有什么用。

那么,valueOf的作用是什么呢?按照语言标准上的说法就是,用于toPrimitive需要Number时,而toPrimitive出现的时机,说得简单一点就是,当需要一个Number或者String,但是被传入了一个对象时,就会执行这个操作。有一个常见的用法实际上是使用了valueOf:

将Date对象转换为时间戳时,会很自然的用+ new Date(), 实际上是悄悄地调用了new Date().valueOf()

详细地说,有如下场景,会出现偏好结果是Number的toPrimitive,也就是说valueOf可能被调用(如果没有valueOf的话,可以被toString等替代):

  • ToNumber

具体而言,当obj前后操作符是加法,以及减法、乘法、除法,以及调用Number(obj)以及new Number(obj)时。值得注意的是,parseInt, parseFloat等方法实际上不会调用ToNumber, 他们调用的是偏好String的toPrimitive。举个代码的例子:

class Test {
  valueOf() {
    return 1;
  }
  toString() {
    return '2';
  }
}

const a = new Test();

console.log(parseInt(a, 10)); //  打印出2,也就是toString被调用了
console.log(Number(a));//  打印出1,也就是valueOf被调用了

const obj = {};
obj[a] = 1;
console.log(obj); // 打印出 {‘2’: 1},也就是toString被调用了

const b = 0;
const c = '0';
console.log(a + b); // 打印出 1, 也就是valueOf被调用了
console.log(a + c);// 打印出 10, 也就是还是ValueOf被调用
  • 比较大小

直接上代码:

class Test {
  constructor(val) {
    this.__val = val;
  }
  valueOf() {
    return this.__val;
  }
}

const a = new Test('12');
const b = new Test(2);
console.log(a < b); // 打印出false,说明valueOf被调用,且两者不都是string时,转换为Number
                                // 且如果不能转为Number,则值为NaN

嗯,我们可以得出如下结论,在应该使用“值”,也就是数值的地方,如果出现了对象,就会调用valueOf。

再给出一个例子,证明valueOf不存在时,可以用toString作为替代品

class Test {
  toString() {
    return '2';
  }
}

const a = new Test();

console.log(parseInt(a, 10)); //  打印出2,也就是toString被调用了
console.log(Number(a));//  打印出2,也就是toString被调用了

const obj = {};
obj[a] = 1;
console.log(obj); // 打印出 {‘2’: 1},也就是toString被调用了

const b = 0;
const c = '0';
console.log(a + b); // 打印出 1, 也就是toString被调用了
console.log(a + c);// 打印出 10, 也就是还是toString被调用

但是反过来是不成立的,有的地方必须使用toString, 比如说把对象作为对象的key使用时,以及向parseInt中传入对象时。

到这里,可以得出一个结论,一个对象想要在希望转化为数字的地方,通过给出特殊的valueOf来给出不同于期望转化为字符串的地方的值,最好的例子就是Date对象。

另外,我们频繁提到的toPrimitive这个操作,在ES2015标准中,已经真的添加了这个方法,并且这个方法会比toStringvalueOf的优先级都高,并且嘛,几乎都可以替代这俩货了, 给个例子:

class Test {
  valueOf() {
    return 1;
  }
  toString() {
    return '2';
  }
  [Symbol.toPrimitive](hint) {
    console.log(hint);
    return 3;
  }
}

let a = new Test();

const obj = {};
obj[a] = 1;
console.log(obj); // => string  { '3': 1 }

const b = 0;
const c= '0';
console.log(a + b); // => default 3, default相当于number
console.log(a + c); // => default 30

console.log(parseInt(a, 10)); // => string 3
console.log(Number(a)); // => number 3

上述代码中Symbol.toPrimitive方法可以接收参数,表示期望的类型,就像我们提到的,如果是string,就是期望获得字符串(也就是之前说的调用toString),如果是default或者number则希望获取一个数字(也就是之前说的优先调用valueOf,否则调用toString)。

可以看出,Symbol.toPrimitive是完完全全可以取代掉valueOf,甚至toString

另外,++运算符也可以触发偏好Number的toPrimitive,而且很有意思的是toPrimitive系是内建函数,可以返回左值,所以可以用到对象上:

class Test {
  constructor(val) {
    this.__val = val;
  }
  [Symbol.toPrimitive](hint) {
    return this.__val;
  }
}

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

let b = new Test('2');
b++;
console.log(b); // => 3

let c = '1';
console.log(typeof c) // => 'string'
console.log(++c)
console.log(typeof c) // => 'numer', ++ 确实有转型的作用
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant