本文编写于 169 天前,最后修改于 169 天前,其中某些信息可能已经过时。

说明

  前面的两篇博客介绍了js中的基本知识中的变量类型、标识符等。这篇博客主要谈表达式以及运算符。

原始表达式

  原始表达式就是表达式中最小的,不能在分割的表达式,一般指变量、常数直接量、关键字(true、false、null、this),这里需要注意的是undefined是一个全局变量,而不是关键字。


  数组初始化表达式是一个方括号,中间放置数组中的元素,元素间用逗号分割。元素可以是表达式、数组、空。

[]    //一个空数组,数组里面没有元素
 [1+2,3+4]    //一个拥有两个元素的数组,第一个是3,第二个是7。
 [[1,2,3],[4,5,6],[7,8,9,]]    //数组中的元素还可以是数组。
 [1,,,,5]    //数组中的元素可以为空,这时空位会填充值undefined。

  在执行var a=[1,,,,5]这条语句后,在chrome中表现为下图所示:

  对象初始化表达式和数组初始化表达式非常类似。

var stu={name:"张三", age:15};    //定义一个拥有两个属性的对象。

  上面这行初始化对象的表达式类似于下面的数组,事实上js也确实支持这种写法访问对象中的属性。

 stu["name"]="张三";
 stu["age"]=15;

  如果你学过java那么上面那句var stu={name:"张三", age:15};可以很容易的写成下面的java代码:

class Student{
    String name;
    int age;

    Student(String name, int age){
        this.name=name;
        this.age=age;
    }
}

Student stu=new Student("张三", 15);

  不过js可以完美支持多层的内部类,而java却对内部类有好多限制。

属性访问表达式

  在js中,属性访问有①expression.identifier、②expression[expression]这两种方式。其中第一种虽然方便却只能用于对象,而且要求访问的属性(identifier)不能是关键字、不能含有空格、不能是数字。而第二种访问方式不仅支持对象还能用于数组,并且用于对象访问自己的属性时没有第一种那么多的限制。

var o={x:1, y:{z:3}};
var a=[o, 4, [5, 6]];
o.x   //1:访问o的x属性
o.y.z    //3:访问o的y的z属性
o["x"]    //1:访问o的x属性
a[1]    //4:访问a的索引为1的元素
a[2]["1"]    //6:访问a[2]中的索引为1的元素
a[0].x    //1:通过访问a[0]得到o,再访问o的x属性

运算符

  js所支持的运算符如上图所示,学过其它编程的同学基本上看上一眼就知道了。这里只对需要注意的部分做出说明。在基本算术运算符(加减乘除和求余)只有加法是相对有难度的,因为有时候它是加法,而有时候它又是字符串连接符,具体用法可概括为只有+运算符两边都是数字才是加法,更详细的用法请参考这篇博客。此外还需要谈的就是求余运算符,其运算结果的符号和左操作数相同,即5%1=1、-5%1=-1。

  在js的位运算时,会有一个隐式转换过程。默认情况下js中的数字都是存储为64位浮点数格式,在位运算的时候会转换为32位整数,所以隐式转换会截断小数部分和整数中超过32位能表示的部分。此外NaN、infinity和-infinity会转换为0。

  关于等于(==)运算符和严格等于(===)运算符请参考本系列教程的第一篇。

in运算符
  in运算符希望它的左操作数是一个字符串或者能转换为字符串,希望它的右操作数是一个对象。如果右侧对象拥有一个名为左操作数的属性则整个in表达式返回true。

var point={x:1, y:1};
"x" in point    //true:对象中有一个名为x的属性
"z" in point    //false:对象中不存在一个名为z的属性
"toString" in point    //true:对象继承了toString()方法

var data=[7, 8, 9];
"0" in data    //true:数字包含下标为0的元素
1 in data    //true:数字转换为字符串
3 in data    //false:没有索引为3的元素

&&与||
  学过C和java等语言的同学应该知道这两个运算符有一个特性就是短路,这个特性在js中也同样存在,习惯性利用这种特性为函数提供默认值。

function copy(o, p){
     o.p = p || {};
 }

  上面的代码中,如果copy函数被调用的时候p形参有值,则将p复制给o.p,否则将默认值{}复制给o.p。

表达式计算

  在js中存在一个函数叫eval(),虽然这是一个函数,但一般被当成运算符来对待。一般来说函数是可以起别名的,比如var f=eval;再通过f调用eval函数。但是一般不推荐对eval函数起别名,ECMAScript3标准甚至直接禁止对eval函数起别名,所以说它几乎被当成运算符对待。之所以这样是因为这涉及到代码优化的问题,如果允许对eval函数起别名将严重影响代码的优化过程。

  上面谈了eval的特点,现在来说一下它的作用。这个函数只有一个参数,且一般是字符串。如果传入的参数是字符串,js解释器会把这个字符串当成js代码来执行,且其执行环境为直接调用这个eval函数的上下文(下面将详细解释),如果其传入的参数不是字符串,则eval函数直接返回这个参数值。

var global=1;
var foo = function(ele){
    var local = 2;
    eval(ele);
    console.log(where);
}
foo("console.log(global);console.log(local);var where='I am here!';");
console.log(where);

  上面这行代码的执行结果(在chrome中)如下图所示:

  根据上面所说的eval的执行环境为直接调用eval的上下文,在上图显示的结果中为第8行所处的上下文,这样的话eval自然能访问global和local变量,但是其定义的where变量只能让foo函数内部且在第8行以后的代码访问,这样以来第9行代码能正确执行,而第12行代码抛出异常。

  前面谈过ECMAScript3禁止对eval起别名,但是现实中大多数解释器的实现并非如此,之后的ECMAScript5标准也允许别名调用,但规范了其行为。当eval被别名调用时,其运行环境永远为全局环境,与其调用者环境没有关系,也就是说间接调用(别名调用)无法读、写、定义局部变量。

var geval = eval;
var x = "global", y = "global";
function f(){
    var x="local";
    eval("x += ' changed';");
    return x;
}
function g(){
    var y="local";
    geval("y += ' changed';");
    return y;
}
console.log(f(), x);
console.log(g(), y);

  上面代码执行的结果(在chrome中)为:

链接:
JavaScript基础入门教程(一) http://blog.zh66.club/index.php/archives/81/
JavaScript 基础入门教程(二) http://blog.zh66.club/index.php/archives/83/