主要讲述[] {} 以及 JavaScript中的类型转换
主要参考文章(非常推荐):https://segmentfault.com/a/1190000008432611#articleHeader1
首先看一些示例:
[]==[] //false
[]==![] //true
{}==!{} //false
{}==![] //VM1896:1 Uncaught SyntaxError: Unexpected token ==
![]=={} //false
[]==!{} //true
undefined==null //true
1.[] == [] 为什么是false?
有点js基础应该知道对象是引用类型,就会一眼看出来[] == []会输出false,因为左边的[]和右边的[]看起来长的一样,但是他们引用的地址并不同,所以返回false。
我们可以学习下JavaScript中内存空间的概念,可以参考:https://www.jianshu.com/p/996671d4dcc4

简单类型都放在栈(stack)里
对象类型都放在堆(heap)里
var a = 20;
var b = 'abc';
var c = true;
var d = { m: 20 }//地址假设为0x0012ff7c
var e = { m: 20 }//重新开辟一段内存空间假设为0x0012ff8f
console.log(e==d);//false
那为什么引用值要放在堆中,而原始值要放在栈中,不都是在内存中吗,为什么不放在一起呢?
function Person(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
var num = 10;
var bol = true;
var str = "abc";
var obj = new Object();
var arr = ['a','b','c'];
var person = new Person(100,"笨蛋的座右铭",25);
然后我们来看一下内存分析图:

变量num,bol,str为基本数据类型,它们的值,直接存放在栈中,obj,person,arr为复合数据类型,他们的引用变量存储在栈中,指向于存储在堆中的实际对象。
由上图可知,我们无法直接操纵堆中的数据,也就是说我们无法直接操纵对象,但我们可以通过栈中对对象的引用来操作对象,就像我们通过遥控机操作电视机一样,区别在于这个电视机本身并没有控制按钮。
现在让我们来回答为什么引用值要放在堆中,而原始值要放在栈中的问题:
记住一句话:能量是守衡的,无非是时间换空间,空间换时间的问题 堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,并且可以自由扩展,如:数组可以无限扩充,对象可以自由添加属性。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。相对于简单数据类型而言,简单数据类型就比较稳定,并且它只占据很小的内存。不将简单数据类型放在堆是因为通过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。所以简单数据类型的值直接存放在栈中。
另一个关于原始值和引用值存储的示例:
var a1 = 0; // 变量对象
var a2 = 'this is string'; // 变量对象
var a3 = null; // 变量对象
var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中

从一道测试题去看原始值和引用值的区别:
// demo01.js
var a = 20;
var b = a;
b = 30;
// 这时a的值是多少?20
// demo02.js
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 这时m.a的值是多少 30
变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。

在demo02中,我们通过var n = m执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在变量对象中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。如图所示。
因此当我改变n时,m也发生了变化。这就是引用类型的特性。


通过内存的角度来理解,是不是感觉要轻松很多?除此之外,我们还可以以此为基础,一步一步的理解JavaScript的执行上下文,作用域链,闭包,原型链等重要概念。其他的我会在以后的文章慢慢总结,敬请期待。
2.[]==![]为什么是true?
ECMAScript规范里面==的真正含义:
执行相等比较运算符的结果总是Boolean类型。表示是否由运算符指定的关系对两操作数成立。
产生EqualExpression == RelationalExpression 按照下面的过程执行:
1. 令Iref为解释执行EqualityExpression的结果
2. 令Ival为GetValue(Iref)
3. 令rref为解释执行EealtionalExpression的结果
4. 令rval为GetValue(rref)
5. 返回做用相等比较算法于rval==lval的结果
GetValue 会获取一个子表达式的值(消除掉左值引用),在表达式 [] == ![] 中,[] 的结果就是一个空数组的引用(上文已经介绍到数组是引用类型),而 ![] 就有意思了,它会得到 false。
我们先了解一下运算符的优先级:

!取反运算符的优先级会高于==,所以我们先看看!在ECAMScript是怎么定义的?
!UnaryExpression按照下面的过程执行:
- 令expr为解释执行UnaryExpression的结果
- 令oldValue为ToBoolean(GetValue(expr))
- 因为是!,所以如果oldValue是true,最后返回false
- 否则返回true
![] //false
所以![]最后会是一个Boolean类型的值。
抽象相等比较算法:
比较运算x==y,其中x和y是值,产生true或者false。这样的比较按如下方式进行:

在这段算法里,[]是Object,![]是Boolean,两者的类型不同,y是Boolean类型,由此可知[] == ![]匹配的是条件 8,所以会递归地调用[] == ToNumber(Boolean)进行比较。
ToBoolean:

[]是一个对象,所以对应转换成Boolean对象的值为true;那么![]对应的Boolean值就是false
进而就成了比较[] == ToNumber(false)了。
再来看看ECMAScript规范中对于toNumber的转换:

由此可以得出此时的比较成了[]==0;
在此处因为 [] 是对象,0是数字Number,比较过程走分支 10(若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。),可以对比上面那张图.
ToPrimitive 默认是调用 toString 方法的(依 8.2.8),于是 ToPrimitice([]) 等于空字符串。
ECMAScript标准定义ToPrimitice方法:

查了一下资料,上面要说的可以概括成:
ToPrimitive(obj,preferredType)
JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个preferredType为希望转换成的类型(默认为空,接受的值为Number或String)。
在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的事例时,此时preferredType会被设置为String,其他情况下preferredType都会被设置为Number。
如果preferredType为Number,ToPrimitive执行过程如下:
- 如果obj为原始值,直接返回;
- 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
- 否则调用 obj.toString(),如果执行结果是原始值,返回之;
- 否则抛异常。
如果preferredType为String,将上面的第2步和第3步调换,即:
- 如果obj为原始值,直接返回;
- 否则调用 obj.toString(),如果执行结果是原始值,返回之;
- 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
- 否则抛异常。
首先我们要明白obj.valueOf()和 obj.toString()还有原始值分别是什么意思,这是弄懂上面描述的前提之一:
toString用来返回对象的字符串表示。
var obj = {};
console.log(obj.toString());//[object Object]
var arr2 = [];
console.log(arr2.toString());//""空字符串
var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (中国标准时间)
valueOf方法返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象。
var obj = {
name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}
var arr1 = [1];
console.log(arr1.valueOf());//[1]
var date = new Date();
console.log(date.valueOf());//1456638436303
如代码所示,三个不同的对象实例调用valueOf返回不同的数据
弄清楚这些之后,我们举个例子:
var a={};
ToPrimitive(a)
分析:a是对象类型但不是Date实例对象,所以preferredType默认是Number,先调用a.valueOf()不是原始值,继续来调用a.toString()得到string字符串,此时为原始值,返回之.所以最后ToPrimitive(a)得到就是"[object Object]".
分析了这么多,刚才分析到哪里了,好像到了比较ToPrimitive([]) == 0现在我们知道ToPrimitive([])="",也就是空字符串;
那么最后就变成了""==0这种状态,继续看和比较这张图:

发现typeof("")为string,0为number,发现第5条满足规则,最后就成了toNumber("")==0的比较了,根据toNumber的转换规则:

所以toNumber("")=0,最后也就成了0 == 0的问题,于是[]==![]最后成了0 == 0的问题,答案显而易见为true,一波三折
3.最后总结一下

前面说得很乱,根据我们得到的最终的图3,我们总结一下==运算的规则:
-
undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
-
String == Boolean,需要两个操作数同时转为Number。
-
String/Boolean == Number,需要String/Boolean转为Number。
-
Object == Primitive,需要Object转为Primitive(具体通过valueOf和toString方法)。
瞧见没有,一共只有4条规则!是不是很清晰、很简单。
3.+!{}[true]输出1的总结:

本文深入探讨了JavaScript中数组、空对象以及类型转换的关系,通过实例分析了[] == []为何为false,[] == ![]为何为true,并解释了+!{}[true]输出1的原因,揭示了JavaScript中相等运算符背后的逻辑和类型转换规则。

1318

被折叠的 条评论
为什么被折叠?



