隐式强制类型转换
和 显示强制类型转换
。两者的区别在于是否可直观看出。var a = 42;
var b = a + ""; // 隐式强制类型转换
var c = String( a ); // 显式强制类型转换
b; // 42
c; // 42
负责处理非字符串到字符串的强制类型转换。
对于普通对象来说,除非自定义,否则都会调用其内部的 toString() 方法。
数组的toString() 方法经过了重定义,会将所有数组元素用
, 连接起来。
var a = [1,2,3];
a.toString(); // 1,2,3
JSON.stringify(undefined); // undefined
JSON.stringify(function () {}); // undefined
JSON.stringify([1, undefined, function () {}, 4]); // "[1,null,null,4]"
JSON.stringify({ a: 2, b: function () {} }); // "{"a":2}"
JSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]'
toJSON()
方法进行重写。// eg1:
var o = { };
var a = {
b: 42,
c: o,
d: function(){}
};
// 在a中创建一个循环引用
o.e = a;
// 循环引用在这里会产生错误
// JSON.stringify( a ); // TypeError: Converting circular structure to JSON
// 自定义的JSON序列化
a.toJSON = function() {
// 序列化仅包含b
return { b: this.b };
};
JSON.stringify( a ); // "{"b":42}"
// eg2:
var a = {
val: [1,2,3],
// 可能是我们想要的结果!
toJSON: function(){
return this.val.slice( 1 );
}
};
JSON.stringify( a ); // "[2,3]"
数组或函数
。如果 replacer 为数组, 那它必须为字符串数组,数组中包含了要序列化要处理的对象 key, 除此之外的属性则会被忽略
。如果 replacer 为函数,那它会对对象本身调用一次,然后对对象中的每个属性各调用一次,可传递两个参数,键和值。
var a = {
b: 42,
c: "42",
d: [1,2,3]
};
// replacer 为数组
JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
// replacer 为函数
JSON.stringify( a, function(k,v){
if (k !== "c") return v;
} );
// "{"b":42,"d":[1,2,3]}"
还有一个可选参数 space,用于指定缩进格式。
var a = {
b: 42,
c: "42",
d: [1,2,3]
};
JSON.stringify( a, null, 3 );
// "{
// "b": 42,
// "c": "42",
// "d": [
// 1,
// 2,
// 3
// ]
// }"
对于字符串,数字,布尔值和 null,结果与 toString() 基本相同。
如果在 JSON.stringify() 的对象中重定义了 toJSON() 方法,那该方法会在字符序列化前调用。
在使用 Number() 或 toNumber() 方法将一个字符串转换为数字时,如果字符串中出现非数字字符,则会返回 NaN。
let obj = {
// 首先调用
[Symbol.toPrimitive]() {
return 200;
},
// 中间调用
valueOf() {
return 300;
},
// 最后调用
toString() {
return 'Hello';
}
}
console.log(obj + 200); // 400
• undefined
• null
• false
• +0、-0 和 NaN
• ""
// 假值的布尔强制类型转换结果为 false。
// eg1:
var a = new Boolean(false);
var b = new Number(0);
var c = new String("");
console.log(!!(a && b && c)); // true
// eg2:
var a = 'false';
var b = '0';
var c = "''";
console.log(!!(a && b && c)); // true
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
~(非) 运算符
~ 会返回 2 的补码
。// ~x 大致等于 -(x + 1)。
~42; // -(42 + 1) ==> -43
在 -(x + 1) 中唯一能够得到 0 的 x 值是 -1。也就是如果 x 为 -1 时,~ 与其他一些数字值会返回 false 值,否则返回 true 值。
// before
var a = "Hello World";
if (a.indexOf( "lo" ) >= 0) { // true
// 找到匹配!
}
if (a.indexOf( "lo" ) != -1) { // true
// 找到匹配!
}
if (a.indexOf( "ol" ) < 0) { // true
// 没有找到匹配!
}
if (a.indexOf( "ol" ) == -1) { // true
// 没有找到匹配!
}
// after
var a = "Hello World";
~a.indexOf( "lo" ); // -4 <-- 真值!
if (~a.indexOf( "lo" )) { // true
// 找到匹配!
}
~a.indexOf( "ol" ); // 0 <-- 假值!
!~a.indexOf( "ol" ); // true
if (!~a.indexOf( "ol" )) { // true
// 没有找到匹配!
}
// before
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
// after
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
!!a; // true
!!b; // true
!!c; // true
!!d; // false
!!e; // false
!!f; // false
!!g; // false
+
运算符既能用于加法运算,也能用于字符串拼接。var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42
如果 + 运算符中其中一个操作数是字符串,则执行字符串拼接,否则执行加法运算。
|| 和 &&
返回值两个操作数中的其中一个
。var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null
|| 和 && 操作符会对第一个操作数进行条件判断,且会对第一个操作数进行隐式类型转换(会通过 toBoolean 操作),然后再进行条件判断。
|| 运算符,如果条件判断结果为true, 就返回第一个操作数的结果。如果为 false, 就返回第二个操作数的结果。
&& 运算符则相反,如果条件判断结果为 true 就返回第二个操作数结果,如果为 false, 就返回第一个操作数的结果。
a || b;
// 大致相当于
a ? a : b;
a && b;
// 大致相当于
a ? b : a;
判断条件上
。== 检查值是否相等, === 检查值和类型是否相等
。这么说听起来蛮有道理,但不够准确。正确的解释应该是: == 允许在相等比较中进行强制类型转换,而 === 不允许
== 检查值是否相等, === 检查值和类型是否相等
)解释:严格相等(===) 比 宽松相等(==) 似乎做的事情更多,因为它还要检查值的类型。而第二种(== 允许在相等比较中进行强制类型转换,而 === 不允许
) 解释: 宽松相等(==) 似乎做的事情更多,如果值类型不同还需要进行强制类型转换。在进行比较两个值类型相同的情况下,使用 == 与 === 没有什么区别
。如果两个值类型不同,这时候就要考虑有没有强制类型转换的必要,有就用 ==,没有就用 ===,不需要在乎性能
。var a = 42;
var a = "42";
a === b; // false
a == b; // true
如果 Type(x) 为数字,Type(y) 为字符串,则返回 x == toNumber(y) 的结果
如果 Type(x) 为字符串,Type(y) 是数字,则返回 toNumber(x) == y 的结果
var a = "42";
var b = true;
a == b; // false
如果 Type(x) 是布尔类型,则返回 toNumber(x) == y 的结果
如果 Type(y) 是布尔类型,则返回 x == toNumber(y) 的结果
== 两边的布尔值会进行 toNumber 操作
。var a = "42";
// 不要这样用,条件判断不成立:
if (a == true) {
// ..
}
// 也不要这样用,条件判断不成立:
if (a === true) {
// ..
}
// 这样的显式用法没问题:
if (a) {
// ..
}
// 这样的显式用法更好:
if (!!a) {
// ..
}
// 这样的显式用法也很好:
if (Boolean( a )) {
// ..
}
如果 x 为 null, y 为 undefined, 则结果为 true
如果 x 为 undefined, y 为 null, 则结果为 true
在 == 中的 null 和 undefined 是一回事,可进行隐式的强制类型转换
。var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
null == undefined; // true
null == null; // true
undefined == undefined; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
可将 null 和 undefined 作为等价来处理
。var a = doSomething();
if (a == null) {
// ..
}
如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == toPromitive(y) 的结果
如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 toPromitive(x) == y 的结果
var a = 42;
var b = [ 42 ];
a == b; // true
// 方法一:
var a = {
i: 1,
[Symbol.toPrimitive]() {
return this.i++;
}
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 方法二:
var a = {
i: 1,
valueOf() {
return this.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 1, 第二次返回 2,以此类推,就会产生这种情况。
// 方法三:
var a = {
i: 1,
toString() {
return this.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false
如何安全使用 == 操作符呢?
如果两边的值有 true 或 false, 千万不要使用 ==
如果两边的值有 []、""、0, 千万不要使用 ==
a < b, a = "" > b 会被处理为 b <>
比较双方会首先调用 toPromitive,如果结果中出现非字符串,就根据 toNumber 的规则将双方强制类型转换为数字进行比较
// 如下:
var a = 42;
var b = "43";
a < b; // true 这里为什么会返回 true, 先保留疑惑,后面会解答
b < a; // false
// 再比如:如果比较双方都是字符串,则按照字母顺序进行比较:
var a = ["42"];
var b = ["043"];
a < b; // false
b < a; // true
// 再比如:如果比较双方都是字符串, 则会进行 toPromitive 操作
var a = {b: 42};
var b = {b: 43};
a < b; // false
b < a; // false
// 因为 a = [object Object], b 也是 [object, Object],所以按照字母顺序排序 a < b, b < a 不成立。
// 再比如:
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 和 a == b 都是 false,为什么 a <= b 和 a > b 为 true?
// 因为根据规则 a <= b 会被处理为 b < a, 然后将结果反转。(如果没懂,回头看这段实例代码)
如果要避免 a < b 之间的隐式强制类型转转,就只能确保 a 和 b 为相同的类型, 或进行显示的强制类型转换。
toString: 对于普通对象来说,除非自定义,否则都会调用其内部的 toString() 方法。
toNumber: 在使用 Number() 或 toNumber() 方法将一个字符串转换为数字时,如果字符串中出现非数字字符,则会返回 NaN。
toBoolean: 除 undefined、null、false、+0、-0 和 NaN、"" 都为真值
toPromitive: 如果检查该值是否有 valueOf 方法,看是否会返回原始值,如果返回值是原始值,则直接使用。否则,就使用 toString 方法,如果 toString 方法返回的是原始值,则直接使用,否则抛出 TypeError 错误。
如果 + 运算符中其中一个操作数是字符串,则执行字符串拼接,否则执行加法运算
。~(非) 运算符: ~ 会返回 2 的补码, 而 ~x 大致等于 -(x + 1)
// ~x 大致等于 -(x + 1)。
~42; // -(42 + 1) ==> -43
|| 和 && 操作符会对第一个操作数进行条件判断,且会对第一个操作数进行隐式类型转换(会通过 toBoolean 操作),然后再进行条件判断。
|| 运算符,如果条件判断结果为true, 就返回第一个操作数的结果。如果为 false, 就返回第二个操作数的结果
。&& 运算符则相反,如果条件判断结果为 true 就返回第二个操作数结果,如果为 false, 就返回第一个操作数的结果
。a || b;
// 大致相当于
a ? a : b;
a && b;
// 大致相当于
a ? b : a;
在于对操作数类型不同时他们的处理方式不同
):== 允许在相等比较中进行强制类型转换,而 === 不允许
。在两个值类型相同情况下,使用 == 与 === 没有区别
在两个值类型不同情况下,就要考虑是否有没有强制类型转换的必要,有就用 ==, 没有就用 ===
如果 Type(x) 为数字,Type(y) 为字符串,则返回 x == toNumber(y) 的结果
如果 Type(x) 为字符串,Type(y) 是数字,则返回 toNumber(x) == y 的结果
var a = 42;
var a = "42";
a === b; // false
a == b; // true
如果 Type(x) 是布尔类型,则返回 toNumber(x) == y 的结果
如果 Type(y) 是布尔类型,则返回 x == toNumber(y) 的结果
var a = "42";
var b = true;
a == b; // false
var a = "42";
// 不要这样用,条件判断不成立:
if (a == true) {
// ..
}
// 也不要这样用,条件判断不成立:
if (a === true) {
// ..
}
// 这样的显式用法没问题:
if (a) {
// ..
}
// 这样的显式用法更好:
if (!!a) {
// ..
}
// 这样的显式用法也很好:
if (Boolean( a )) {
// ..
}
如果 x 为 null, y 为 undefined, 则结果为 true
如果 x 为 undefined, y 为 null, 则结果为 true
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
null == undefined; // true
null == null; // true
undefined == undefined; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
可将 null 和 undefined 作为等价来处理
。var a = doSomething();
if (a == null) {
// ..
}
如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == toPromitive(y) 的结果
如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 toPromitive(x) == y 的结果
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 晕!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false
如果两边的值有 true 或 false, 千万不要使用 ==;
如果两边的值有 []、""、0, 千万不要使用 ==;
a < b, a = "" > b 会被处理为 b <>
判断中,其中一个很重要的点是,会将结果反转
。如何规避掉上述隐式的强制类型转换
?确保 a 和 b 为相同的类型, 或进行显示的强制类型转换。
// 方法一:
var a = {
i: 1,
[Symbol.toPrimitive]() {
return this.i++;
}
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 方法二:
var a = {
i: 1,
valueOf() {
return this.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
// 如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 1, 第二次返回 2,以此类推,就会产生这种情况。
// 方法三:
var a = {
i: 1,
toString() {
return this.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(a); // { i: 4, valueOf: [Function: valueOf] } 输出 a 对象,注意 i 的值
}
•问题标注 Q:(question)
•答案标注 R:(result)
•注意事项标准:A:(attention matters)
•详情描述标注:D:(detail info)
•总结标注:S:(summary)
•分析标注:Ana:(analysis)
•提示标注:T:(tips)