一文看懂JS里隐式转换、toString() 和 valueOf()

你越是认真生活,你的生活就会越美好——弗兰克·劳埃德·莱特
《人生果实》经典语录

js隐式类型转换

JavaScript数据类型非常弱弱类型语言)在使用算术运算符时,运算符两边数据类型可以任意,比如,一个字符串可以和数字相加。

之所以不同的数据类型之间可以做运算,是因为JavaScript 引擎运算之前会悄悄的把他们进行了隐式类型转换

数值类型和布尔类型的相加

5 + true // 6

结果是一个数值类型

上面的运算不会因为运算符两边的数据类型不一致而导致报错,在 JavaScript 中,只有少数情况下,错误类型才会导致出错,比如调用非函数,或者读取 null 或者 undefined 的属性时,如下

"hello"(1); // Uncaught TypeError: "hello" is not a function

null.x; // Uncaught TypeError: Cannot read property 'x' of null

字符串和数字相加

字符串和数字相加,JavaScript 会自动把数字转换成字符的,不管数字在前还是字符串在前

"2" + 3; // "23"
2 + "3"; // "23"

字符串数字相加结果是字符串

需要注意:+ 的运算方向从左到右

1 + 2 + "3"; // "33"

(1 + 2) + "3"; // "33"

下面跟上面对比

1 + "2" + 3; // "123"

隐式类型转换隐藏一些错误

隐式类型转换,某些情况下,会隐藏一些错误,比如,

  • null 会转换成 0
  • undefined 会转换成 NaN

需要注意的是,NaN 和 NaN 不相等(这是由于浮点数的精度决定的),如下:

let x = NaN;
x === NaN; // false

isNaN()

isNaN–MDN
算术运算返回一个未定义的或无法表示的值时NaN就产生了。
但是,NaN并不一定用于表示某些值超出表示范围的情况。

将某些不能强制转换为数值的非数值转换为数值的时候,也会得到NaN

例如,0 除以0会返回NaN —— 但是其他数除以0则不会返回NaN。

虽然,JavaScript提供了isNaN方法来检测某个值是否为NaN,但是,这也不太精确的,因为,在调用isNaN函数之前,本身就存在了一个隐式转换的过程,它会把那些原本不是NaN的值转换成NaN的,如下:

isNaN("foo"); // true
isNaN(undefined); // true
isNaN({}); // true
isNaN({ valueOf: "foo" }); // true

如果isNaN函数的参数不是Number类型isNaN函数会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN进行判断。

因此,对于能被强制转换为有效的非NaN数值来说(空字符串和布尔值分别会被强制转换为数值0和1),返回false值也许会让人感觉莫名其妙。

JavaScript 中其他的值不同,NaN不能通过相等操作符(== 和 ===)来判断 ,因为 NaN == NaN 和 NaN === NaN 都会返回 false

有一种可靠的并且准确的方法可以检测NaN

我们都知道,只有NaN是自己不等自己的,那么,我们就以使用不等于号(!==)来判断一个数是否等于自身,从而,可以检测到NaN了

let a = NaN;
a !== a; // true
let b = "foo";
b !== b; // false
let c = undefined;
c !== c; // false
let d = {};
d !== d; // false
let e = { valueOf: "foo" };
e !== e; // false

简单封装下

function isReallyNaN(x) {
    return x !== x;
}

对象的隐式转换

对象可以转换成原始值,最常见的方法就是把它转换成字符串

"the Math object: " + Math; // "the Math object: [object Math]"
"the JSON object: " + JSON; // "the JSON object: [object JSON]"

对象转换成字符串是调用了对象的toSting方法的,我们可以手动的调用它来检测一下:

Math.toString(); // "[object Math]"
JSON.toString(); // "[object JSON]"

类似的,对象也是可以转换成数字的,他是通过valueOf函数的,当然,你也是可以自定义这个valueOf函数的,如下:

"J" + { toString: function() { return "S"; } }; // "JS"
2 * { valueOf: function() { return 3; } }; // 6

如果,一个对象同时存在valueOf方法和toString方法,那么valueOf方法总是会被优先调用的,如下:

var obj = {
    toString: function() {
        return "[object MyObject]";
    },
    valueOf: function() {
        return 17;
    }
};
"object: " + obj; // "object: 17"

但是,多数情况下,这都不是我们想要的,一般的,尽可能使valueOf和toString表示的值相同(尽管类型可以不同)。

强制类型转换 - “真值运算”

强制类型转换,我们常常称之为“真值运算”,比如,if, ||, &&,他们的操作数不一定是布尔型。

JavaScript会通过简单的转换规则,将一些非布尔类型的值转换成布尔型的值

大多数的值都会转换成true,只有少数的是false,他们分别是:false, 0, -0, "", NaN, null, undefined

因为存在数字和字符串以及对象的值为false,所以,直接用真值转换来判断一个函数的参数是否传进来了,这是不不太安全的

比如,有一个可以具有默认值得可选参数的函数,如下:

function point(x, y) {
if (!x) {
    x = 320;
}
if (!y) {
    y = 240;
}
    return { x: x, y: y };
}

这个函数会忽略任何的真值为假的参数的,包括0,-0;

point(0, 0); // { x: 320, y: 240 }

检测undefined的更加准确的方法是用typeof操作:

function point(x, y) {
if (typeof x === "undefined") {
    x = 320;
}
if (typeof y === "undefined") {
    y = 240;
}
    return { x: x, y: y };
}

这种写法,可以区分开0和undefined

point(); // { x: 320, y: 240 }
point(0, 0); // { x: 0, y: 0 }

另外一种方法是利用参数跟undefined作比较,如下:

if (x === undefined) { ... }

// 如下
let z
z === undefined // true

隐式转换总结

  • 类型错误有可能会被类型转换所隐藏。
  • “+”既可以表示字符串连接,又可以表示算术加,这取决于它的操作数,如果有一个为字符串的,那么,就是字符串连接了。
  • 对象通过valueOf方法,把自己转换成数字,通过toString方法,把自己转换成字符串。
  • 具有valueOf方法的对象,应该定义一个相应的toString方法,用来返回相等的数字的字符串形式。
  • 检测一些未定义的变量时,应该使用typeOf或者与undefined作比较,而不应该直接用真值运算

toString()

toString() – MDN
toString() 方法返回一个表示该对象的字符串。

  • 每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用
  • 对于对象xtoString() 返回 “[object type]”,其中type是对象类型
  • 如果x不是对象,toString() 返回x应有的文本值(不是单纯的加”“)
var x = {}
console.log(x.toString()) //  "[object Object]"
toString.call(undefined) //"[object Undefined]"
toString.call(null) // "[object Null]"

//其他类型的toString():
var x = [123]
x.toString() // "1,2,3"

var x = function(){console.log('hello world')}
x.toString() // "function(){console.log('hello world')}"

var x = 12345
x.toString() // "12345"
  • 可以定义一个对象的toString()方法来覆盖它原来的方法。这个方法不能含有参数,方法里必须return一个值。
var a = {}
console.log(a.toString()) // [object Object]

a.toString = function() {return "hello world!"};

console.log(a + " hello" ) // "hello world!hello"

console.log(a + 1) // "hello world!1"

valueOf()

valueOf() — MDN文档
JavaScript调用valueOf方法将对象转换为原始值

我们很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。

默认情况下,valueOf方法由Object后面的每个对象继承。

每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法返回值和返回值类型均可能不同

不同类型对象的valueOf()方法的返回值
对象返回值
Array返回数组对象本身。
Boolean布尔值。
Date存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function函数本身。
Number数字值。
Object对象本身。这是默认情况。
String字符串值。
 Math 和 Error 对象没有 valueOf 方法。

你可以在自己的代码中使用valueOf方法将内置对象转换为原始值。

创建自定义对象时,可以覆盖Object.prototype.valueOf()来调用自定义方法,而不是默认Object方法。

//一下例子均没有原始值,返回这个对象本身
var x = {}
x.valueOf() // {}

var x = [1,2,3]
x.valueOf() // [1, 2, 3]

var x = function(){console.log('hello world')}
x.valueOf() // function (){console.log('hello world')}

var x = 12345 
x.valueOf() // 返回 12345  依旧是number类型

可以自己定义一个对象的valueOf()方法来覆盖它原来的方法。
这个方法不能含有参数,方法里必须return一个值。

var x = {}
x.valueOf = function(){
    return 10
}
console.log(x+1) // 11
console.log(x+"hello") // 输出10hello

toString() vs valueOf()

  • 如果一个对象它的toString() valueOf()方法均存在时,到需要调用的时候,则只调用valueOf()
function fn() {
    return 20
}
console.log(fn + 10) // function fn() {return 20}10
console.log(fn + 'hello') // function fn() {return 20}hello
fn.toString = function() {
    return 10
}
console.log(fn + 10) // 20
console.log(fn + 'hello') // 10hello

fn.valueOf = function() {
    return 5
}

console.log(fn + 10) // 15
console.log(fn + 'hello') // 5hello

从上面我们可以看出:

当函数fn+连接一个字符串或者是数字的时候(隐式转换),如果我们没有重新定义valueOf和toString,其隐式转换会调用默认的toString()方法,将函数本身内容作为字符串返回;

如果我们自己重新定义toString/valueOf方法,那么其转换会按照我们的定义来,其中valueOf比toString优先级更高

var x = {
    toString: function () { return "foo"; },
    valueOf: function () { return 42; }
};

alert(x); // foo
"x=" + x; // "x=42"
x + "=x"; // "42=x"
x + "1"; // 421
x + 1; // 43
["x=", x].join(""); // "x=foo"

可以看到+时,和上面的结论一样

如果我们自己重新定义toString/valueOf方法,优先调用valueOf()方法

但是alert , [x].join(“”)等这类特殊的表达均优先调用toString(),当作特例记住就行了

推荐阅读

连点成线


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值