原生函数和强制类型装换
var a = new String("abc"); console.log(typeof a); //是Object,而不是string
使用new String("abc"); 创建的是字符串 abc 的封装对象,而不是基本类型值"abc"。
封装对象
由于基本类型值没有.length和.toString()这样的属性和方法,需要通过封装对象才能访问,此时JavaScript会自动为基本类型值包装一个封装对象,如:
var a = "abc"; console.log(a.length);
注意:如果需要频繁的用到.length这样的属性和方法,那么从一开始就创建一个封装对象也许更方便,但需要注意的是,事实并非如此,因为浏览器已经为这些常用的方法做了性能优化。直接使用封装对象来提前优化,反而会降低执行效率。
var a = new Boolean(false); if (!a) { console.log("aaaa"); //永远不会执行到这里 }
为false创建一个封装对象,然而该对象的真是是truthy,所有总是返回返回true,如果想要自行封装基本类型值,可以使用object函数。
拆封
如果想要得到封装对象中的基本类型值,可以使用valueOf函数:
var a = new String("abc"); console.log(a.valueOf());//abc
日期Date
创建日期对象必须使用new Date() Date可以带参数用来指定日期和时间,而不带参数的话,则使用当前的日期和时间。
强制类型装换
强制类型转换分为隐式转换和显示转换,这两种转换的区别是根据你对程序的理解来定义的。例如:
var a = 42; var b = a + "";//隐式转换 var c = String(a);//显示转换
如果你对a+""理解的话,那么这行代码对你来说也是显式转换。
ToString方法
基本类型字符串化规则为:null转换为"null",undefined转换为"undefined" true转换为"true",数字的字符串化则最需通用规则。
对普通对象来说,除非自行定义,否则tostring返回内部属性Class的值,如:[object Object]
JSON字符串化
JSON.stringify在对象中遇到undefined、function、和symbol时会自动将其忽略。(连同属性一起忽略,例如:JSON.stringify({'a':1,'b':2,c:undefined}); 结果为:"{"a":1,"b":2}")
replacer
我们可以向JSON.stringify方法传递一个可选参数replacer,可以是数组或者函数,用来指定对象序列号过程中那些属性应该被处理或排除,如果replacer是一个数组,那么它必须是一个字符串数组
其中包含序列化要处理的对象的属性名称,除此之外的属性将被忽略。
如果replacer是一个函数,它会对对象本身调用一次,然后对对象的每个属性各调用一次,每次传递两个参数,建和值,如果要忽略某个键就返回undefined,否则返回指定的值。
例如:
console.log(JSON.stringify(a, ["b", "c"])); //{"b":42,"c":"42"} console.log(JSON.stringify(a, function (k, v) { //{"b":42,"d":[1,2,3]} if (k != "c") { return v; } }));
tonumber
tonumber方法,其中true转换为1,false转为0,undefined转换为NaN,null转换为0
在转换时,先检查是否有valueOf方法,如果有并且返回基本类型值,就使用该值进行强制类型转换,如果没有就使用tostring的返回值来进行强制类型转换。如果valueOf和tostring均不返回基本类型值,会产生typeerror错误。(使用object.crete(null)创建的对象属性为null,并且没有这两个方法)
var a = { valueOf: function () { return "43"; } } var b = { toString: function () { return "43"; } } var c = [4, 3]; c.toString = function () { return this.join(""); } console.log(Number(a)); //43 console.log(Number(b)); //43 console.log(Number(c)); //43 console.log(Number("")); //0 console.log(Number([])); //0 console.log(Number(["abc"])); //NaN
ToBoolean
我们常误以为数值1和0分别等同于true和false,在有些语言中可能是这样,但在JavaScript中布尔值和数字是不一样的,虽然我们可以将1强制转换为true,0转换为false,反之亦然,但它们并不是一回事。
下面这些是可以转换为false的值:
undefined
null
false
+0、-0、NaN
""
我们可以理解为除了以上转换为假值外,其他的都是真值
例如:
var a = "false"; var b = "0"; var c = "''" var d = []; var e = {}; var f = function () { }; console.log(Boolean(a)); //true console.log(Boolean(b));//true console.log(Boolean(c));//true console.log(Boolean(d));//true console.log(Boolean(e));//true console.log(Boolean(f));//true
真值可以无限长,所以无法一一列举,这里只能以假值列表作为参考。
显式强制转换
+运算符
var a = "3.14"; console.log(+a);//3.14
直接转换为数值类型而非数字假发运算,也不是字符串拼接。
var a = "3.14"; var d = 5 + +a; console.log(+d);//8.14
这里需要注意两个+号的问题,如果中间不添加空格的话,那么可能会被按照 ++自增来运算。
var d = new Date(); console.log(+d); //1482134335608 var d = +new Date(); console.log(d); //1482134335609
日期直接转为毫秒数。
不过这样的写法可能在可读性上不是太好,并不推荐使用强制转换来得到毫秒数,所以可以有以下这几种写法,得到毫秒数:
var timestamp = new Date().getTime(); console.log(timestamp);//1482134515222 var timestamp = (new Date()).getTime(); console.log(timestamp);//1482134515222 var timestamp = (new Date).getTime(); console.log(timestamp);//1482134515222 var timestamp = Date.now(); console.log(timestamp);//1482134515222 推荐这种写法
~运算符
~表示字位操作 “非”的相关枪支转换。
~x大致等同于-(x+1)。例如:
console.log(~42);//-43
这里需要注意的是,在-(x+1)中,唯一能够得到0的x值是-1,这个-1是一个稍为值,也就是被赋予了特殊含义的值。程序中一般用大于等于0的值来表示成功,-1表示失败。例如indexOf方法。
那么这种>=0或者==-1的写法不是很好,这种代码被称为“抽象渗漏”,意思是在代码中暴露了底层的实现细节。这里是指用-1作为失败时返回的值,这些细节应该被屏蔽掉。
例如:
var a = "hello"; if (~a.indexOf("lo")) { console.log("true"); } if (!~a.indexOf("li")) { console.log("false"); }
使用这种方法会比>=0更简洁。
~~字位截除
console.log(Math.floor(-49.6)); //-50 console.log(~~-49.6); //-49 优先级更高 console.log(-49.6|0); //-49 优先级低
解析数字字符串
Number()和parseInt()方法的区别:
var a = "42"; var b = "42px"; console.log(Number(a)); //42 console.log(parseInt(a));//42 console.log(Number(b)); //NaN console.log(parseInt(b));//42
可以看到parseint方法是执行到非不可转换为数字的字符时,停止转换,然后返回前面的值,而number方法则是要求整体必须都可以进行转换,才返回,否则返回NaN。
parseint方法的执行过程:
var a = { toString: function () { return "48"; } } console.log(parseInt(a));//48
可以看到,parseInt方法先将参数转换为字符串再进行解析。
!!布尔值转换
var a = 0; console.log(!!a);//false
隐式强制转换
+运算符
当有+运算符左右两端存在字符串时,那么会进行字符串连接,否则会进行加法运算。这是一般的情况下,但还有一种情况:
var a = [1, 2]; var b = [3, 4]; console.log(a + b);//1,23,4
a和b都不是字符串,这里为何会进行拼接呢,这是因为在进行转换时,会先调用数组的valueOf方法,然而数组的valueOf操作无法得到简单的基本类型,所以转而调用tostring方法,这样返回两个字符串后,进行拼接。
a+""和String之间有一个席位的差别需要注意,a+""会调用valueof方法,然后通过tostring转为字符串,而String方法,会直接调用tostring方法。
var a = { valueOf: function () { return 42 }, toString: function () { return 4} } console.log(a + "");//42 console.log(String(a));//4
||和&&
在JavaScript中,它们被称为选择器运算符更恰当。因为和其他语言不同,它们返回的并不是布尔值。
它们返回的是两个操作数中的一个(且仅一个),例如:
var a = 42; var b = "abc"; var c = null; console.log(a || b); //42 console.log(a && b); //42 console.log(c || b); //42 console.log(c && b); //null
可以看出,它们返回的是两个操作数中的一个,而非布尔值。
可以换一个角度来理解:
a||b 可以理解为:
a?a:b
a&&b可以理解为:
a?b:a;
下面是一个常见的用法:
a=a||hello;
还有一种方法并不常见,一般在压缩工具中常见,即:
var a=43; a&&foo();
这种被称为短路,也就是只有a在被定义时,才执行foo方法。
当它们在if中使用时,其实是由if对最后的结果进行了强制类型转换。如:
var a = 42; var b = "abc"; if (a && b) { console.log(true); //true }
也相当于:
var a = 42; var b = "abc"; if (!!a && !!b) { console.log(true); //true }
==和===运算符
我们常见的对它们区别的解释为:==是检查值是否相等,而===是检查值和类型是否相等。
这种解释还不够准确和全面,正确的解释为:==允许在相等比较重进行强制类型转换,而===不允许。
根据第一种解释,我们可以列举为===似乎做的事比==更多,因为它还要检查类型,而第二种则正好相反。
==在比较两个不同类的值会发生隐式强制转换,会将其中之一或两者转换为相同的类型,再进行比较。
例如:
var a = 42; var b = "42"; console.log(a == b); //true console.log(a === b);//false
那么到底是将字符串转换为数值,还是将数值转换为字符串呢,具体的转换规则如下:
如果type(x)是数字,type(y)是字符串,则返回x==ToNumber(y)的结果
如果type(x)是字符串,type(y)是数字,那么则返回ToNumber(x)==y的结果
==最容易出错的一个地方是true和false与其他类型直接的比较:
var a = "42"; var b = true; console.log(a == b); //false
我们都知道"42"是一个真值,那么为什么==的结果不是true呢,看一下规范:
如果type(x)是布尔类型,则返回ToNumber(x)==y的结果
如果type(y)是布尔类型,则返回x==ToNumber(y)的结果
所以在这里,根据规范,先将true转换为1进行比较,变成"42"==1,然而类型仍然不同,在进行转换,"42"被转换为42变成42==1,所以为false。
建议无论何时都不要使用 ==true 或者 ==false
在==中 null和undefined是相当的,也就是说只判断 ==null就可以把undefined的情况也判断进去了。
最特殊的情况:
var i = 2; Number.prototype.valueOf = function () { return i++; } var a = new Number(42); if (a == 2 && a == 3) { console.log(true);//true }
可以看到,判断条件为true,所以最好不要修改内置原型。
所以==会在不经意间就进行了隐式转换,特别需要注意的两点:
如果两边的值中有true或者false,千万不要使用==
如果两边的值有[]、""或者0尽量不要使用==
这时应该使用ongoing===来避免这些不经意间的错误。
抽象关系比较
一般来说比较对象时,会将对象转换为字符串,然后对字母进行比较,比较常见的是"123">"13"为false。但是有几种特殊的情况,如:
var a = { b: 42 }; var b = { b: 43 }; console.log(a < b); //false console.log(a == b);//false console.log(a > b);//false console.log(a <= b);//true console.log(a >= b);//true
这里需要注意的是,为什么a<b和a==b都为false,那么a<=b为什么为true?
因为根据规范,a<=b被处理为a>b然后将结果反转。因为a>b 的结果为false,那么反转的结果为true。
实际上JavaScript在处理>=时,是按照不大于的意思进行处理的,即(!(a<b))的结果。