JavaScript权威指南(4)——表达式和运算符

490 阅读9分钟

表达式和运算符

原始表达式

  1. 原始表达式包括常量或直接量、关键字和变量

对象和数组的初始化表达式

  1. 这些表达式可以嵌套

函数定义表达式

  1. 函数表达式可称为函数直接量

属性访问表达式

  1. 表达式后跟句点和标识符,或者使用方括号

调用表达式

  1. 对调用表达式进行求值时,先计算函数表达式,再计算参数表达式,得到一组参数值

对象创建表达式

  1. 和函数调用表达式类似,多了一个关键字new

运算符概述

  1. JavaScript运算符

  1. 操作数的个数:大多数是二元运算符,还有一元运算符(-x),三元运算符(?:)
  2. 操作数类型和结果类型——运算符会根据需要对操作数进行类型转换
  3. 左值——表达式只能出现在赋值运算符的左侧
  4. 运算符的副作用——赋值,如++,--,delete,如果在函数体或者构造函数内部运用了这些运算符并产生了副作用的时候,可以说函数调用表达式和对象创建表达式是有副作用的
  5. 运算符优先级——赋值运算的优先级非常低,可以用圆括号强行指定运算次序
  6. 运算符的结合性——上表中L指从左至右结合,R指从右至左结合
  7. 运算顺序——按照从左至右的顺序来计算表达式

算术表达式

  1. 如果操作数是NaN值,算术运算的结果也是NaN
  2. “+”优先考虑字符串连接,如果其中一个操作数是字符串或者转换为字符串的对象,另一个操作数将会转换为字符串,加法将进行字符串的连接操作
    1. 如果其中一个操作数是对象,则对象遵循对象到原始值的转换规则转换为原始类值:日志通过toString执行转换,其他对象则通过valueOf执行转换。多数对象不具备可用的valueOf,会通过toString执行转换
    2. 在进行了对象到原始值的转换后,如果其中一个操作数是字符串,另一个也会转换为字符串,进行字符串连接
    3. 否则,两个操作数都转换为数字(或NaN),进行加法操作
  3. 一元运算符——优先级高,且都是右结合(一元+,-,++,--)
  4. 位运算符——&,|,^,~,<<,>>,>>>无符号右移

关系表达式

  1. ===严格相等运算符,==相等运算符,允许类型转换
  2. JavaScript对象的比较是引用的比较,而不是值的比较
  3. ===的比较过程:
    1. 如果两个值类型不同,则不相等
    2. 如果两个值都是null或或者undefined,则不相等
    3. 如果连个值都是true或者都是false,则相等
    4. 如果其中一个是NaN,或者两个都是NaN,则不相等,NaN和任何值都不相等,包括它本身。通过x!==x来判断x是否为NaN,只有在x为NaN的时候,才为true
    5. 如果两个值为数字,且数值相等,则相等,如果一个是0,另一个是-0,则相等
    6. 如果两个值是字符串,且所含对应位上的16位数完全相等,则相等。如果它们的长度或内容相等,则它们不等。两个字符串可能含义完全一样且所显示出的字符也一样,但具有不同编码的16位值。JavaScript并不对Unicode进行标准化的转换,因此像这样的字符串通过“===”和“==”运算符的比较结果也不相等。
    7. 如果两个引用值指向同一个对象、数组或函数,则相等。如果指向不同的对象,则不等。
  4. ==的比较过程
    1. 如果两个操作数的类型相同,则和===的规则一样,如果严格相等,那么结果为相等
    2. 类型不同,“==”也可能认为它们相等,需要遵守如下规则和类型转换:
      1. 一个值是null,另一个是undefined,则它们相等
      2. 如果一个是数字,另一个是字符串,先将字符串转换为数字,然后再比较
      3. 如果一个是true,先转换为1再比较,如果是false,转换为0
      4. 如果一个是对象,另一个是数字或字符串,则将对象转换为原始值,再进行比较。内置类型先尝试使用valueOf,再使用toString。日期类只使用toString
      5. 其他类型均不相等
  5. 比较运算符——只有数字和字符串才能执行比较操作,类型转换规则
    1. 对象转换为原始值
    2. 如果一个操作数不是字符串,那么两个操作数都转换为数字进行数值比较
    3. 0和-0相等,Infinity比其他数字大,-Infinity比其他数字小,如果一个操作数是NaN,那么总是返回false
  6. in运算符
  7. instanceof运算符——原型链

逻辑表达式

  1. &&当操作数都是布尔值,执行与操作
  2. &&可以对真值和假值进行与操作,如果都是真值则返回一个真值,否则返回一个假值
  3. &&的短路行为
  4. ||对布尔值进行运算。如果其中一个或两个操作数是真值,它返回一个真值。如果两个是假值,则返回几个假值。
  5. !x,如果x是真值,返回false,如果是假值,返回true

赋值表达式

  1. (a==b) == 0,=优先级较低,需要用圆括号
  2. =的结合性是从右到做——一个表达式中出现多个赋值运算符,运算顺序是从右到左
  i=j=k=0   //三个变量初始化为0
  1. 带操作的赋值运算

    Operator Example Equivalent

    >>= a >>= b a = a >> b
    >>>= a >>>= b a = a >>> b
    &= a &= b a = a & b
    |= a |= b a = a | b
    ^= a ^= b a = a ^ b

表达式计算

  1. eval()是一个函数,被当做运算符来对待。①用于动态执行的代码通常来讲是不能分析,解释器无法对它做进一步的优化;②它可以被赋予其他名字,如var f =eval则解释器将无法放心地优化任何调用f()的函数。如果eval是一个运算符,可以避免该问题。
  2. eval()只有一个参数。
    1. 如果参数不是字符串,则直接返回这个参数。
    2. 如果是字符串,会将字符串当成JavaScript代码进行编译,如果编译失败则抛出一个语法错误异常。
    3. 如果编译成功,则开始执行这段代码,并返回字符串的最后一个表达式或语句的值
    4. 如果最后一个表达式或语句没有值,则返回undefined
    5. 如果字符串抛出一个异常,这个异常将把该调用传递给eval()
  3. **eval()**使用了调用它的变量作用域环境——查找变量的值和定义新变量和函数的操作和局部作用于中代码完全一样。如果在最顶层代码中调用eval(),则会作用于全局变量和全局函数
  4. 需要保证传递给eval()的字符串在语义上讲的通——不能通过eval()往函数中任意粘贴代码片段,比如,eval("return;")是没有意义的。
  5. 全局eval()——ES 3规定任何解释器不允许对eval()赋予别名,如果eval()函数通过别名调用的话,会抛出一个EvalError异常
  6. 大多数实现:当通过别名调用时,eval()会将其字符串当成顶层的全局代码来执行。执行的代码可能会定义新的全局变量和全局函数,或者给全局变量赋值,但却不能使用或修改主调函数中的局部变量(这样不会影响到函数内的代码优化)
  7. ES 5的规则——直接的eval,直接调用eval()时,总是在调用它的上下文作用域内执行。其他的间接调用则使用全局对象作为其上下文作用域,并且无法读、写、定义局部变量和函数。
 var geval = eval; // Using another name does a global eval
 var x = "global", y = "global"; // Two global variables
 function f() { // This function does a local eval
     var x = "local"; // Define a local variable
     eval("x += 'changed';"); // Direct eval sets local variable
     return x; // Return changed local variable
 }
 function g() { // This function does a global eval
     var y = "local"; // A local variable
     geval("y += 'changed';"); // Indirect eval sets global variable
     return y; // Return unchanged local variable
 }
 console.log(f(), x); // Local variable changed: prints "localchanged global":
 console.log(g(), y); // Global variable changed: prints "local globalchanged":
  1. 严格eval():在严格模式下调用eval()时,或者eval()执行的代码段以“use strict”指令开始,这里的eval()是私有上下文环境中的局部eval。在严格模式下,eval执行的代码可以查询或更改局部变量,但不能在局部作用域中定义新单变量或函数

其他运算符

  1. 条件运算符?:——三元运算符
  2. typeof运算符
  3. delete运算符——用来删除对象属性或者数组元素
    1. delete希望操作数是一个左值,如果不是,delete不进行操作并返回true
    2. delete成功,返回true,否则返回false
    3. 并不是所有的属性都可删除,一些内置核心和客户端属性是不能删除的
    4. 用户通过var语句声明的变量不能删除
    5. 通过function语句定义的函数和函数参数也不能删除
    6. ES 5中如果delete的操作数是非法的,比如变量、函数或函数参数,将抛出一个语法错误
    7. 在严格模式下,delete删除不可配置的属性会抛出一个类型错误异常;非严格模式下,只是返回false
  var o = {x:1, y:2}; // Define a variable; initialize it to an object
  delete o.x; // Delete one of the object properties; returns true
  typeof o.x; // Property does not exist; returns "undefined"
  delete o.x; // Delete a nonexistent property; returns true
  delete o; // Can't delete a declared variable; returns false.
  // Would raise an exception in strict mode.
  delete 1; // Argument is not an lvalue: returns true
  this.x = 1; // Define a property of the a global object without var
  delete x; // Try to delete it: returns true in non-strict mode
  1. void运算符——常用在javascript:URL中,void让浏览器不必显示这个表达式的计算结果。如:
  <a href="javascript:void window.open();">Open New Window</a>
  1. 逗号运算符