一、 注释
JavaScript 注释分两种 跟c风格有点相似:
- 单行注释://
- 多行注释:/**/
提示:单行注释不能折行,可以嵌套;多行注释可以折行,不能嵌套;
二、 语句
一段脚本就是一系列计算机能够一步一步执行的指令。每一条单独的指令或步骤就被称为一条语句。语句以分号(;
)结尾,多个语句可以写在一行内。
var a = 10; // 这是一条赋值语句
var b = 10; var c = a + b; // 多条语句可以写在一行内;
三、 变量
1、var 变量
脚本必须暂时地存储一些完成工作所需的信息,可以将这些数据存储在变量中,变量是对“值”的引用,使用变量等同于引用一个值。每一个变量都有一个变量名,如下所示:
var a = 10;
JavaScript使用 var
关键字定义变量,以上示例定义了一个变量a,其引用的值为10。变量一定要“先声明后使用”,不能使用未声明的变量,否则系统会报错。变量分为声明与赋值,如下所示:
var a; // 声明变量a
a = 10; // 为变量a赋值
提示:你可以将变量的声明与赋值放在一起,一步到位,如
var a = 10
,这种定义变量的方式被称为字面量定义,即在声明的同时为变量赋值。
如果只是声明变量而不为其赋值,则该变量的值是undefined
。undefined 是一个JavaScript关键字,表示未定义。
var a;
a; // undefined
你可以在同一行中声明多个变量。JavaScript是一种动态类型的语言,也就是说变量的类型没有限制,可以赋予各种类型的值,变量的类型在赋值之后确定,JavaScript本身存在一种类型推断机制,所谓类型推断,就是根据变量的值确定变量的类型:
var a, b; // 一行中声明多个变量
var c = 1; // 声明变量,并为其赋值为1,此时变量的类型为’Number‘;
c = 'Hello, world!'; // 修改变量的c的值,此时变量的类型为’String‘;
我们再来看一个示例,如果你使用var关键字声明了一个已经存在的变量,则该变量是无效的,但是,如果你在声明时为该变量赋了值,则该变量会覆盖前面的值,如下所示:
var x = 10;
var x; // 重复声明,该变量无效;
var x = "China!"; // 重复声明并且赋值,则x的值为’China!‘,覆盖之前的’10‘;
注意:变量必须先声明后使用;在声明变量时,你应该总是使用
var
关键字。
2、let 变量
ES6新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
var命令会发生 ”变量提升“ 现象,即变量可以在声明之前使用,值为 undefined ,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
// var 的情况
console.log(a); // 输出undefined
var a = 2;
// let 的情况
console.log(b); // 报错ReferenceError
let b = 2;
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let
又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。ES6明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
let不允许在相同作用域内,重复声明同一个变量。
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
3、const 常量
const
为ES6新增指令,用于声明一个只读常量,常量就是不可变的量,即定义之后不能被修改。
const a = 10;
a = 20; // Uncaught TypeError: Assignment to constant variable
你需要注意的是,const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const
只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
提示:ES6 建议不再使用
var
命令,而是使用let
命令取代。如果一个量不可变,使用const
命令。
四、输出语句
JavaScript通过 console.log() 函数向控制台输出信息,请参考如下示例:
let name = 'Petter';
// 1、输出字符串
console.log('Hello, world!');
// "Hello, world!"
// 2、输出变量
console.log(name);
// "Petter"
// 3、输出变量/字符串
console.log('Hello, ' + name + '!');
// "Hello, Petter!"
// 4、字符串模板输出(ES6新增)
console.log(`Hello, ${name}!`);
// "Hello, Petter!"
五、 标识符
标识符(identifier)是用来识别具体对象的一个名称。最常见的标识符就是 “变量名 ”,以及后面要提到的 “函数名 ”。JavaScript语言的标识符对大小写敏感,所以a和A是两个不同的标识符。标识符的命名规则如下:。
- 必须以字母、美元符号($)以及下划线(_)开头,不能以数字开头;
- 名字可以包含字母、数字、美元符号或下划线。注意,在变量中不能使用连字符(-)和点(.)号;
- 不能使用关键字或保留字来对变量命名。
- 所有的变量都是大小写敏感的,变量
x
与变量X
是不一样的; - 变量命名必须见名知意,语义化,如创建一个数字变量,名字可以取名为number,不能使用拼音或中文命名变量;
- 如果变量的名字由多个单词组成,则应遵守驼峰命名法则,即第一个单词首字母小写,后面单词首字母大写,如一个描述人的姓名的变量,正确的变量命名应该是personName;
六、关键字与保留字
在上一节中我们提到了两个词语——“关键字”和“保留字”,就是我们在进行变量命名的时候需要避免使用的两类名称。所谓“关键字”,就是JavaScript现在正常使用的一些名称,它们可能是系统变量名,也可能是一个JS内置的方法函数名,还可能是已知宿主环境提供的API。而“保留字”是现在暂时没有使用,但在未来JavaScript可能会使用的名称,这些“保留字”经过JS不断的发展,已经有一些成为了“关键字”。
- 关键字
break | do | instanceof | typeof | if | else |
---|---|---|---|---|---|
case | var | new | finally | return | void |
continue | for | switch | while | debugger | function |
this | with | try | catch | throw | delete |
default | in | implements | package | public | interface |
private | static | let | protected | yield |
- 保留字
abstract | enum | int | short | boolean | export |
---|---|---|---|---|---|
byte | extends | long | super | native | synchronized |
class | float | throws | const | goto | transient |
volatile | double | import |
但是需要明白的是JavaScript中的“关键字”和“保留字”也不是一成不变的,它们也会随着这个语言的发展新增或删除(例如“debugger”就是ECMAScript 5中新增的关键字)。但考虑到“向上兼容”,以前提出的这些“关键字”和“保留字”也应该在规范出现更改之后也避免去使用,以免造成程序异常。
七、变量作用域
JavaScript变量分为“局部变量”和“全局变量”。所谓“局部变量”就是指所定义的变量只会在一个限定的范围内生效,通常指在某一个函数(function)内。而通过全局的方法或者其它函数是无法获取该变量的值的。
而“全局变量”,它的值可以被所有的方法或函数获取到。通常将一个变量写在所有函数的外部的变量,就叫做“全局变量”,但是在函数内部,若忘记在变量名前加上var
的话,该变量也会成为全局变量,从而不小心被其它函数将值给误改了,所以在函数内部一定要记得在变量名前加上关键字 var
来定义一个变量。
var a = "全局变量";
function print() {
console.log(a); // a 为全局变量,在任意位置都可以访问
}
function test() {
var x = "局部变量"; // x 在函数内部创建,为一个局部变量
}
console.log(x); // x 为局部变量,此处不能访问x变量,程序会报错
变量作用域表示变量起作用的范围,全局变量的作用域为整个文件内部,称之为全局作用域。而局部变量的作用域通常限于函数内部,称之为函数作用域。ES6中,还新增了块级作用域,块级作用域的出现得益于let
指令的出现,块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// 立即执行函数
(function () {
var str = `Hello, World!`;
console.log(str);
})();
// 块级作用域
{
let str = `Hello, China!`;
console.log(str);
}
八、 运算符与表达式
1、 基本运算符
基本运算符主要用于一些数值操作,这里假设有变量:x = 10, y = 10;
:
运算符 | 作用 | 实例 | 运算结果 | 备注 |
---|---|---|---|---|
+ | 相加 | x + y | 20 | |
- | 相减 | x - y | 0 | |
* | 相乘 | x * y | 100 | |
/ | 相除 | x / y | 1 | |
% | 模运算(取余) | x % y | 0 | |
++ | 自增 | x++ | 11 | |
– | 自减 | x– | 9 | |
** | 指数运算符 | x**3 === x * x * x | 1000 | ES6 |
2、 赋值运算符
赋值运算符是为JavaScript中的变量赋值,即将该运算符右方计算出的值赋予左侧的变量。假设:x = 10, y = 3;
运算符 | 作用 | 实例 | 相当于 | 结果 | 备注 |
---|---|---|---|---|---|
= | 赋值 | x = y | x = y | 3 | |
+= | 变量与值相加 | x += y | x = x + y | 13 | |
-+ | 变量与值相减 | x -= y | x = x - y | 7 | |
*= | 变量与值相乘 | x *=y | x = x * y | 30 | |
/= | 变量与值相除 | x /= y | x = x / y | 3.333… | |
%= | 变量对值取余 | x %= y | x = x % y | 1 | |
**= | 变量求指数值 | x**2 | x=x*x | 100 | ES6 |
3、 关系运算符
关系运算符描述两个变量的关系,其返回值类型为布尔类型,即true
和false
。这里假设有变量x = 10, y = 20
运算符 | 作用 | 实例 | 结果 |
---|---|---|---|
== | 判断两个变量是否相等 | x == y | false |
> | 判断等号左边的变量是否大于等号右边的变量 | x > y | false |
< | 判断等号左边的变量是否小于等号右边的变量 | x < y | true |
= | 判断等号左边的变量是否大于等于等号右边的变量 | x >= y | false |
<= | 判断等号左边的变量是否小于等于等号右边的变量 | x <= y | true |
=== | 恒等于,要求不仅数据类型要一致,值也要一致 | x === y | false |
!== | 不恒等于,只要类型或值不一样,结果为true | x !== y | true |
4、 逻辑运算符
逻辑运算符是JavaScript用于判断几条语句成立情况的一种运算,该运算符的返回值类型为布尔类型。这里假设有变量x = -1, y = 1
运算符 | 描述 | 规则 | 实例 | 结果 |
---|---|---|---|---|
&& | 逻辑与 | 同时为真才为真 | x < 0 && y > 0 | true |
|| | 逻辑或 | 同时为假才为假 | x > 0 || y > 0 | true |
! | 逻辑非 | 取反 | ! (x < 0) | false |
5、 三目运算符
三目运算用于条件判断,根据判断的结果执行不同的语句,其语法形式为:a ? b : c
。如果a表达式的条件为真,则执行b语句,否则执行c语句。我们来看一个示例:
var isLogin = true; // 定义变量’isLogin‘用于记录登录状态
isLogin ? console.log('已登录!') : console.log('未登录!');
上述代码使用三目运算符根据登录状态执行不同的语句,代码中的 console.log()
函数用于向控制台输出信息。由于定义的变量值为true,因此上述代码执行后会在控制台输出:“您已登录!”
八、 基本数据类型
数据类型决定了如何将值存储到计算机的内存中。和其它编程语言一样,JavaScript也有自己的数据类型,主要有以下6种:
Number:数值型,该类型是用于表示阿拉伯数字的数据类型,可以是“0”、正数、负数、浮点数(小数)。数值型的值可以参与数学运算。
String:字符串类型,该类型用于表示如“英文”,“中文”,“其它国家语言”,“键盘上的符号”,“特殊编码”等字符,甚至可以是数字。该类型的值都需要用一对英文引号(可以是单引号,也可以是双引号)阔起来,否则在使用该变量的时候会报出未定义的错误,成为一个“未定义”类型,而本想作为字符串的数字也会成为一个“数值型”。
Boolean:布尔类型,该类型只有两个值,即:
- true:表示“是”,在判断语句中可以用“非0”、“非undefined”、“非null”、“非NaN”、“字符串”、“对象”甚至是一个“函数”表示
- false:表示“否”,在判断语句中可以用数值“0”、“undefined”、“null”或“NaN”表示。
Object:该类型用于表示一个对象,在JavaScript中并没有“数组”这个类型,所以数组在JavaScript里也作为一个对象,空值
null
也是一个对象。在JavaScript中可以通过语句“new Object()”来创建一个对象来赋给某一个变量。以下为对象的示例代码:
var a = new Object(); // 对象
var b = [1, 2, 3, 4, 5]; // 数组对象
var c = ["html", "css", "JavaScript", "jQuery"]; // 数组对象
var d = {
name: "Henrry Lee",
gender: "male",
e_mail: "lihy_online@163.com",
age: "24",
github: "https://github.com/LiHongyao"
}
var e = {
stu1: {name:"Q", age:"23", grade="A"},
stu2: {name:"K", age:"22", grade="B"},
stu3: {name:"J", age:"23", grade="A+"},
}
当然,上例中的这种写法只是一种示例,用于表示对象的格式。实际生产环境中,每个变量对象的值都是动态地从服务器端的数据库通过一定的代码手段获取的。示例中的变量“d”和“e”对象的格式也就是Web技术领域中常会提到的“JSON格式”(JavaScript Object Notation,JavaScript的对象表示法),从名称就能看得出来,它所表示的数据类型正是一个“对象”。现在编程中的“面向对象思维”或“面向对象编程”正是在这个“对象类型”的基础上开展的。
function:函数类型,该类型表示变量的类型为一个可运行的“函数”。一个函数内包含一组语句(至少一条语句),通过这些语句的组合去实现一个特定的功能。在JavaScript中函数主要有以下分类:
- 普通函数
- 构造函数
- 变量函数
- 匿名函数
- 闭包函数
函数类型的几个简单示例:
// 普通函数 function print() { console.log("Hello, world!"); } // 构造函数 function Person(name, age, gender, address) { this.name = name; this.age = age; this.gender = gender; this.address = address; } // 变量函数 var showAlert = function() { aler("Hello, world!"); } // 匿名函数 (function() { console.log("匿名函数无需调用,可以直接执行!"); })(); // 闭包 function closure() { var info = "每15s弹出一次"; setInterval(function() { // 这是一个匿名函数 alert(info); }, 15000); }
函数类型是JavaScript中最复杂的一个数据类型,但它在JavaScript中扮演中相当重要的作用,它也同是面向对象编程中一个非常核心的内容。函数的设计非常大程度地决定了一个网站或应用的性能。从编程角度来讲,它直接影响到了代码的可复用性,代码量,可以维护性,可扩展性等因素。这个对于JavaScript的初学者来说也是最难掌握好的一个部分,就算一个已经工作了好几年的Web前端开发工程师,在对函数的组织和优化上都会下不少功夫。
undefined:未定义,该类型出现于调用一个变量时,该变量不存在或未定义值时产生的类型。很多时候这个值的出现是由于编程中的粗心造成的,但在有的时候会刻意地“制造”出这个类型,或者说是利用这个类型去完成程序中的一些特殊的功能。
ES6还新增了Set、Map 2个类型。
九、类型转换
JavaScript是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
let x = y ? 3.14 : "china";
上面代码中,变量x
到底是数值还是字符串,取决于另一个变量y
的值。只有在代码运行时,才可能知道x
的类型。
虽然变量没有类型,但是数据本身和各种运算符是有类型的。如果运算符发现,数据的类型与预期不符,就会自动转换类型。比如,减法运算符预期两侧的运算子应该是数值,如果不是,就会自动将它们转为数值。
'4' - '3' // 1
上面代码中,虽然是两个字符串相减,但是依然会得到结果 1
,原因就在于JavaScript将它们自动转为了数值。
1、强制转换
强制转换主要指使用 Number()、String() 和 Boolean()三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。
1.1 Number()
使用Number函数,可以将任意类型的值转化成数值。下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。
1.1.1 原始类型值的转换规则
原始类型的值主要是字符串、布尔值、undefined和null,它们都能被Number转成数值或NaN(not a number)。
Number函数将字符串转为数值,要比 parseInt 函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。
上面代码中,parseInt 逐个解析字符,而 Number 函数整体转换字符串的类型。
1.1.2 对象的转换规则
简单的规则是,Number 方法的参数是对象时,将返回 NaN
。
1.2 String()
使用String函数,可以将任意类型的值转化成字符串。转换规则如下。
1.2.1 原始类型值的转换规则
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:true转为“true”,false转为“false”。
- undefined:转为“undefined”。
- null:转为“null”。
1.2.2 对象的转换规则
String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
1.3 Boolean()
使用Boolean函数,可以将任意类型的变量转为布尔值。它的转换规则相对简单:除了以下六个值的转换结果为false,其他的值全部为true。
- undefined
- null
- -0
- 0或+0
- NaN
- ”(空字符串)
注意,所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true。
所有对象的布尔值都是 true
,这是因为JavaScript语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于 obj1 && obj2
这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为 true
。
2、自动转换
下面介绍自动转换,它是以强制转换为基础的。
遇到以下三种情况时,JavaScript会自动转换数据类型,即转换是自动完成的,对用户不可见。
// 1. 不同类型的数据互相运算
123 + 'abc' // "123abc"
// 2. 对非布尔值类型的数据求布尔值
if ('abc') {
console.log('hello')
} // "hello"
// 3. 对非数值类型的数据使用一元运算符(即“+”和“-”)
+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN
自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用String函数进行转换。如果该位置即可以是字符串,也可能是数值,那么默认转为数值。
由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用 Boolean()、Number() 和 String() 函数进行显式转换。
2.1 自动转换为布尔值
当JavaScript遇到预期为布尔值的地方(比如if
语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用 Boolean函数。
因此除了以下六个值,其他都是自动转为 true
。
- undefined
- null
- -0
- 0或+0
- NaN
”(空字符串)
下面这个例子中,条件部分的每个值都相当于
false
,使用否定运算符后,就变成了true
。
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true
2.2 自动转换为字符串
当JavaScript遇到预期为字符串的地方,就会将非字符串的数据自动转为字符串。系统内部会自动调用 String() 函数。
字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
这种自动转换很容易出错。
上面代码中,开发者可能期望返回 120,但是由于自动转换,实际上返回了一个字符 10020。
2.3 自动转换为数值
当JavaScript遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用 Number() 函数。
除了加法运算符有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
上面代码中,运算符两侧的运算子,都被转成了数值。
一元运算符也会把运算子转成数值。