10.1 JS 基础语法

开发环境

和其他脚本语言一样,编写JavaScript只需要简单的文本编辑器,当然为了提高开发效率,基本的语法着色和自动完成功能还是很有帮助的。

对于一些大的项目,我们建议你使用WebStorm(需要商业授权,基于IntelliJ IDEA的一款Web定制IDE)。如果你同时还是PHP/Java后台开发工程师,那么推荐使用NetBeans、Eclipse或IDEA。

对于喜欢轻量级工具的同学,可以使用Sublime Text或VIM,要快得多,对电脑配置要求低。

如果只是简单的测试一些脚本实例或者作品演示,我们建议使用踏得网的在线开发工具,可以保存在云端,在线调试和分享,而且也支持排版、自动补齐和Emmet功能。

当然和编写HTML文档一样,你还需要一个浏览器(推荐Chrome),浏览器内置了JS的解释器,也称为JavaScript引擎,浏览器负责解释JS脚本并执行。而且Chrome还提供了强大的开发工具来调试JS脚本。

如何加载JS脚本

JavaScript是内置在网页中的脚本语言,其加载方式通过script关键字来实现。如下所示:

通常我们把JS脚本放在页面内容的最下面来加载(即紧靠body闭合标签),一来为了页面内容加载得更快,再者JS的执行依赖于内容。

我们也可以直接把JS脚本写在页面中,但是跟单独的js文件相比,这样的代码不能被浏览器缓存,会影响性能,而且不符合结构和行为分离的最佳实践。

基础语法

如前面章节所述,JavaScript的语法采用了类C语法,对象部分的语法类似Java。

语句(Statements)

计算机编程语言由一系列指令(instructions)所组成,这些被称之为语句。语句必须遵循正确的语法,这样才能被正确的解释。 JS的语法相比较系统语言而言要灵活或宽松得多,你可以简单的把语句放在不同行,比如:
first statement
second statement
如果要放在同一行,你必须使用分号(semicolons)将它们隔开,比如:
first statement; second statement;
在每条语句的后面加上分号是一个良好的编程习惯,这样可读性好。

注释(Comments)

我们可以在JS代码中添加注释,这只是为了提高代码的可维护性,方便工程师阅读以帮助理解代码,这些注释将被JS引擎所忽略。

注释可以使用如下几种形式:

  • //,用于单行注释
    // note to self
  • /* */,用于多行注释
    /* note to self
           note to self second line */
  • <--,和双斜杠一样,用于单行注释,是HTML风格的注释,比较少用。
    <!-- note to self

推荐使用前2种C语言风格的注释。

变量(Variables)

变量是程序设计语言里面保存可变数据的单元,一般要求先声明一个变量,然后才能使用它。但JavaScript并不要求这样严格,如果我们在JS中直接给一个变量赋值,那么它将被自动声明。

mood = "happy";
    age = 33;
上面的代码自动声明了2个变量,并被直接赋值。我们说“变量包含那个值”。你可以通过alert语句来打印变量的值:
alert(mood);
    alert(age);
尽管可以不做声明,但先声明再使用仍然是一个好的编程习惯和实践。你可以像下面这样声明:
var mood;
    var age;
或者在一条语句中一起声明:
var mood, age;
还可以在声明的同时直接赋值:
var mood = "happy";
    var age = 33;
或者:
var mood = "happy", age = 33;
显然最后一种方法最简洁。

JavaScript语言是大小写敏感的,因此变量mood和Mood, MOOD是不同的。

JS的语法不允许变量名中包含空格或标点符号。下面这一行将导致语法错误:
var my mood = "happy";
一个合法的变量名只能包含字母、数字、$符号和下划线。为了提高可读性,我们可以使用下划线把较长的变量名分隔开,如下:
var my_mood = "happy";
上述语句中的文本“happy”是一个“文字(literal)”,所谓“文字”就是它仅仅表示字面原意,而var是一个关键字,my_mood是一个变量名。

数据类型(Data types)

mood变量是一个字符串,而age是一个整型。这里有两种数据类型,但是JS对此不做区分。有些编程语言如C需要明确声明数据类型,这类语言称为强类型语言(strongly typed languages)。 而JS是弱类型语言(weakly typed language)。这意味着你可以随意更改变量的类型,下面的语句在强类型语言中是非法的,但在JS中可以工作:

var age = "thirty three";
age = 33;

字符串(Strings)

字符串由0或更多字符组成。字符包括字母、数字、标点符号以及空格。字符串必须包含在引号中。你可以使用单引号或双引号:

var mood = 'happy';
var mood = "happy";

上面两条语句是等价的。但是如果你的字符串中包含单引号,那么使用双引号是好的:

var mood = "don't ask";
如果你仍然想使用单引号,那么需要使用反斜杠(backslash)符号把字符串内的单引号转义(escaping)一下,这和C语言一样:
var mood = 'don\'t ask';
类似的,使用双引号的情况也是这样处理。为了风格统一,我们应该保持使用其中的一种方式。

数字(Numbers)

数字包含整形、浮点,可以是正数或负数:

var age = 33;
var length = 10.25;
var temperature = -20.33333333

布尔值(Boolean)

布尔值是另外一个常用类型,代表是或否。可以取值:true 或 false。使用时注意不要和字符串弄混了: 下面的语句定义了一个布尔值:var married = true; 而这一句定义的是字符串:var married = "true";

数组(Arrays)

字符串,数字和布尔值都是标量(Scalars),也就是只能包含单个取值,如果想在一个变量中保存多个取值,我们可以使用数组。 数组表示同名的一组数据,每个数据都是数组的一个元素。比如披头士乐队,包含若干成员: var beatles = Array(4); 有时候我们不能提前知道元素的个数,这要求数组能够动态分配和管理。在JS里,你只需要如下声明即可: var beatles = Array();

添加元素到数组中被称为填充(populating)。当我们填充数组时,不只是指定取值,还需要指定元素位置,这称为元素索引(index)。 索引被包含在方括号中:array[index] = element;

现在我们来填充上述beatles数组,和C语言语法一样,第一个元素的索引为0:

var beatles = Array(4);
beatles[0] = "John";
beatles[1] = "Paul";
beatles[2] = "George";
beatles[3] = "Ringo";
我们还可以在声明时一次性赋值,以逗号(Comma)隔开各个元素:
var beatles = Array("John","Paul","George","Ringo");
每个元素的索引被自动生成,从0开始。你甚至可以忽略Array名称,相反使用一个简写的方括号来表示:
var beatles = ["John","Paul","George","Ringo"];
C语言中数组的数据类型必须是确定且相同的,而JS则宽松灵活得多,我们可以保存多种类型的数据在一个数组中:
var lennon = Array("John",1940,false);
数组的元素可以是一个变量:
var name = "John";
beatles[0] = name;
或者从另外一个数组读取元素来填充:
var names = Array("Ringo","John","George","Paul");
beatles[1] = names[3];
数组还可以嵌套,也就是数组元素也可以是数组:
var lennon = Array("John",1940,false);
var beatles = Array();
beatles[0] = lennon;
要读取嵌套数组的元素取值,需要使用更多方括号,比如beatles[0][0]的值为“John”,beatles[0][1]是1940,而beatles[0][2]是false。 不过使用索引有一个不好的地方是,可读性不好,我们记不清楚这样的数字。还好,我们可以使用关联数组。

关联数组(Associative arrays)

beatles数组是一个数字数组。索引是从0开始的自增长整数。如果我们只给定了数组的值,那么该数组就是数字类型的。 我们可以通过指定元素的索引来改变这一缺省行为,比如使用字符串:

var lennon = Array();
lennon["name"] = "John";
lennon["year"] = 1940;
lennon["living"] = false;
这被称之为关联数组。数字数组实际上是关联数组的一个特例。

操作(Operations)

有了数据,我们还需要添加操作,才能完成实际的应用任务。

算术运算符(Arithmetic operators)

加减乘除就是算术运算符。每个算术运算符需要一个操作符(Operator),比如等号(=)用来赋值,加号(+)用来执行加法。类似的,减法操作符是(-),除法是斜杠(/),乘法是星号(*)。 操作符有优先级,乘除要高于加减操作,为了消除歧义,可以使用括号,括号里面的操作具有最高的优先级。我们还可以使用连加(++)操作符来表示自我递增,如 year++;year = year + 1;是等价的。 如果要递增超过1的数值,可以使用+=号,比如year += 2;year = year + 2;是等价的。 类似的,-- 操作符用来递减。 加法(+)操作符有点特殊,既可以用来加数字,也可以用来连接字符串:

var message = "I am feeling " + "happy";
我们还可以使用把数字和字符串拼接起来,因为JS是弱类型的。数字会被自动转换成一个字符串:
var year = 2005;
var message = "The year is " + year;
但是如果你使用加号运算符在两个数字上,结果会是数字之和:
alert ("10" + 20);
alert (10 + 20);
第一条语句的结果是字符串“1020”,而第二条语句返回数字30。

条件语句(Conditional statements)

浏览器解析脚本时,语句是顺序被执行的。所以我们可以通过条件语句来设定在执行更多语句之前首先检查某个先决条件:

if (condition) {
statements;
}
条件被包含在小括号中,取值总是被解析为布尔类型,也就是true或false。而大括号中的语句只在条件为true的时候被执行。 我们可以给if语句添加else语句来表示不满足条件时如何处理: if (1 > 2) { alert("Sun rises from west."); } else { alert("All is well with the world"); }

比较操作符(Comparison operators)

比较操作符通常使用在条件语句中。比如等于(==), 不等于(!=), 大于(>), 小于(<), 大于等于(>=), 和小于等于(<=)。 记住,判断是否相等用的是连等号(==),而单个等号(=)是用来赋值的,条件的值将取决于等号右侧语句的值。 下面这个是错误的相等比较:

var my_mood = "happy";
var your_mood = "sad";
if (my_mood = your_mood) {
alert("We both feel the same.");
}
上述条件语句的值为true,和if("sad")等价。 正确的比较应该是下面这样:
var my_mood = "happy";
var your_mood = "sad";
if (my_mood == your_mood) {
alert("We both feel the same.");
}
这次,条件语句的结果是false。

逻辑操作符(Logical operators)

如果存在多个条件语句,我们可以使用逻辑操作符来进行归并,比如判断数字是否在5到10之间:

if ( num>=5 && num<=10 ) {
alert("The number is in the right range.");
}
这里使用了“与”操作符,由两个与号组成(&&)。这就是一个逻辑操作符。 下面是使用“或”操作符的例子,表示判断数字是否小于5或大于10:
if ( num > 10 || num < 5 ) {
alert("The number is not in the right range.");
}
还有一个逻辑关系,也就是“否”(not),使用感叹号表示,代表条件不成立:
if ( !(num > 10 || num < 5) ) {
alert("The number IS in the right range.");
}
注意,这里我们把条件语句包含在括号中,因为引起代码阅读和执行优先级上的歧义。

循环语句(Looping statements)

当我们需要遍历数据或需要执行一些重复性工作时,就需要使用循环语句。

while

while循环和if很类似: while (condition) { statements; } 唯一的区别是括号内包含的语句会被反复执行,直到条件语句为false为止。

var count = 1;
while (count < 11) {
alert (count);
count++;
}
上面的代码将执行多次,直到count被加到等于11。

do...while

do...while语句和while语句类似,唯一的区别是先执行语句,然后判断条件。

do {
statements;
} while (condition);
比如下面的代码,即使一开始count<1的条件语句就是false,仍然会先执行里面的alert等语句然后退出循环。
var count = 1;
do {
alert (count);
count++;
} while (count < 1);
for
while通常用于处理一些不确定次数或无穷循环,而对于确定次数的循环,我们可以使用for语句。
for (初始条件; 条件检查; 改变条件) {
statements;
}
我们用for语句来重构下前面的while例子:
for (var count = 1; count < 11; count++ ) {
alert (count);
}
for语句最常用的地方是数组(或链表)遍历:
var beatles = Array("John","Paul","George","Ringo");
for (var count = 0 ; count < beatles.length; count++ ) {
alert(beatles[count]);
}
上述语句将把beatles成员名都alert出来。

函数(Functions)

在JS中,函数是头等公民,从本质上来讲,函数是代码复用的一种形式。 我们把一些公共代码封装在一个函数中,然后通过函数名来调用它。这样代码显得简洁而高效。

function list() {
var items = Array("pen","book","paper");
for (var count = 0 ; count < items.length; count++ ) {
alert(items[count]);
}
}

function关键词表明这里是一个函数定义,list是函数名称,小括号里面是参数(上例中参数为空),大括号里面的是函数体也就是实际要执行的代码。 该函数的功能是把产品列表alert出来。然后我们可以像下面这样调用它:
shout();
而如果函数需要处理不同的情况,我们通过给函数传递不同的参数来实现这种变化。打个比方,函数就像是一个工厂,参数就是你的订单,这个工厂可以根据订单为不同的顾客提供服务。 比如上述打印产品列表的函数,如果我们想打印不同的产品系列,那么可以把items数据通过参数传递给该函数:
function list(items) {
for (var count = 0 ; count < items.length; count++ ) {
alert(items[count]);
}
}
这样,这段代码的复用性就得到了很大的提高,我们可以这样来调用它:
var my_order = Array("pen","book","paper");
list(my_products);
your_order = Array("ruber","notes");
list(your_order);
为了方便使用,语言本身会提供一系列基础的内置函数,比如数学计算、字符串处理等。 有哪些内置函数,一般不需要特别的记忆,可以查阅在线JS参考手册。 在上面的函数中,我们用的是alert输出,但实际使用时,函数通常是处理数据,并返回处理后的数据。比如:
function multiply(num1,num2) {
var total = num1 * num2;
return total;
}
上述代码返回乘法结果。 函数还可以被用作数据类型,我们可以把函数结果赋值给一个变量:
var num1 = 5;
var num2 = 10;
var result = multiply(num1,num2);
alert(result);
我们在命名函数时,建议遵循Camel Casing风格,也就是首字母小写,然后其它每个单词的首字母大写。

变量范围(Variable scope)

一个好的编程习惯是在第一次使用1个变量时,加上var声明。尤其是在函数中使用变量时。 这是因为变量都是有其生存范围的,在代码规模较大时,变量定义的冲突常常会导致难以调试的错误。 变量可以是全局(global)或局部(local)的,全局变量可以在脚本的任何地方引用。 使用全局变量的好处是可以在不同模块中共享数据,不好的地方是全局变量很危险,可能会被某个地方的代码无意中改变。 局部变量的生存范围只存在于其声明所在的函数中。当我们在函数中使用var来声明变量时,该变量是一个局部变量,仅在该函数上下文中有效。 如果你不使用var,该变量被当作一个全局变量。如果已经有一个同名的变量,该函数将可能覆盖它的值。看看下面的例子:

function square(num) {
total = num * num;
return total;
}
var total = 50;
var number = square(20);
alert(total);

变量total的值在计算平方值时被意外的修改了。正确的代码应该如下:

function square(num) {
var total = num * num;
return total;
}
函数应该被看作是一个自我包含的脚本块,原则不应该影响除了传入参数和返回值意外的任何变量,或被影响。

对象(Objects)

如前面章节所述,尽管函数和过程是JS中的一等公民,JS也引入了面向对象的设计。 一个对象(object)是一个自包含的数据集,数据包括两种格式:属性(properties)和方法(methods): 属性是属于对象的一个变量,而方法是对象可以调用的函数。 这些属性和方法被包含在一个单独的实体中,也就是这个对象,属性和方法都是通过点号(dot)语法来访问: Object.propertyObject.method() 比如一个Person对象,包含年龄、心情等变量,可以执行的动作是行走、睡觉等:

Person.mood
Person.age
Person.walk()
Person.sleep()
和C++/Java语言不同的是,JS中没有关键字class(ES6以前),这对于熟悉系统编程的开发人员会有点不习惯。JS的面向对象模型是基于原型的。 和类相对应的概念是通用对象(generic object),而通用对象的实例化(instance)就是一个对象。我们都是人类,每个人都是一个单独的个体。 创建一个对象实例的方法是使用new:
var ryan = new Person;
jeremy.age
jeremy.mood
在JS中,并没有Person这个对象,需要开发者自己去创建,这些叫自定义对象。但JS里面也包含很多已经定义好的对象,称之为本地对象(native objects)。

原生对象(Native objects)

数组(Array)就是这样一个原生对象。其他例子包括Math 和 Date,包含一些很有用的处理数字和日期的方法:

var num = 7.561;
var num = Math.round(num);
alert(num);

var current_date = new Date();
var today = current_date.getDay();
上面的代码使用Math对象的round方法来取整,使用Date对象的getDay方法来获取今天的日期。

宿主对象(Host objects)

原生对象不是唯一的一种预定义对象。另一种对象不是由JavaScript语言本身提供,而是由它所在的运行环境所提供的接口。比如浏览器。 由浏览器提供的对象称为宿主对象。宿主对象包括窗体、图像和元素。这些对象可以用来获取网页中的表单、图像和表单元素的信息。 这里不讲述如何使用这些宿主对象。还有另一个对象可以用来获取网页中的任何元素的信息:文档对象(document)。这个会在DOM操作相关章节讲述。