• JS的强制类型转换


    将值从一种类型转换为另一种类型通常称为类型转换,这是显式的情况,隐式的情况称为强制类型转换。
    JavaScript中的强制类型转换总是返回标量基本类型值,如字符串、数字和布尔值,不会返回对象和函数。
    类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时。
    我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。

    1.字符串、数字和布尔值之间类型转换的基本规则

    1.1 ToString

    基本类型值的字符串化规则为:null转换为"null",undefined转换为"undefined",true转换为"true"。

    对于普通对象来说,除非自定义,否则toString()返回内部属性[[Class]]的值,如"[object Object]"。

    var a = [1,2,3];
    console.log(a.toString());//"1,2,3"
    

    toString()可以被显式调用,或者在需要字符串化时自动调用。

    JSON.stringify(..)在将JSON对象序列化为字符串时也用到了ToString。

    JSON.stringify(42);//"42"
    JSON.stringify("42");//""42""
    JSON.stringify(null);//"null"
    JSON.stringify(true);//"true"
    

    所有安全的JSON值都可以使用JSON.stringify(..)字符串化。安全的JSON值是指能够呈现为有效JSON格式的值。
    不安全的JSON值包括undefined、function、symbol和包含循环引用的对象。

    如果要对含有非法JSON值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义toJSON()方法来返回一个安全的JSON值。
    如果对象中定义了toJSON()方法,JSON字符串化时会首先调用该方法,然后用它的返回值来进行序列化。
    toJSON()返回的应该是一个适当的值(一个能够被字符串化的安全的JSON值),可以是任何类型,然后再由JSON.stringify(..)对其进行字符串化。

    var a = {
    	b : 42,
    	c : "42",
    	d : [1,2,3]
    };
    console.log(JSON.stringify(a,["b","c"]));//"{"b":42,"c","42"}"
    console.log(JSON.stringify(a,function(k,v){if(k != "c"){return v;}}));//"{"b":42,"d",[1,2,3]}"
    

    1.2 ToNumber

    ES5规范定义了抽象操作ToNumber用于将非数字值转换为数字值。
    其中true转换为1,false转换为0。undefined转换为NaNnull转换为0。

    对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
    为了将值转换为相应的基本类型值,抽象操作ToPrimitive会首先检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toString()的返回值(如果存在)来进行强制类型转换。

    var a = {
    	valueOf: function(){
    		return "42";
    	}
    };
    
    var b = {
    	toString: function(){
    		return "42";
    	}
    };
    
    var c = [4,2];
    c.toString = function(){
    	return this.join("");//"42"
    };
    
    Number(a);//42
    Number(b);//42
    Number(c);//42
    Number("");//0
    Number([]);//0
    Number(["abc"]);//NaN
    

    1.3 ToBoolean

    假值

    • undefined
    • null
    • false
    • +0、-0和NaN
    • ""

    假值的布尔强制类型转换结果为false

    所有的对象都是真值,假值对象除外。
    假值对象不是包装了假值的封装对象。

    var a = new Boolean(false);
    var b = new Number(0);
    var c = new String("");
    
    var d = Boolean( a && b && c );
    console.log(d);//true
    //d为true,说明a、b、c都为true。
    

    浏览器在某些特定情况下,在常规JavaScript语法基础上自己创建了一些外来(exotic)值,这些就是“假值对象”。

    真值

    真值就是假值列表之外的值。

    var a = "false";
    var b = "0";
    var c = "''";
    
    var d = Boolean( a && b && c );
    console.log(d);//true
    //所有字符串都是真值。
    
    var a = [];//空数组
    var b = {};//空对象
    var c = function(){};//空函数
    
    var d = Boolean( a && b && c );
    console.log(d);//true
    //[]、{}和function(){}都不在假值列表中,因此它们都是真值。
    

    2.显式强制类型转换

    我们在编码时应尽可能地将类型转换表达清楚,以免给别人留坑。类型转换越清晰,代码可读性越高,更容易理解。

    2.1 字符串和数字之间的显式转换

    var a = 42;
    var b = String(a);//将数字转换为字符串
    
    var c = "3.14";
    var d = Number(c);//将字符串转换为数字
    
    console.log(b);//"42"
    console.log(d);//3.14
    
    var e = 5+ +c;//+c显式地将c转换为数字
    console.log(e);//8.14
    

    日期显式转换为数字

    var d = new Date("Mon, 18 Aug 2014 08:53:06 CDT");
    console.log(+d);//1408369986000
    

    我们不建议对日期类型使用类型转换,应该使用Date.now()来获得当前的时间戳,使用new Date(..).getTime()来获得指定时间的时间戳。

    奇特的~运算符

    //~x大致等同于-(x+1)。
    ~42;//-(42+1) ==> -43
    //~和indexOf()一起可以将结果强制转换为真/假值。
    //JavaScript中字符串的indexOf(..)方法在字符串中搜索指定的子字符串,如果找到就返回子字符串所在的位置(从0开始),否则返回-1。
    var a = "Hello World";
    
    ~a.indexOf("lo");//-4 <-- 真值!
    
    if(~a.indexOf("lo")){//true
    	//找到匹配!
    }
    
    //~-1的结果为0。
    ~a.indexOf("ol");//0 <-- 假值!
    !~a.indexOf("ol");//true
    
    if(!~a.indexOf("ol")){//true
    	//没有找到匹配!
    }
    

    字位截除

    一些开发人员使用~~来截除数字值的小数部分,~~只适用于32位数字。
    我们在使用~~~进行此类转换时需要确保其他人也能够看得懂。

    2.2 显式解析数字字符串

    var a = "42";
    var b = "42px";
    
    Number(a);//42
    parseInt(a);//42
    
    Number(b);//NaN
    parseInt(b);//42
    

    2.3 显式转换为布尔值

    var a = "0";
    var b = [];
    var c = {};
    
    var d = "";
    var e = 0;
    var f = null;
    var g;
    
    Boolean(a);//true
    Boolean(b);//true
    Boolean(c);//true
    
    Boolean(d);//false
    Boolean(e);//false
    Boolean(f);//false
    Boolean(g);//false
    
    !!a;//true
    !!b;//true
    !!c;//true
    
    !!d;//false
    !!e;//false
    !!f;//false
    !!g;//false
    //在if(..)..这样的布尔值上下文中,如果没有使用Boolean(..)和!!,就会自动隐式地进行ToBoolean转换。
    

    3.隐式强制类型转换

    隐式强制类型转换的作用是减少冗余,让代码更简洁。

    3.1 字符串和数字之间的转换

    var a = "42";
    var b = "0";
    
    var c = 42;
    var d = 0;
    
    a + b;//"420"
    c + d;//42
    
    var e = [1,2];
    var f = [3,4];
    
    e + f;//"1,23,4"
    
    var g = 42;
    var h = g + "";
    
    h;//"42"
    
    var i = "3.14";
    var j = i - 0;
    
    j;//3.14
    

    3.2 布尔值到数字的转换

    function onlyOne(){
    	var sum = 0;
    	for(var i = 0; i<arguments.length; i++){
    		if(arguments[i]){
    			//下面等同于 sum += Number(!!arguments[i]);
    			sum += arguments[i];
    		}
    	}
    	return sum == 1;
    }
    var a = true;
    var b = false;
    
    console.log(onlyOne(b,a));//true
    console.log(onlyOne(b,a,b,b,b));//true
    console.log(onlyOne(b,b));//false
    console.log(onlyOne(b,a,b,b,b,a));//false
    

    3.3 隐式强制类型转换为布尔值

    下面的情况会发生布尔值隐式强制类型转换:

    1. if(..)语句中的条件判断表达式。
    2. for(..;..;..)语句中的条件判断表达式。
    3. while(..)do..while(..)循环中的条件判断表达式。
    4. ? :中的条件判断表达式。
    5. 逻辑运算符||(逻辑或)与&&(逻辑与)左边的操作数。

    3.4 || 和&&

    和其他语言不同,在JavaScript中||&&的返回值是两个操作数中的一个(且仅一个)。即选择两个操作数中的一个,然后返回它的值。

    var a = 42;
    var b = "abc";
    var c = null;
    
    a || b;//42
    a && b;//"abc"
    
    c || b;//"abc"
    c && b;//null
    //a || b相当于a ? a : b
    //a && b相当于a ? b : a
    

    对于||来说,如果条件判断结果为true就返回第一个操作数的值,如果为false就返回第二个操作数的值。
    对于&&来说,如果条件判断结果为true就返回第二个操作数的值,如果为false就返回第一个操作数的值。


    4.宽松相等和严格相等

    宽松相等==检查值是否相等,严格相等===检查值和类型是否相等。
    ==允许在相等比较中进行强制类型转换,而===不允许。

    4.1 抽象相等

    字符串和数字之间的相等比较

    var a = 42;
    var b = "42"
    
    a === b;//false
    a == b;//true
    

    其他类型和布尔类型之间的相等比较

    var a = "42";
    
    //不要这样用,条件判断不成立
    if(a == true){
    	//..
    }
    //也不要这样用,条件判断不成立
    if(a === true){
    	//...
    }
    //这样的显式用法没问题
    if(a){
    	//..
    }
    //这样的显式用法更好
    if(!!a){
    	//..
    }
    //这样的显式用法也很好
    if(Boolean(a)){
    	//..
    }
    

    null和undefined之间的相等比较

    var a = null;
    var b;
    
    a == b;//true
    a == null;//true
    b == null;//true
    
    a == false;//false
    b == false;//false
    a == "";//false
    b == "";//false
    a == 0;//false
    b == 0;//false
    

    对象和非对象之间的相等比较

    var a = "abc";
    var b = Object(a);
    
    a === b;//false
    a == b;//true
    //a == b结果为true,因为b通过ToPromitive进行强制类型转换(也称为"拆封"),并返回标量基本类型值"abc",与a相等。
    
    var a = null;
    var b = Object(a);
    a == b;//false
    
    var c = undefined;
    var d = Object(c);
    c == d;
    
    var e = NaN;
    var f = Object(a);
    e == f;
    //因为没有对应的封装对象,所以null和undefined不能够被封装。
    //NaN能够被封装为数字封装对象,但拆封之后NaN == NaN返回false,因为NaN不等于NaN。
    

    4.2 比较少见的情况

    假值的相等比较

    极端情况

    安全运用隐式强制类型转换

    • 如果两边的值中有true或者false,千万不要使用==
    • 如果两边的值中有[]""或者0,尽量不要使用==

    这时最好用===来避免不经意的强制类型转换。这两个原则可以让我们避开几乎所有强制类型转换的坑。

    5.抽象关系比较

    var a = [42];
    var b = ["43"];
    
    a < b;//true
    b < a;//false
    
    var c = ["42"];
    var d = ["043"];
    c < d;//false
    
    var e = [4,2];
    var f = [0,4,3];
    
    e < f;//false
    //e转换为"4,2",f转换为"0,4,3",按字母顺序进行比较
    
    var a = {b:42};
    var b = {b:43};
    
    a < b;//false
    a == b;//false
    a > b;//false
    
    a <= b;//true
    a >= b;//true
    //根据规范a <= b被处理为b < a,然后将结果反转,因为b < a的结果是false,所以a <= b的结果是true。
    

    参考资料:《你不知道的JavaScript》(中卷) 第四章

  • 相关阅读:
    "#"
    网络请求
    iOS_正则表达式判断手机型号、邮箱、手机号、身份证、昵称、密码等
    程序员
    js交互
    android 性能优化
    Android 开源的项目框架
    Android 开源框架案例
    Android Listview上拉刷新加载框架
    android 上传文件到服务器FIP
  • 原文地址:https://www.cnblogs.com/gzhjj/p/9023612.html
Copyright © 2020-2023  润新知