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–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
toString() – MDN
toString() 方法返回一个表示该对象的字符串。
被表示为文本值时
或者当以期望字符串
的方式引用对象时,该方法被自动调用
。var x = {}
console.log(x.toString()) // "[object Object]"
toString.call(undefined) //"[object Undefined]"
toString.call(null) // "[object Null]"
//其他类型的toString():
var x = [1,2,3]
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"
覆盖
它原来的方法。这个方法不能含有参数
,方法里必须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() — MDN文档
JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身
。
JavaScript的许多内置对象都重写
了该函数,以实现更适合自身的功能需要。因此,不同类型对象的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
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()
,当作特例记住就行了
谢谢你阅读到了最后
期待你,点赞、评论、交流