0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

JS强制类型转换

修罗 2021-12-23 1282 0条评论 JS

首页 / 正文

参考自:《你不知道的JS》中卷

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

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

1.1 ToString

非字符串转换为字符串。

  • 基本类型转字符串null转换为"null"undefined转换为"undefined"true转换为"true"null和undefined没有toString方法,可以通过String方法转为字符串,如:String(null)
  • 普通对象:除非自定义,否则调用原型的toString方法返回内部属性[[Class]]的值,如"[object Object]"
// 数组重写了toString
var a = [1,2,3];
console.log(a.toString()); // "1,2,3"

1.1.1 JSON.stringify

JSON.stringify(..)将JSON对象序列化为字符串。

JSON.stringify(42); // "42"
JSON.stringify("42"); // ""42""
JSON.stringify(null); // "null"
JSON.stringify(true); // "true"
  • 所有安全的JSON值都可以使用JSON.stringify(..)字符串化。安全的JSON值是指能够呈现为有效JSON格式的值。不安全的JSON值包括undefinedfunctionsymbol和包含循环引用的对象
  • 如果对象中定义了toJSON()方法,JSON字符串化时会首先调用该方法,然后用它的返回值来进行序列化。
    toJSON()返回的应该是一个能够被字符串化的安全的JSON值,可以是任何类型,然后再由JSON.stringify(..)对其进行字符串化。
var a = {
    val:[1,2,3],
    toJSON:function(){
        return this.val.slice(1)
    }
}
JSON.stringify(a) // "[2,3]"

JSON.stringify第二个参数可以为数组或对象,用来指定对象序列化过程哪些属性应该被处理,哪些应该被排除,和toJson很像

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

非数字值转换为数字值。

  • 基本类型转数字值:其中true转换为1,false转换为0。undefined转换为NaNnull转换为0。
  • 对象类型转数字值:会通过ToPrimitive转换为基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
    ToPrimitive:为了将值转换为相应的基本类型值,抽象操作ToPrimitive会首先检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toString()的返回值来进行强制类型转换,都没返回基本类型就报错TypeError
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

1.3.1 假值

  • 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

1.3.2 真值

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

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

二. 显式强制类型转换

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

// +c显式地将c转换为数字
var e = 5+ +c; 
console.log(e); // 8.14

2.1.1 日期显式转换为数字

var d = new Date("Mon, 18 Aug 2014 08:53:06 CDT");
// +还能将日期类型转时间戳
console.log(+d); // 1408369986000

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

2.1.2 奇特的~运算符

非运算符,二进制位按位取反

~1计算步骤:

  • 1转二进制 = 00000001
  • 按位取反 = 11111110
  • 发现符号位(即最高位)为1(表示负数),将除符号位之外的其他数字取反 = 10000001
  • 末位加1取其补码 = 10000010
  • 转换回十进制 = -2
// ~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";
var c = "3.14"

Number(a); // 42
parseInt(a); // 42

// Number从整体来解析
Number(b); // NaN
// parseInt/parseFloat从左到右解析
parseInt(b); // 42

Number(c); // 3.14
parseInt(c); // 42
parseFloat(c) // 3.14

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转换。
  • 建议使用Boolean(a)!!a来显式强制类型转换。

三. 隐式强制类型转换

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

a+b若其中一个类型为字符串,就是字符串拼接。

var a = "42";
var b = "0";

var c = 42
var d = ""

a + b; // "420"
c + d; // "42"

对象类型会通过ToPrimitive转成基本类型再处理

var e = [1,2];
var f = [3,4];

e + f; // "1,23,4"

-符号能将字符串转数字

var i = "3.14";
var j = i - 0;

j; // 3.14

3.2 布尔值到数字的转换

// 参数只有一个true才返回true
function onlyOne(){
    var sum = 0;
    for(var i = 0; i<arguments.length; i++){
        // 跳过假值
        if(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中||&&的返回值是两个操作数中的一个。C和php中返回true或false。

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.1 抽象相等

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

字符串会转成数字再比较

var a = 42;
var b = "42"

a == b; // true

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

boolean会转成数字再比较

var a = "42";

// 不要这样用,条件判断不成立
// true首先转为1,a转为42
if(a == true){ }

4.1.3 null和undefined之间的相等比较

null和undefined两者互相宽松比较才会返回true,任何变量和null或undefined比较都会返回false。

var a = null;
var b;

// null == undefined
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

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

如下代码,a == b结果为true,因为b通过ToPromitive进行强制类型转换(也称为"拆封"),并返回标量基本类型值"abc",与a相等。

var a = "abc";
var b = Object(a);

a === b;// false
a == b; // true 

如下代码,因为nullundefined没有对应的封装对象,所以不能够被封装。
NaN能够被封装为数字封装对象,但拆封之后NaN == NaN返回false,因为NaN不等于NaN

var a = null;
var b = Object(a);
a == b; // false

var c = undefined;
var d = Object(c);
c == d; // false

var e = NaN;
var f = Object(a);
e == f; // false

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

不管是宽松相等比较还是严格比较都比较两者的地址值

4.2 容易头疼的比较

'0' == false; // true
false == 0; // true
false == ''; // true
false == []; // true
'' == 0; // true
'' == []; // true
[] == ![]; // true, 首先进行![]的隐式转换,先将`[]`转换成布尔值,为`true`,然后取反是`false`,而`[] == false`是true

'' == [null]; // true, 注意`[null]`字符串话后是空字符串`''`
0 == '\n'; // true; 原因在于: ''或'\n'等空字符串在`ToNumber`的过程中被转换成`0`

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

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

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

五. 抽象关系比较

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。

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis