JavaScript是一门动态类型语言,变量是没有类型的,可以随时赋予任意值。但数据本身和运算是区别类型的。因此需要进行数据类型转化,有些转换是自动进行的,有些转换则需要强制转化。
1. 强制转换
强制转化使用三个构造函数:Number、String和Boolean,手动将各种类型的值转化为数字、字符串或者布尔值。
1.1 Number函数:强制转化为数值
将任意类型的变量转化为数字
原始类型值的转换规则
1)数值:转换后还是原来的值
2)布尔值:true转化为1,false转化为0
3)undefined:转化为NaN
4)null:转化为0
5)字符串:转换规则较多,如下所示:
------如果字符串中只包含数字,转化为相应的十进制数值。eg:"1"转化为1,"123"转化为123,而"011"转化为11(忽略前导0);
------如果字符串中包含有效的浮点格式,转化为浮点数。eg:"12.3"转化为12.3(也会忽略前导0);
------如果字符串为空字符"",返回0;------如果字符串中包含有效的十六进制格式,如"0xf",返回对应的十进制数值(由于忽略前导0,不能识别八进制数值);
------除上述规则外,其它情况下返回NaN。
eg: console.log(Number("123")); "123abc"--->NaN、""--->0、"0xf"--->15、undefined--->NaN、null--->0、false--->0;
console.log(Number(" v12.34 ")); //12.34(Number函数会自动过滤一个字符串前导和后缀中的空格)
console.log(Number([])); //空数组转换为0
对象的转换规则
1)首先调用对象自身的valueOf方法,如果结果为原始类型的值(数值、字符串或布尔值),再对该值使用Number函数,不再进行下面的步骤;
2)如果valueOf方法返回复合类型的值,则调用对象自身的toString方法,如果结果为原始类型的值,再对该值使用Number函数,不再进行下面的步骤;
3)如果toString方法返回的是复合类型的值,则报错。
eg:Number({a:1}); //NaN
上面代码的执行过程如下:
Number({a:1}.toString());
} else {
Number({a:1}.valueOf());
}
上面代码分析如下:首先调用对象的valueOf方法,返回对象本身{a:1},所以toString方法的返回值"[Object Object]"使用Number函数,结果为NaN。
如果toString方法返回的是复合类型的值,则会报错。
toString: function() {
console.log("toString");
return {};
},
valueOf: function() {
console.log("toString");
return {};
}
}
Number(obj);
上述代码中,valueOf方法和toString方法返回的都是对象,Number方法报错:"TypeError: Cannot convert object to primitive value"。
Number({toString: function(){ return 3; }});
Number({valueOf: function(){ return 2;}, toString: function() {return 3;}});
上述代码的执行结果分别是:2,3,3。第一个对象返回valueOf方法的值,第二个对象返回toString方法的值,第三个对象表示valueOf方法先与toString方法执行。
1.2 String函数:强制转化为字符串
可以将任意类型的值转化为字符串。
原始类型值的转化规则
1)数值:转化为相应的字符串
2)字符串:转化为原来的值
3)布尔值:true转化为"true",false转化为"false"
4)null:转化为"null"
5)undefined:转化为"undefined"
对象的转化规则
1)首先调用对象自身的toString方法,如果返回的是原始类型的值,对该值使用String函数,不再进行下面的步骤;
2)如果toString方法返回的是复合类型的值,再调用对象自身的valueOf方法,如果返回原始类型的值,对该值使用String函数, 不再进行下面的步骤;
3)如果valueOf方法放回的是复合类型的值,报错。
String函数的这种过程正好与Number函数正好相反。
eg: String({a:1}); //"[Object Object]"
上述代码相当于String({a:1}.toString()); //"[Object Object]"
toString: function() {
console.log("toString");
return {};
},
valueOf: function() {
console.log("toString");
return {};
}
}
String(obj);
上述代码中toString方法和valueOf方法返回的都不是原始类型的值,那么String方法报错:"TypeError: Cannot convert object to primitive value"。
String({valueOf: function() {return 2; }});
String({toString: function() {return 3;}, valueOf: function(){ return 2;}});
上述代码的执行结果分别为:3 2 3。第一个对象返回toString方法,第二对象返回valueOf方法,第三个对象说明toString方法咸鱼valueOf方法执行。
1.3 Boolean函数:强制转化成布尔值
可以将任意类型的值转化为布尔值。
原始类型值得转化规则
仅有7个值转化为false,这7个值分别是:null、undefined、+0、-0、""、NaN、false,其它值则转化为true;
对象的转化规则
1)所有对象都转化为true,包括false对应的布尔对象;
2)空数组和空对象也转化为true。
eg:Boolean(false); //false Boolean(new Boolean(false)); //true Boolean([]); //true; Boolean({}); //true;
对于数组,三种方式的强制类型转换
eg:Number([]); //0 Number([1,2,3]); //NaN String([]); //"" String([1,2,3]); //"1,2,3" Boolean([]); //true Boolean([1,2,3]);// true
2. 自动转换
当遇到以下3种情况,JavaScript会进行自动类型准换。
1)不同类型数据进行互相运算;
2)对非布尔值类型的数据求布尔值;
3)对非数值类型的数据进行一元运算(+,-);
2.1 自动转化为数值
当JavaScript遇到预期为数值的地方,会自动将非数值的参数转化为数值,转化规则与强制转化为数值规则一致,即在预期为数值的地方,自动调用Number方法。
除了"+"运算符有可能将值转化为字符串外,其它运算符将两侧的值自动转化为数值。
eg: ('5' - '2'; //3) ('5' * '2' //10) (false - 1; //-1) (true - 1; //0) ('1' - 1; //0) ( '5' * []; //0) (false / '4'; //0) ("abc" - 1; //NaN)
上述例子是二元算术运算符的例子,JavaScript中的两个一元运算符,一元加运算'+'和一元减运算'-'也会将运算因子自动转化为数值。
eg:(+'abc'; //NaN) (-'abc'; //NaN) (+true; //1) (-false; //0)
2.2 自动转化为字符串
当JavaScript遇到预期为字符串的地方,会自动将非字符串的数据转化为字符串,转化规则与强制转化为字符串的规则一致。
自动转化为字符串的情况发生在,'+'运算中,当一个运算因子为字符串,而另一个运算因子为非字符串时,自动将非字符串的数据转化为字符串。
eg:('5' + 123; //"5123")('5' + true; //"5true")('5' + {}; //"5[Object Object]")('5' + []; //"5")('5' + function() {}; //"5function() {}")('5' + undefined; //"5undefined")('5' + null; //"5null")
2.3 自动转化为布尔值
当JavaScript遇到预期为布尔值得地方,会自动将非布尔值的参数转化为布尔值,转化规则与强制转化为布尔值规则一致。
除了6个值,分别是undefined、null、+0、-0、""、NaN是自动转化为false外,其它值自动转化为true。
if(!undefined && !null && !0 && !"" && !NaN) {console.log(true);} //true
2.4 总结
由于自动转换有很大的不确定性,因此在预期为数值、字符串、布尔值的地方,全部使用Number()、String()、Boolean()进行显示转换。
3. 加法运算符的类型转换
加法运算符(+)需要特别讨论,因为它涉及到两种类型的运算(加法和字符连接),所以不单要考虑数据类型转换,还需确定运算的类型。
3.1 三种情况
加法运算符的类型转化,可以分为如下三种情况讨论:
1)运算因子之中存在字符串
只要有一个运算因子为字符串,那么执行的是字符连接操作,另一个运算因子会自动转化为字符串。
2)两个运算因子都为数值或布尔值
这种情况下,执行加法运算,布尔值转化为数值。eg: true + 5; //6 true + true; //2
3)运算因子之中存在对象
运算因子之中存在对象(更准确地说:存在非原始类型的值),首先调用对象的valueOf方法,如果返回的是原始类型的值,则依据上面两条规则进行运算。如果返回的是复合类型的值,则调用toString方法,对返回值运用上面两条规则。
1 + [1,2]; //"11,2"
首先调用[1,2].valueOf(),返回数组[1,2]本身,则继续调用[1,2].toString(),返回"1,2",所以最终结果为"11,2"。
1 + {a:1}; //"1[Object Object]"
首先调用对象{a:1}的valueOf方法,返回的是对象本身,接着调用对象{a:1}的toString方法,返回字符串"[Object Object]",所以最终结果是"1[Object Object]"
有趣的是,如果更换上例中前后因子的顺序,结果不一样了。
{a:1} + 1; //1
原来此处,js引擎将{a:1}解析为代码块,并不解析为对象。{a:1}代码块没有返回值,因此原式相当于{a:1};+1,结果就是1。为了避免这种情况,要将{a:1}放置在括号()中,这样js引擎不会将他解析为代码块,而是解析为对象处理。({a:1}) + 1; //"[Object Object]1"
1 + {valueOf: function() {return 2;}};//3 此代码中,valueOf方法返回数值2,因此最终结果为3。
1 + {valueOf: function() { return {};}};//"1[Object Object]" 此代码中,valueOf方法返回一个空对象,继续调用toString方法,返回"[Object Object]",所以最终结果为"1[Object Object]"。
1 + {valueOf: function() { return {};}, toString: function() { return 2; }};//3 此代码中,valueOf方法返回空对象,非原始类型值,继续调用toString方法,返回数值2,所以最终结果是3。
1 + {valueOf: function() { return {};}, toString: function() { return {};}};//报错:"TypeError:can not covert object to primitive value" 此代码中,valueOf返回空对象,继续调用toString方法,返回空对象,非原始类型值,因此报错。
3.2 四个特殊表达式
1)空数组 + 空数组 eg: [] + []; //"" 此代码中,首先对空数组调用valueOf方法,返回数组本身,因此再对空数组调用toString方法,返回空字符串,因此最终结果是空字符串""。
2)空数组 + 空对象 eg: [] + {}; //"[Object Object]" 此代码中,等同于空字符串""与"[Object Object]"相加,因此最终结果为"[Object Object]"
3)空对象 + 空数组 eg: {} + []; //0 此代码中,{}被解析为空代码块,因此原表达式等同于+ [],此时(+)表示一元加运算,对空数组求正值,将[]强制转化成数值,首先调用valueOf方法,返回数组本身,因此继续调用toString方法,返回"",再执行Number(""),最终结果为0。+[] 代码执行过程为:Number([]); Number([].toString()); Number(""); 如果不将{}解析为代码块,则有:({a:1}) + []; //"[Object Object]"。
4)空对象 + 空对象 eg: {} + {}; // 此代码中,第一个{}被解析为代码块,因此原表达式等同于+ {},执行一元加运算,将{}强制转化为数值,首先调用valueOf方法,返回对象本身,因此继续调用toString方法,返回"[Object Object]",再执行Number("[Object Object]"),最终结果为NaN。+ {}代码执行过程为:Number({}); Number({}.toString()); Number("[Object Object]");
{} + {}; 此代码中,如果第一个{}不被解析为代码块,最终结果为:"[Object Object][Object Object]"。情况如有:({}) + {}; ({} + {}); console.log({} + {}); var a = {} + {}; a;
需要指出的是对于情况3)和4),Node.js的运行结果不同于浏览器环境。
{} + []; // "[Object Object]"
{} + {}; //"[Object Object][Object Object]"
可以看到Node.js并未将第一个{}解析为代码块,原因是Node.js的命令行环境,内部执行机制大致如下所示:
eval.call(this,"(function() { return {} + {}; } }).call(this);");
Node.js把命令行输入都放在eval中执行,因此不会把起首的大括号理解为空代码块加以忽略。
引用:http://javascript.ruanyifeng.com/grammar/conversion.html#toc5
时间:2014-10-15
地点:合肥科大图书馆