在不更,基本也就告别简书了.
提出几个案例,咱们从后往前推,看看一个小玩意到底牵扯了多少知识点
1. a+++a;
2. a++ + ++a;
3. a+b++;
4. +!!a++;
5. a?b:c?d:e?f:g;
6. [ ] == ![ ]
7. a?b:c === a&&b||c
一、优先级 => 关联性(结合性) =>运算顺序
1、优先级
我们在上数学课的时候,经常会听老师叨叨:'先算乘除,再算加减,遇到小括号先算括号里面的'.这个就是典型的运算优先级,小括号>乘除>加减,js中也是一样,
var a = 2*(2+3);
2、关联性(结合性)
当一条表达式的运算符优先级都是同级时,我们就需要考虑此类运算符的关联性,也就是说遇到多个同级操作符相连接的表达式时,我们是从左开始算还是从右开始算由关联性决定
var a = 2*3/8*9; //从左向右运算
var b = !+!!a; //从右向左运算
3、运算顺序
JS运算顺序永远都是从上到下,从左到右
运算符.jpg
总结一个运算口诀就是‘先算谁(优先级)’,‘怎么算(关联性)’,
测验:
var a = 2*(2+3)/+![0][0]++;//能否口算结果?
拆分运算步骤
//圆括号优先级19,最高,得出
a = 2*5/+![0][0]++;
//属性访问优先级18,仅次于圆括号,得出
a = 2*5/+!0++; //注意,此行代码直接运行会报错,原因++操作符只能操作左值,此处只为展示
//++运算符优先级16,得出
a = 2*5/+!0;
//一元操作符优先级15,
//并且+与!都为同一优先级,开始考虑关联性,一元操作符的关联性为从右到左运算,得出
a = 2*5/+true; a = 2*5/1;
//二元操作符*,/优先级14,
//并且*与/都为同一优先级,开始考虑关联性,二元操作符的关联性为从左到右运算,得出
a = 10/1 ; a = 10;
//最后为 = 赋值运算符,它的优先级只比,高,
//所以 10 被赋值给了 a(栈内存保存数据),表达式运算结束
注:由于JS运算顺序永远都是从上到下,从左到右,所以不重点强调。
经过这个貌似变态的例子的解析过程,我们终于可以高呼“so easy!”
由此,我们可以解决开始提出的1 - 5,和7的问题了。我在这里就不占用太多篇幅了。
//第5题可以这样改写,容易理解
a?b:c?d:e?f:g ==> a?b:(c?d:(e?f:g))
第7题: a?b:c 与a&&b||c的运算原理是不是一致得?
答:不是 。很多人以为三元运算符(也称三目运算符)就是 xx&&xx||xx的一个变体,然而并不是这样。以题目例子为例,我们可以这样写(a&&b)||c ,
- 如果a和b都为真,那么a&&b就返回 b,在运算b||c,b是真,
b||c 肯定就返回了 b;- 如果a为假,b为真,那么a&&b就返回 false,在运算false||c,此时无论c为真或假,c的值都会被返回。
此时看着貌似和三目运算符是一样的对吧?再看第三条
- 如果a为真,b为假,那么a&&b就返回 false,在运算false||c,此时无论c为真或假,c的值都会被返回。
这就不对了,我们看一段代码:
所以,再有人跟你写这种奇葩格式,你可以狠狠地回绝他。
可能会遇到的困惑:
为什么a+++++a会报错,而a+++a就不会呢?
答:我也不知道,可能规则如此
二、JS中 等值运算符(==) 涉及到的隐式类型转换
1、先来看对象如何转换成基础数据类型
var a = {
toString:function(){
console.log('执行了toString方法');
return this;
},
valueOf:function(){
console.log('执行了valueOf方法');
return this;
}
}
我们创建了一个对象a,再对这个a的toString和valueOf方法进行了重写,这样做的目的就是要验证一下,一个对象在转化为基础数据类型的时候JS解释器是如何帮我们转化的.
var b = String(a);
图S
先解释一下为什么报错,在把对象转化为基础数据类型时,js解释器会调用此对象的toString方法或者valueOf方法,总之就是要把对象转化成一个基础数据类型再进行转化,但是由于我们重写了这两个方法,返回的值并不是一个基础数据,js解释器就会报出异常:不能将对象转换为原始值(原始值就是基础数据);
var b =Number(a);
我们可以借此规律去验证一些平时使用的方法的数据转换规律
alert(a);
由此,我们得出结论:
把一个对象转化为字符串时,会先调用对象的toString方法,如果无法得到原始值,再调用valueOf方法,如果还得不到原始值,则报错
把一个对象转化为数字时,会先调用对象的valueOf方法,如果无法得到原始值,再调用toString方法,如果还得不到原始值,则报错
alert()方法是把参数的数据转化成字符串,然后再弹框显示的
补充:如果是单纯的把对象转换成原始值,所有对象都会先调用valueOf方法,再调用toString方法。
说到alert,我想起了两个面试题
① alert( 1,2,3,4,5)
② alert((1,2,3,4,5))
第一个答案是1,虽然alert是浏览器实现的ui方法,但是本质还是一个函数.而且这个函数只有一个参数,所以得到1是自然而然的事.
第二个答案是5,这个有点迷惑性,分清两个括号,期中alert()为函数调用表达式,(1,2,3,4,5)为一个单独的表达式,拆分一下,相当于var a = (1,2,3,4,5); alert(a);先计算括号里面的.再说逗号运算符.
它将先计算左边的参数,再计算右边的参数值。然后返回最右边参数的值。
举个例子:
var n1 = 1;
var n2 = 2;
var n3 = (n1++,++n2);
console.log(n1,n2,n3) //2,3,3
注意:并不是所有数据构造器原型上的的toString方法运行机制都是一样的,几乎每个构造器都对toString方法进行了重写,我们可以验证:
var obj = new Object().toString();
var arr = new Array(1,2).toString();
var date = new Date().toString();
console.log(obj + '\n' + arr + '\n' + date);
array.png
object.png
当然,我们也可以把基础数据类型转换成对象
//string、number、boolean类型都可以通过自身的构造器new出一个新对象,
//这个对象与object构造器new出的效果是一样的
var str = new String('a');
var strO = new Object('a');
var num = new Number(1);
var numO = new Object(1);
var boo = new Boolean(false);
var booO = new Object(false);
//undefined和null在js解释器内没有构造器,所以不能直接new对应类型,只能通过 Object构造器
//undefined和null转成对象都是空对象,老版浏览器也有可能报错
var undO = new Object(undefined);
var nulO = new Object(null);
console.log(str,strO,num,numO,boo,booO,undO,nulO);
打印结果
2、再来看ECMAScript中对于 == 运算符运算规则的定义,(任何看似杂乱无章的转化其实都是有规律可循的,善于查看规范)
1.若Type(x)与Type(y)相同, 则
a.若Type(x)为Undefined, 返回true。
b.若Type(x)为Null, 返回true。
c.若Type(x)为Number, 则
Ⅰ.若x为NaN, 返回false。
Ⅱ.若y为NaN, 返回false。
Ⅲ.若x与y为相等数值, 返回true。
Ⅳ.若x 为 +0 且 y为−0, 返回true。
Ⅴ.若x 为 −0 且 y为+0, 返回true。
d.若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
e.若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
f.当x和y为引用同一对象时返回true。否则,返回false。
2.若x为null且y为undefined, 返回true。
3.若x为undefined且y为null, 返回true。
4.若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
5.若Type(x) 为 String 且 Type(y)为Number, 返回比较ToNumber(x) == y的结果。
6.若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
7.若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
8.若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
9.若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
之前我发过这个表,但是有同学反应有点乱,那我就翻译一个更乱的:
x == y
1.如果变量x与变量y的数据类型相同,则
a.如果变量x为Undefined类型,那么变量y也是Undefined类型,Undefined类型只有一个值,那就是undefined,所以可得结论:
undefined == undefined 返回true
b.如果变量x为Null类型,那么变量y也是Null类型,Null类型只有一个值,那就是null,所以可得结论:null == null 返回true
注:如果想要判断一个数据是不是Null类型,不要使用typeof进行判断,因为typeof null 返回的是'object',原因是js解释器设定,
如果一个数据的二进制编码前三位都为0的话,用typeof判断就会返回'object',而null数据的二进制编码所有位数都是0.所以返回'object',
可能就是因为这个原因,在ES3版中新增了一个数据类型 undefined,以弥补null的不足;
如果我们想判断一个数据是否为null,可以使用 (数据 === null),返回true,则为null。或者使用
Object.prototype.toString.call(数据),返回'[object Null]',则为null;
c.如果变量x位数字类型,则:
Ⅰ.和Ⅱ.的意思是,无论x和y谁为NaN,返回的结果都为false,因为NaN不等于任何数值,包括它本身
Ⅲ.数值相等,返回true,不用解释;
Ⅳ.和Ⅴ. 在js中,0有两种表示方式,0和-0,但是这两个值是完全相等的,0===-0 也会返回 true
d.如果 == 两边都是字符串,那么将会依次判断==两边索引相同的字符在Unicode字符集中的编码值是否相等;举个例子:'abc' == 'abd' ;
解释器会先判断'abc'的'a'与'abd'的'a'在Unicode字符集中的编码值是否相等(获取一个字符的在Unicode字符集中的编码值的方法是:charCodeAt(索引)
比如要获取'abc'的a的编码值 =>'abc'.charCodeAt(0) ,得到97),相等就去比较'b'和'b',以此类推,直到找到不相等的项,返回false。
或者全部检索完成相等,返回true。
{注意:javaScript采用Unicode的国际化标准,而Unicode目前普遍采用的是UCS-2,使用2个字节,16进制无疑是最合适的表现手段,
\u + 4位16进制在javaScript中也可表示一个字符,并且和对应字符是完全相等的。比如,'我' === '\u6211'返回true;
我们可以如下手段把普通字符转换成Unicode标准形式,来加深印象:
var str = '我';
var code = str.charCodeAt(0); //获取Unicode对应编码;
var code16 = code.toString(16); //把获取到的编码转换成16进制
var unnicodeStr = eval('"'+'\\u' + code16+'"'); //在转换的4位16进制前 + '\u';
str === unnicodeStr; //返回true
又或者:
var str = '我';
var code = str.charCodeAt(0); //获取Unicode对应编码;
var unnicodeStr = String.fromCharCode(code);//直接获取Unicode对应字符,但是就和'\u'没什么关系了
str === unnicodeStr; //返回true
如果还有疑惑,可以搜索关键字['ASCII','Unicode','utf-8','utf-16','utf-32','GBK']
}
e.这个很好理解;
f.还记得我们说过的基础数据和复杂数据的区别吗,这段规则的潜在台词就是:任何两个独立的对象都是不相等的,比如:
var a = [];var b = []; a==b返回false
但是,如果他们引用了同一个对象的内存地址,就完全相等了:
var a = []; var b = a; a==b返回true,a===b返回true.或者
var a = b = [];a==b返回true,a===b返回true //强烈不推荐这么写,因为此时声明的a变量是局部的,但是b却是全局的,
//而且连等赋值同一对象的内存地址,会给你造成很多意想不到的麻烦
var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a,b) //{n:2},{n:1,x:{n:2}}
2.和3.的潜在台词就是,null和undefined在和其他数据进行==比较时,不会进行隐式类型转换,但同时又规定,
null和undefined是相等的(可能因为undefined是null的延伸的历史原因)。
[undefined == undefined,null == null, null == undefined,undefined == null]
除了这四种情况返回true外,任何null和undefined参与的==比较全部返回false。
4.和5.表示 如果==两边为number和string类型,那么会把两边都转换成数字类型,在进行比较。
6.和7.表示 如果boolean类型在参与==比较时,会把自身转换成数字类型,在进行比较。
8.和9.表示 除了对象与对象==比较时比较的是内存地址外,对象与任何数据进行==比较都会先把自身转换成原始值
(也就是我们提到的先调用自身的valueOf(),再调用toString()),再参与比较
说了这么多,终于可以解析提到的6. [ ] == ![ ] 的问题
解析步骤:
[ ] == ![ ]
1.首先会先运算 ![ ],任何对象在转换boolean时,都会返回true,再取反,得到false。得出:
[ ] == false;
2.然后开始==比较,从左向右运算,根据== 运算符运算规则,[ ]会转换成原始值,调用valueOf(),再调用toString()。得到
空字符串 '',得出:
'' == false;
3.根据== 运算符运算规则7.,要把false转换成数字,得到 0 ,得出:
'' == 0;
4.根据== 运算符运算规则5.,要把''转换成数字,得到 0 ,得出:
0 == 0;
返回true。
收工。
网友评论