你是否在面试中遇到过各类奇葩和比较细节的问题?html
[]==[] //false []==![] //true {}==!{} //false {}==![] //VM1896:1 Uncaught SyntaxError: Unexpected token == ![]=={} //false []==!{} //true undefined==null //true
看了这种题目,是否是想抽面试官几耳光呢?哈哈,是否是看了以后一脸懵逼,两脸茫然呢?心想这什么玩意前端
其实这些都是纸老虎,知道原理和转换规则,理解明白这些很容易的,炒鸡容易的,真的一点都不难,咱们要打到一切纸老虎,不信?git
咱们就从[] == []和[] == ![]例子切入分析一下为何输出的结果是true而不是其它的呢?github
有点js基础应该知道对象是引用类型,就会一眼看出来[] == []会输出false,由于左边的[]和右边的[]看起来长的同样,可是他们引用的地址并不一样,这个是同一类型的比较,因此相对没那么麻烦,暂时不理解[] == []为false的童鞋这里就不细说,想要弄清楚能够经过这篇文章来了解JavaScript的内存空间详解.面试
变量对象与堆内存数组
简单类型都放在栈(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);
而后咱们来看一下内存分析图:ui
`变量num,bol,str为基本数据类型,它们的值,直接存放在栈中,obj,person,arr为复合数据类型,他们的引用变量存储在栈中,指向于存储在堆中的实际对象。
由上图可知,咱们没法直接操纵堆中的数据,也就是说咱们没法直接操纵对象,但咱们能够经过栈中对对象的引用来操做对象,就像咱们经过遥控机操做电视机同样,区别在于这个电视机自己并无控制按钮。
`this
`记住一句话:能量是守衡的,无非是时间换空间,空间换时间的问题
堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,而且能够自由扩展,如:数组能够无限扩充,对象能够自由添加属性。将他们放在堆中是为了避免影响栈的效率。而是经过引用的方式查找到堆中的实际对象再进行操做。相对于简单数据类型而言,简单数据类型就比较稳定,而且它只占据很小的内存。不将简单数据类型放在堆是由于经过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。因此简单数据类型的值直接存放在栈中。`
搬运文章:理解js内存分配
首先第一步:你要明白ECMAScript规范里面==的真正含义
GetValue 会获取一个子表达式的值(消除掉左值引用),在表达式 [] == ![] 中,[] 的结果就是一个空数组的引用(上文已经介绍到数组是引用类型),而 ![] 就有意思了,它会按照 11.4.9 和 9.2 节的要求获得 false。
首先咱们了解一下运算符的优先级:
刚看到这里是否是就咬牙切齿了,好戏还在后头呢,哈哈
!取反运算符的优先级会高于==,因此咱们先看看!在ECAMScript是怎么定义的?
因此![]最后会是一个Boolean类型的值(这点很关键,涉及到下面的匹配选择).
二者比较的行为在 11.9.3 节里,因此翻到 11.9.3:
在这段算法里,[]是Object,![]是Boolean,二者的类型不一样,y是Boolean类型,由此可知[] == ![]匹配的是条件 8,因此会递归地调用[] == ToNumber(Boolean)进行比较。
[]空数组转化成Boolean,那么这个结果究竟是true仍是false呢,这个固然不是你说了算,也不是我说了算,ECMAScript定义的规范说了算:咱们来看看规范:
[]是一个对象,因此对应转换成Boolean对象的值为true
;那么![]对应的Boolean值就是false
进而就成了比较[] == ToNumber(false)了
再来看看ECMAScipt规范中对于Number的转换
由此能够得出此时的比较成了[]==0;
在此处由于 [] 是对象,0是数字Number,比较过程走分支 10(若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。),能够对比上面那张图.
ToPrimitive
默认是调用 toString
方法的(依 8.2.8),因而 ToPrimitice([]) 等于空字符串。
再来看看ECMAScript标准怎么定义ToPrimitice方法的:
是否是看了这个定义,仍是一脸懵逼,ToPrimitive这尼玛什么玩意啊?这不是等于没说吗?
再来看看火狐MDN上面文档的介绍:
JS::ToPrimitive
查了一下资料,上面要说的能够归纳成:
ToPrimitive(obj,preferredType) JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个 preferredType为但愿转换成的类型(默认为空,接受的值为Number或String) 在执行ToPrimitive(obj,preferredType)时若是第二个参数为空而且obj为Date的事例时,此时preferredType会 被设置为String,其余状况下preferredType都会被设置为Number若是preferredType为Number,ToPrimitive执 行过程如 下: 1. 若是obj为原始值,直接返回; 2. 不然调用 obj.valueOf(),若是执行结果是原始值,返回之; 3. 不然调用 obj.toString(),若是执行结果是原始值,返回之; 4. 不然抛异常。 若是preferredType为String,将上面的第2步和第3步调换,即: 1. 若是obj为原始值,直接返回; 2. 不然调用 obj.toString(),若是执行结果是原始值,返回之; 3. 不然调用 obj.valueOf(),若是执行结果是原始值,返回之; 4. 不然抛异常。
首先咱们要明白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返回不一样的数据
**原始值指的是['Null','Undefined','String','Boolean','Number']五种基本数据类型之一(我猜的,查了一下
确实是这样的)**
弄清楚这些之后,举个简单的例子:
var a={}; ToPrimitive(a) 分析:a是对象类型但不是Date实例对象,因此preferredType默认是Number,先调用a.valueOf()不是原始值,继续来调 用a.toString()获得string字符串,此时为原始值,返回之.因此最后ToPrimitive(a)获得就是"[object Object]".
若是以为描述还很差明白,一大堆描述晦涩又难懂,咱们用代码说话:
const toPrimitive = (obj, preferredType='Number') => { let Utils = { typeOf: function(obj) { return Object.prototype.toString.call(obj).slice(8, -1); }, isPrimitive: function(obj) { let types = ['Null', 'String', 'Boolean', 'Undefined', 'Number']; return types.indexOf(this.typeOf(obj)) !== -1; } }; if (Utils.isPrimitive(obj)) { return obj; } preferredType = (preferredType === 'String' || Utils.typeOf(obj) === 'Date') ? 'String' : 'Number'; if (preferredType === 'Number') { if (Utils.isPrimitive(obj.valueOf())) { return obj.valueOf() }; if (Utils.isPrimitive(obj.toString())) { return obj.toString() }; } else { if (Utils.isPrimitive(obj.toString())) { return obj.toString() }; if (Utils.isPrimitive(obj.valueOf())) { return obj.valueOf() }; } } var a={}; ToPrimitive(a);//"[object Object]",与上面文字分析的一致
分析了这么多,刚才分析到哪里了,好像到了比较ToPrimitive([]) == 0如今咱们知道ToPrimitive([])="",也就是空字符串;
那么最后就变成了""==0这种状态,继续看和比较这张图
发现typeof("")为string,0为number,发现第5条知足规则,最后就成了toNumber("")==0的比较了,根据toNumber的转换规则:
因此toNumber("")=0,最后也就成了0 == 0的问题,因而[]==![]最后成了0 == 0的问题,答案显而易见为true,一波三折
==运算规则的图形化表示
前面说得很乱,根据咱们获得的最终的图3,咱们总结一下==运算的规则: 1. undefined == null,结果是true。且它俩与全部其余值比较的结果都是false。 2. String == Boolean,须要两个操做数同时转为Number。 3. String/Boolean == Number,须要String/Boolean转为Number。 4. Object == Primitive,须要Object转为Primitive(具体经过valueOf和toString方法)。 瞧见没有,一共只有4条规则!是否是很清晰、很简单。
喜欢我总结的文章对你有帮助有收获的话麻烦点个star
个人github博客地址,总结的第一篇,很差之处和借鉴不获得之处还望见谅,您的支持就是个人动力!