JS:隐式类型转换、相等性判断

隐式类型转换

在不同情形下,隐式类型转换的规则也是不同的,主要区分为三类情形。

//转换 boolean 类型

// 逻辑运算符 !  
!undefined // true
!null   // true
!NaN   // true
!''   // true
!0   // true

// 条件运算符
if( undefined ){ // false }
if( null ){ // false }
if( NaN ){ // false }
if( '' ){ // false }
if( 0 ){ // false }

//转换 string 类型

// 字符串拼接运算符 当 + 两边至少有一个变量为字符串类型时,即为字符串拼接运算符
1+'0' // '10'
'1'+0 // '10'
'1'+NaN // '1NaN'
'1'+true // '1true'
'1'+null // '1null' 
'1'+undefined // '1undefined'

// 转换 number 类型 

// 算数运算符 ++ -- - * % / 当 + 两边没有字符串类型变量时,即为算数运算符
1+true    // 2
true+true // 2
1+null    // 1
null+null // 0
1+undefined // NaN
undefined+undefined // NaN

// 关系运算符  == === != !== > >= < <=
false < '1' // true
true > 1 // false
'1' > 1 // false

// 特殊情形 相等性判断 == === 技术细节见下文
// NaN 不和任何值相等
NaN == NaN // false
// -0 和 +0 完全相等
+0 == -0  // true
// 类型相同,直接比较值
undefined == undefined // true
null == null // true
// 比较null和undefined时,有如下特殊情形
null == undefined // true
null == 0 // false
undefined == 0 // false
null == false // false
undefined == false  // false

复杂类型数据隐式转换

复杂类型数据在进行隐式转换时,会根据转换的数据类型进行不同的转换操作,最终转换成需要的类型:

转换number时,先使用valueOf方法获取其原始值,如果原始值不是number类型,则使用toString方法转换为string类型,再将string类型转换为number。

转换string时,使用toString方法转换为string类型。

转换boolean时,转换为true。

// 转换 number 类型 

// 算数运算符 ++ -- - * % / 当 + 两边没有字符串类型变量时,即为算数运算符

[]+1    // 1
// [].valueOf() 为 []
// [].toString() 为 ''
// Number('') 为 0 ,相加为1

[1,2]+1  // NaN
// [1,2].valueOf() 为 [1,2]
// [1,2].toString() 为 '1,2'
// Number('1,2') 为 NaN ,相加仍为NaN

{}+0      // NaN
// {}.valueOf() 为 {}
// {}.toString() 为 '[object Object]'
// Number('[object Object]') 为 NaN ,相加仍为NaN

// 关系运算符  == === != !== > >= < <=

[] == 0 // true
// [].valueOf() 为 []
// [].toString() 为 ''
// Number('') 为 0 ,判定为相等

[] == '0' // false
// [].valueOf() 为 []
// [].toString() 为 '',此时 == 两边类型相同,但值不同,则判定为不相等

[1] == 1 // true
// [1].valueOf() 为 [1]
// [1].toString() 为 '1'
// Number('1') 为 1 ,判定为相等

[1] == '1' // true
// [1].valueOf() 为 [1]
// [1].toString() 为 '1',此时 == 两边类型相同,且值相同,则判定为相等

[1,2] == '1,2' // true
// [1,2].valueOf() 为 [1,2]
// [1,2].toString() 为 '1,2',此时 == 两边类型相同,且值相同,则判定为相等

{} == 0 // false
// {}.valueOf() 为 {}
// {}.toString() 为 '[object Object]'
// Number('[object Object]') 为 NaN ,判定为不相等

{} == 1 // false
// {}.valueOf() 为 {}
// {}.toString() 为 '[object Object]'
// Number('[object Object]') 为 NaN ,判定为不相等

{} == '[object Object]' // true
// {}.valueOf() 为 {}
// {}.toString() 为 '[object Object]',此时 == 两边类型相同,且值相同,则判定为相等

// 注意 由于 !运算符优先级高于 ==运算符,故进行相等性判断时如有 !运算的复杂数据,则应先将该数据转换为boolean。

![] == 0  // true
// []转换boolean为true
// !true 为 false
// Number(false)为0,判定相等
// 综上
![] == [] // true
// 也是成立的
// 另外
[] == [] // false
// 由于比较值类型相同,故不进行隐式转换,而当两个引用类型数据进行比较时,实则比较的是这两个数组的指针,故不相等。

!{} == {} // false
// {}转换boolean为true
// !true 为 false
// Number(false)为0
// {}.valueOf() 为 {}
// {}.toString() 为 '[object Object]'
// Number('[object Object]')为NaN,判定不相等

{} == {} // false
// 此处比较的是这两个对象的指针,故不相等

相等性判断

在JS中,通常使用"=="和"==="来判断两个变量是否相等。

===

  • 如果Type(x)与Type(y)不同,返回false。Type方法用于检测一个变量的类型。
  • 如果Type(x)是Undefined ,返回true。
  • 如果Type(x)是Null,返回true。
  • 如果Type(x)是数值,
    • 如果x是NaN,返回false。
    • 如果y是NaN,返回false。
    • 如果x和y是相同值的Number,返回true。
    • 如果x是+0,y是-0,返回true。
    • 如果x是-0,y是+0,返回true。
  • 如果Type(x)是字符串,
    • 如果x和y是完全相同的代码单元序列(在相应的索引中相同的长度和相同的代码单元),返回true
    • 返回false。
  • 如果Type(x)是布尔值,
    • 如果x和y都是true或false,返回true。
    • 返回false。
  • 如果x和y是相同的Symbol值,返回true。
  • 如果x和y是相同的Objcet值,返回true。Object值即引用类型数据的指针。
  • 返回false。

总结:

"==="必须两边变量的类型和值全都相等才判定为相等。

如果"==="两边变量类型不同,则直接判定为不等。

如果“===”两边变量类型相同,则进一步判断变量值是否相同。

==

  • ReturnIfAbrupt(x)。ReturnIfAbrupt方法用于检查一个变量是否导致突然中断,返回一个包含类型和值的合法结果,否则中断执行。
  • ReturnIfAbrupt(y)。
  • 如果Type(x)与Type(y)相同,执行严格相等运算x === y。
  • 如果x是null,y是undefined,返回true。
  • 如果x是undefined,y是null,返回true。
  • 如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
  • 如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
  • 如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
  • 如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
  • 如果Type(x)是字符串/数值/Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。ToPrimitive方法用于返回一个变量的原始值。
  • 如果Type(x)是对象,Type(y)是字符串/数值/Symbol值,返回ToPrimitive(x) == y的结果。
  • 返回false。
// 上文提到的 使用 == 进行相等性判断时的特殊情形,均符合以上规则
null == undefined // true
null == 0 // false
undefined == 0 // false
null == false // false
undefined == false  // false

总结:

"=="只需判断两个变量值是否相等,不需判断类型是否相等。

如果“==”两边变量类型相同,则直接比较值是否相同。

如果"=="两边变量类型不同,则先进行隐式类型转换:

  1 两边变量一个为数值一个为字符串或一个为布尔另一个不限,尝试将两个变量都转换为Number类型,再判断值是否相等。

  2 两个变量一个为对象一个为数值/字符串/Symbol值,尝试使用ToPrimitive获取对象变量的原始值与另一个变量进行==判断。

    1 ToPrimitive方法会先判断传入值是否是原始值,是则直接返回

    2 传入值不是原始值则调用valueOf方法,如果结果是原始值则直接返回

    3 结果不是原始值的调用toString方法,如果结果是原始值(字符串 )则返回,否则抛错

    4 如果另一个变量也是字符串,则直接比较值是否相同,否则都转换为数值进行比较(即==第一步判断)

  3 null == undefined ,除了null和undefined及其自身外,不和任何值相等。

判断两个对象是否相等

由于JS中的对象是一个引用类型数据,当使用对象的变量名进行比较时,事实上是在比较两个变量名在栈内存中保存的对应对象在堆内存中的地址,因此只有两个变量名引用同一个对象的地址时才会判定为相等。

var obj1 = {
    name:"Benjamin",
    sex : "male"
}
 
var obj2 = {
    name:"Benjamin",
    sex : "male"
}
 
var obj3 = obj1;

//Outputs: false
console.log(obj1 == obj2);
 
//Outputs: false
console.log(obj1 === obj2);

//Outputs: true
console.log(obj1 == obj3);
 
//Outputs: true
console.log(obj1 === obj3);
 
//Outputs: false
console.log(obj2 == obj3);
 
//Outputs: false
console.log(obj2 === obj3);

通过上面的代码可以看出,由于obj1和ob3的指针指向了内存中的同一个地址,所以判定相等,而obj1和obj2即使其属性完全相同,也不会判定为相等,obj2和obj3同理。

当需要判断两个对象的属性是否相等时,需要根据需求考虑以下几点:

  1. 是否只比较对象的可枚举属性,排除不可枚举属性

  2. 是否只比较对象的自有属性,排除原型属性

确定需求之后可以使用下面列出的方法来尝试创建一个用于实现对象比较的函数:

方法

适用范围

描述

for..in

数组,对象

遍历实例和原型的可枚举的属性

Object.keys()

数组,对象

返回实例自有的可枚举的属性名组成的数组

Object.getPropertyNames()

数组,对象

返回实例自有的全部(可枚举和不可枚举)属性名组成的数组

下面我们尝试构建一个可用于比较两个对象自有全部属性的函数

function isObjectValueEqual(a, b) {

	//如果a和b不是null或undefined且都是对象或数组才进行判断
	if (a && b && typeof a === "object" && typeof b === "object") {
    
	    // 获取两个对象的自有全部属性名的数组
	    var aProps = Object.getOwnPropertyNames(a);
	    var bProps = Object.getOwnPropertyNames(b);
	 
	    // 如果属性数量不一致则直接判定为不相等
	    if (aProps.length != bProps.length) {
	        return false;
	    }
	     
	    //遍历a对象全部自有属性
	    for (var i in aProps) {
	        var propName = aProps[i];
	        
	        //如果属性是引用类型(数组,对象),且b也具有该属性
	        if (b[propName] && typeof a[propName] === "object"){

	        	//则递归调用isObjectValueEqual函数
	            var isObjPropEqual = isObjectValueEqual(a[propName],b[propName]);
	            if(!isObjPropEqual) {
	                return false;
	            }
	        } else if (typeof a[propName] === "function") {
                
                //注意:并没有判断对象方法相等,只是判断是具有相同方法名
                if ( !b[propName] ){
                    return false;
                } 
            } else if (a[propName] !== b[propName]){  // 如果属性是值类型

	        	//则直接比较值和类型,注意使用全等判断,避免隐式类型转换
		        return false;
	        }
	    }

	    //如果a或b存在空值或不是都是对象或数组则判定不等
	    return true;
    } else {

    	//如果a或b存在空值或不是都是对象或数组则判定不等
    	return false;
    }
    
}
 
var obj1 = {
    name:"Benjamin",
    sex : "male",
    desc:{
    	title:'title',
    	content:'content'
    }
};
 
var obj2 = {
    name:"Benjamin",
    sex : "male",
    desc:{
    	title:'title',
    	content:'content'
    }
};

console.log(isObjectValueEqual(obj1, obj2)); // true

注意,这个函数只判断了两个对象内的全部自有属性,不包括原型方法,如果需要判断原型属性,可以考虑使用for in、原型链递归等方式来改写上面的方法,还有一点值得注意的是,这个函数不支持判断两个对象的方法,只支持判断属性。

下面这个方法是Underscore中isEqual的部分源码,实现了更深的对象判断,可以参考其思路:

// Internal recursive comparison function for `isEqual`.
var eq = function(a, b, aStack, bStack) {
  // Identical objects are equal. `0 === -0`, but they aren't identical.
  // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
  if (a === b) return a !== 0 || 1 / a === 1 / b;
  // A strict comparison is necessary because `null == undefined`.
  if (a == null || b == null)return a === b;
  // Unwrap any wrapped objects.
  if (ainstanceof _) a = a._wrapped;
  if (binstanceof _) b = b._wrapped;
  // Compare `[[Class]]` names.
  var className = toString.call(a);
  if (className !== toString.call(b)) return false;
  switch (className) {
    // Strings, numbers, regular expressions, dates, and booleans are compared by value.
    case '[object RegExp]':
    // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
    case '[object String]':
      // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
      // equivalent to `new String("5")`.
      return '' + a === '' + b;
    case '[object Number]':
      // `NaN`s are equivalent, but non-reflexive.
      // Object(NaN) is equivalent to NaN
      if (+a !== +a) return +b !== +b;
      // An `egal` comparison is performed for other numeric values.
      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    case '[object Date]':
    case '[object Boolean]':
      // Coerce dates and booleans to numeric primitive values. Dates are compared by their
      // millisecond representations. Note that invalid dates with millisecond representations
      // of `NaN` are not equivalent.
      return +a === +b;
  }
  if (typeof a != 'object' || typeof b != 'object') return false;
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  var length = aStack.length;
  while (length--) {
    // Linear search. Performance is inversely proportional to the number of
    // unique nested structures.
    if (aStack[length] === a) return bStack[length] === b;
  }
  // Objects with different constructors are not equivalent, but `Object`s
  // from different frames are.
  var aCtor = a.constructor, bCtor = b.constructor;
  if (
    aCtor !== bCtor &&
    // Handle Object.create(x) cases
    'constructor' in a && 'constructor' in b &&
    !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
      _.isFunction(bCtor) && bCtor instanceof bCtor)
  ) {
    return false;
  }
  // Add the first object to the stack of traversed objects.
  aStack.push(a);
  bStack.push(b);
  var size, result;
  // Recursively compare objects and arrays.
  if (className === '[object Array]') {
    // Compare array lengths to determine if a deep comparison is necessary.
    size = a.length;
    result = size === b.length;
    if (result) {
      // Deep compare the contents, ignoring non-numeric properties.
      while (size--) {
        if (!(result = eq(a[size], b[size], aStack, bStack))) break;
      }
    }
  }else {
    // Deep compare objects.
    var keys = _.keys(a), key;
    size = keys.length;
    // Ensure that both objects contain the same number of properties before comparing deep equality.
    result = _.keys(b).length === size;
    if (result) {
      while (size--) {
        // Deep compare each member
        key = keys[size];
        if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
      }
    }
  }
  // Remove the first object from the stack of traversed objects.
  aStack.pop();
  bStack.pop();
  return result;
};
 
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
  return eq(a, b, [], []);
}; 

原文链接:http://www.zuojj.com/archives/775.html,转载请注明转自Benjamin-专注前端开发和用户体验

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值