美文网首页
JS基础回顾-运算符1

JS基础回顾-运算符1

作者: 火锅伯南克 | 来源:发表于2018-12-16 20:17 被阅读0次

    在不更,基本也就告别简书了.
    提出几个案例,咱们从后往前推,看看一个小玩意到底牵扯了多少知识点

    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 ,
    1. 如果a和b都为真,那么a&&b就返回 b,在运算b||c,b是真,
      b||c 肯定就返回了 b;
    2. 如果a为假,b为真,那么a&&b就返回 false,在运算false||c,此时无论c为真或假,c的值都会被返回。
    此时看着貌似和三目运算符是一样的对吧?再看第三条
    1. 如果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。
    
    

    收工。

    相关文章

      网友评论

          本文标题:JS基础回顾-运算符1

          本文链接:https://www.haomeiwen.com/subject/lhxamftx.html