• 语法》第八章 运算符


    第二部分 语法
     
    **************第八章 运算符***************
     
    【阮一峰:运算符是处理数据的基本方法,用来从现有数据得到新的数据。】
     
    一、加法
    1.加法:js中可以完成两种运算;
     
    *算术加法;
     
    *字符串连接;
     
    “+”运算符在js中的算法如下:
     
        (1)如果运算子是对象,先自动转成原始类型的值(即先执行该对象的valueOf方法,如果结果还不是原始类型的值,再执行toString方法;如果对象是Date实例,则先执行toString方法)。
        (2)两个运算子都是原始类型的值以后,只要有一个运算子是字符串,则两个运算子都转为字符串,执行字符串连接运算。
        (3)否则,两个运算子都转为数值,执行加法运算。
    【valueOf()起什么作用?】
    '1' + {foo: 'bar'} // "1[object Object]"
    '1' + true // "1true"
    '1' + [1] // "11"
     
    2.这种由于参数不同,而改变自身行为的现象,叫做“重载”(overload)。由于加法运算符是运行时决定到底执行那种运算,使用的时候必须很小心。
     
    '3' + 4 + 5 // "345"
    3 + 4 + '5' // "75"
     
    3.变通的用法:可以用来将一个值转为字符串。
     
    x + ''
     
    4.
    [1, 2] + [3]
    // "1,23"
    // 等同于
    String([1, 2]) + String([3])
    // '1,2' + '3'
    // 结果"1,23"是由于两个数组被先转成字符串,这里js隐式调用过toString和ValueOf两种方法,但是究竟是那种方法起的作用,不同情况不一定;
     
    var arr=[1,2,3]
    alert(arr)
    alert(String(arr))
    弹出都是//1,2,3
    一个隐式调用了toString()方法转换数据类型,一个显示人为调用String方法,为什么打印到页面上不是[1,2,3],个人猜测是不同的子类在继承String方法时做了重写(规定了独属于自己的String方法),这些都是js编程语言内部事先约定好的规则,与编译器无关,与浏览器无关。
     
     
    5.加法运算符一定有左右两个运算子,如果只有右边一个运算子,就是另一个运算符,叫做“数值运算符, 数值运算符用于返回右边运算子的数值形式
     
    + - 3 // 等同于 +(-3) //输出-3
    + 1 + 2 // 等同于 +(1 + 2)//输出+3
    + '1' // 1 无疑,这里又隐式的做了数据类型转换
    + [1,2,3]  //NaN 
    + [1] // 1
    6.如果只有左边一个运算子,会报错。
    1 +//报错
     
    7.加法运算符以外的其他算术运算符(比如减法、除法和乘法),都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。个人猜测内部调用的Number方法
     
    实例:
    var now = new Date();
    typeof (now + 1) // "string"
    typeof (now - 1) // "number"
     
    二.算数运算符
    JavaScript提供9个算术运算符:
        加法运算符(Addition):x + y
        减法运算符(Subtraction): x - y
        乘法运算符(Multiplication): x * y
        除法运算符(Division):x / y
        余数运算符(Remainder):x % y
        自增运算符(Increment):++x 或者 x++
        自减运算符(Decrement):--x 或者 x--
        数值运算符(Convert to number): +x (下面俩可用来转数据类型)
        负数值运算符(Negate):-x
     
     
    1.余数运算符  Js中%运算符代表的是求余运算,不是python中的取模,差别只在两个运算子符号不同的时候能体现出来,【整数商*除数一定比被除数更靠近数轴的原点0!】
     
    前一个运算子被后一个运算子除所得的余数
    1.1运算结果正负号由第一个运算子决定
    -1 % 2 // -1 整商0
    1 % -2 // 1 整商0
    因此,为得到正确的负数的余数值,需要先使用绝对值函数
     
    1.2 余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。
    6.5 % 2.1// 0.19999999999999973
    【问题焦点在于你是否搞懂了取余和取模】
    1.2自增自减
    一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。
    var x = 1;
    ++x // 2
    x // 2
    --x // 1
    x // 1
    【a++与++a的区别】
    ++ -- 放在在变量之后,会先返回变量操作前的值,再进行自增/自减操作,在变量下次调用的时候就是新值;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。
     
    2.数值运算符、负数值运算符
    数值运算符+是一元运算符,需要一个操作数
    数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)。内部应该是调用了一次Number函数
     
    +true //1
    +[] // 0
    +{} // NaN
    非数值类型的值经过数值运算符以后,都变成了数值类型(最后一行NaN也是数值)
     
    负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符。
    var x = 1;
    -x // -1
    -(-x) // 1
    数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。
     
    3.赋值运算符 用于给变量赋值
    最常见的是等号= 表达式x = y表示将y的值赋给x
    JavaScript还提供其他11个复合的赋值运算符。
    x += y // 等同于 x = x + y
    x -= y // 等同于 x = x - y
    x *= y // 等同于 x = x * y
    x /= y // 等同于 x = x / y
    x %= y // 等同于 x = x % y
    x >>= y // 等同于 x = x >> y
    x <<= y // 等同于 x = x << y
    x >>>= y // 等同于 x = x >>> y
    x &= y // 等同于 x = x & y
    x |= y // 等同于 x = x | y
    x ^= y // 等同于 x = x ^ y
     
    都是先进行指定运算,然后将得到值返回给左边的变量
    =的优先级最低
     
     
    4.比较运算符
    比较运算符用于比较两个值,然后返回一个布尔值,表示是否满足比较条件。
    2 > 1 // true
    js提供了8个比较运算符
        == 相等
        === 严格相等
        != 不相等
        !== 严格不相等
        < 小于
        <= 小于或等于
        > 大于
        >= 大于或等于
     
    4.1比较运算符的算法
    比较运算符可以比较各种类型的值,不仅仅是数值。
    除了相等运算符号和精确相等运算符,其他比较运算符的算法如下。
     
    1. 如果两个运算子都是字符串,则按照字典顺序比较(实际上是比较Unicode码点)。
    2. 否则,将两个原始类型的运算子之间的比较 都转成数值,再进行比较(等同于先调用Number函数)。
    3. 运算子是符合类型、对象,必须先将其转为原始类型的值, 即先调用valueOf方法,如果返回的还是对象,再接着调用toString方法,valueOf和toString方法都使用过以后还不是原始类型会报错
      之后会根据另外一个运算子决定是否该再调用number再转数字
     
    5 > '4' // true
    // 等同于 5 > Number('4')
    // 即 5 > 4
     
    true > false // true
    // 等同于 Number(true) > Number(false)
    // 即 1 > 0
     
    2 > true // true
    // 等同于 2 > Number(true)
    // 即 2 > 1
     
     
    alert(Number({x: 2}) >= Number({x: 1})) //false 两个NaN比较
    alert({x: 2} >= {x: 1}) //true 
    两个对象都会被转成相同的字符串"[object Object]"
     
     
    var x = [2];
    x > '11' // true
    // 等同于 [2].valueOf().toString() > '11'
    // 即 '2' > '11'
    var x = [2];
    x > 11//false
    // 等同于Number( [2].valueOf().toString() )> 11
    //即2>11
     
    x.valueOf = function () { return '1' };
    x > '11' // false
    // 等同于 [2].valueOf() > '11'
    // 即 '1' > '11'
    两个对象之间的比较
    [2] > [1] // true
    // 等同于 [2].valueOf().toString() > [1].valueOf().toString()
    // 即 '2' > '1'
     
    [2] > [11] // true
    // 等同于 [2].valueOf().toString() > [11].valueOf().toString()
    // 即 '2' > '11'
     
    {x: 2} >= {x: 1} // true
    // 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString()
    // 即 '[object Object]' >= '[object Object]'
     
     
     
     
    4.2字符串的比较
    字符串按照字典顺序进行比较。
    比较的是每一个下标位置的字符的Unicode码点
    JavaScript 引擎内部首先比较首字符的 Unicode 码点,如果相等,再比较第二个字符的 Unicode 码点,以此类推。
    'cat' > 'Cat' // true'
    小写的c的 Unicode 码点(99)大于大写的C的 Unicode 码点(67),所以返回true
    由于所有字符都有 Unicode 码点,因此汉字也可以比较。
     
     
     
    严格相等运算符(===)比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符(===)直接返回false
    相等运算符(==):如果统一类型的值,比较值是否相等,就是看是不是同一个值,跟严格一样,否则会将它们转化成同一个类型,再用严格相等运算符进行比较。
     
    4.3严格相等运算符
     
    (1)不同类型的值:直接返回false
    (2)同一类的原始类型值:
    同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false
     
    *十进制的1与十六进制的1,因为类型和值都相同,返回true
    *NaN与任何值都不相等(包括自身)。另外,正0等于负0
    NaN === NaN  // false
    +0 === -0 // true
     
     
    (3)同一类的复合类型值:
    两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象,(比较的是是否指向同一个内存地址)
     
    {} === {} // false
    [] === [] // false
    (function (){} === function (){}) // false
    原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是false
     
    如果两个变量引用同一个对象,则它们相等。
    var v1 = {};
    var v2 = v1;
    v1 === v2 // true
     
    new Date() > new Date() // false
    new Date() >= new Date()   //true
    new Date() < new Date() // false
    new Date() === new Date() // false
    上面的三个表达式,前两个比较的是值,最后一个比较的是地址,所以都返回false
    alert(new Date()-new Date())//结果是0 中间经历了toString有没有ValueOf不确定,最后走一步Number
    typeof((new Date()).valueOf()) //"number"
    (4)undefined 和 null
     
    undefinednull与自身严格相等。
    变量声明后默认值是undefined,因此两个只声明未赋值的变量是相等的。
    var v1;
    var v2;
    v1 === v2 // true
     
    (5)严格不相等运算符
     
    严格相等运算符有一个对应的“严格不相等运算符”(!==),两者的运算结果正好相反。
     
     
    4.4相等运算符
    相等运算符比较相同类型的数据时,与严格相等运算符完全一样,看是不是一个值,
    所以比较,同一类的复合类型值,依然比的是内存地址
     
     
    比较不同类型的数据时,
    (1)原始类型的值
    原始类型的数据会转换成数值类型再进行比较。
    'true' == true // false
    // 等同于 Number('true') === Number(true)
    // 等同于 NaN === 1
     
    '' == 0 // true
    // 等同于 Number('') === 0
    // 等同于 0 === 0
     
    '' == false  // true
    // 等同于 Number('') === Number(false)
    // 等同于 0 === 0
     
    '1' == true  // true
    // 等同于 Number('1') === Number(true)
    // 等同于 1 === 1
     
    '   123  ' == 123 // true
    // 因为字符串转为数字时,省略前置和后置的空格
     
    原始都转数,对象转原始再转数

    (2)对象与原始类型值比较

     
    对象(这里指的符合数据类型,包括数组和函数)与原始类型的值比较时,对象转化成原始类型的值(先valueOf再toString),再走上面一步
    document.write(String([1]))这是显示调用String
    document.write([1])js内部隐式调用toString,隐式转换内部转换规则都是通过调用了ValueOf 和toString来实现的
    [1] == 1 // true
     
    [1] == '1' // true
     
    [1] == true // true
     
    数组[1]分别与数值、字符串和布尔值进行比较,会先转成字符串或数值,再进行比较。比如,与数值1比较时,数组[1]会被自动转换成数值1,因此得到true

    (3)undefined和null

     
    undefinednull与原始类型的值比较时,结果都为false,它们互相比较时结果为true
    false == null // falsefalse == undefined // false

    0 == null // false0 == undefined // false

    undefined == null // true
    绝大多数情况下,对象与undefinednull比较,都返回false。只有在对象转为原始值得到undefined时,才会返回true,这种情况是非常罕见的。
     
     
    (4)由于相等运算符不限制两个操作数的数据类型变化,各种坑,各种 隐藏的类型转换
    '' == '0'           // false  直接比
    0 == ''             // true  转数值比
    0 == '0'            // true   转数值比
     
    2 == true           // false
    2 == false          // false
     
    false == 'false'    // false
    false == '0'        // true
     
    false == undefined  // false
    false == null       // false
    null == undefined   // true
     
    ' ' == 0     // true
     

    (5)不相等运算符

     
    相等运算符有一个对应的“不相等运算符”(!=),两者的运算结果正好相反。
     
    5.布尔运算符
    用于将表达式转为布尔值。
     
    • 取反运算符:!
    • 且运算符:&&
    • 或运算符:||
    • 三元运算符:?:
     
    5.1!取反运算符
    用于将布尔值变为相反值,即true变成falsefalse变成true
    对于非布尔值的数据,取反运算符会自动将其转为布尔值,然后取反。我猜内部用Boolean方法,规则是,以下六个值取反后为true,其他值取反后都为false
     
     
    这意味着,取反运算符有转换数据类型的作用。
    !undefined // true
    !null // true
    !0 // true  (包括+0和-0)
    !NaN // true
    !"" // true
    !false//true
     
    !54 // false
    !'hello' // false
    ![] // false
    !{} // false
     
    5.1.1不管什么类型的值,经过取反运算后,都变成了布尔值。
     
    5.1.2如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。这是一种常用的类型转换的写法。
    两次取反就是将一个值转为布尔值的简便写法。
    !!x
    // 等同于Boolean(x)
    5.2且运算符&&
    且运算符的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值,如果是表达式则计算出结果);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值。
     
     
    't' && '' // ""
    't' && 'f' // "f"
    't' && (1 + 2) // 3
    '' && 'f' // ""
    '' && '' // ""
     
    var x = 1;
    (1 - 1) && ( x += 1) // 0
    x // 1
    由于且运算符的第一个运算子的布尔值为false,第一个运算子可能不是布尔值,这里会有一个转布尔值的隐式转换。
     
    这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if结构,比如下面是一段if结构的代码,就可以用且运算符改写。
    if (i) {
      doSomething();
    }
     
    // 等价于
     
    i && doSomething();
     
    5.3或运算符 ||
    且是false短路,或是true短路。
     
    或运算符(||)的运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,还会去算一下第二个运算子的值,不管它false还是true,返回第二个运算子的值。
    短路规则对这个运算符也适用。
     
    false || 0 || '' || 4 || 'foo' || true
    // 4
     

    运算符常用于为一个变量设置默认值。下面代码不严谨

    function saveText(text) {
     text = text || '';
      // ...
    }
    // 或者写成

    saveText(this.text || '')
     
    上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。默认往里传空字符串
     
    5.4三元条件运算符
    三元条件运算符用问号(?)和冒号(:),分隔三个表达式。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值。第一个值不是布尔类型会转换成布尔
     
    三元条件表达式与if...else语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,if...else是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用if..else
    console.log(true ? 'T' : 'F');
    console.log方法的参数必须是一个表达式,这时就只能使用三元条件表达式。如果要用if...else语句,就必须改变整个代码写法了。
    6.位运算符 暂时不看
     
    7.其他运算符
    7.1void运算符
    void运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
    void 0 // undefined
    void(0) // undefined
     
    7.1.1void运算符的两种写法,都正确。建议采用后一种形式,即总是使用括号。因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,void 4 + 7实际上等同于(void 4) + 7
     
    var x = 3;void (x = 5) //undefined
    x // 5
     
     
    7.1.2这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回undefined可以防止网页跳转。
     
    <a href="javascript:void window.open('http://example.com/')">
      点击打开新窗口
    </a>
    上面代码用于在网页中创建一个链接,点击后会打开一个新窗口。如果没有void,点击后就会在当前窗口打开链接。
     
    7.1.3下面是常见的网页中触发鼠标点击事件的写法。
     
    <a href="http://example.com" onclick="f();">文字</a>
     
    上面代码有一个问题,函数f必须返回false,或者说onclick事件必须返回false,内部机制如何未知,否则会引起浏览器跳转到example.com
    function f() {
      // some code
      return false;
    }
     
    <a href="http://example.com" onclick="f()(给click事件绑定一个函数执行某个功能,但是不让页面跳转);return false;">文字</a>
     
     
    7.1.4等价写法:用void运算符<a href="javascript: void(f())">文字</a>
     
    下面的代码会提交表单,但是不会产生页面跳转。
    <a href="javascript: void(document.form.submit())">
    文字</a>
     
    7.2逗号运算符:
    逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
    'a', 'b' // "b"
     
    var x = 0;
    var y = (x++, 10);
    x // 1
    y // 10
    逗号运算符返回后一个表达式的值。
    很难找到实际应用的场景,而且下面代码测试也不正确
    alert(1,2)//1
    alert((1,2))//2 【原因不详】
    8,运算顺序
    8.1优先级:
    var x = 1;
    var arr = [];

    var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
    根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。因此上面的表达式,实际的运算顺序如下。
    var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
     
    只要记忆常见优先级就可以
     
    8.2圆括号()
    优先级最高,可以提高运算的优先级
    括号中的表达式第一个运算
    由于运算符的优先级别十分繁杂,且都是来自硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。
    顺便说一下,圆括号不是运算符,不具有求值作用,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。
     
    var x = 1;
    (x) = 2;

    如果圆括号具有求值作用,那么就会变成1 = 2,这是会报错了。但是,上面的代码可以运行,这验证了圆括号只改变优先级,不会求值。

     
    这也意味着,如果整个表达式都放在圆括号之中,那么不会有任何效果。
    (exprssion)// 等同于expression
    圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。
     
    (var a = 1)// SyntaxError: Unexpected token var
     
    8.3左结合与右结合
    对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合,从左边开始计算
    少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(=)和三元条件运算符(?:
    w = x = y = z;
    q = a ? b : c ? d : e ? f : g;
    都是先计算最右边的那个运算符
    w = (x = (y = z));
    q = a ? b : (c ? d : (e ? f : g));
  • 相关阅读:
    生成函数trick
    带权并查集维护二分图
    关于二项式反演的一些思考
    CSP集训记录
    解决Maven版本冲突
    蚂蚁金服5轮面试,最后栽这了...
    配置交换机Eth-Trunk+VRRP+MSTP+接口BFD状态联动+Telnet示例
    企业园区网络建设技术方案(华为)
    网络三层架构
    SOA治理
  • 原文地址:https://www.cnblogs.com/xsfx/p/7123415.html
Copyright © 2020-2023  润新知